Shuhei Kagawa

Assign Time/time string in UTC to ActiveRecord date attribute

Mar 2, 2015 - Ruby, Rails

TL;DR: gem 'date_timezone' in Gemfile and include DateTimezone in ActiveRecord models if you are on the east side of the prime meridian.

ActiveRecord is great. It automatically converts data according to the type of database column. That's why we can throw request params, whose values are often strings, into mass assignment methods like create and update without manual conversion. The conversion works perfectly in most of the cases except for date attribute.

To date attribute, we can assign Date and date string like '2015-03-02' without any problem. However, Time and time string with different time zone, usualy UTC, don't work well here. Their own time zones are not taken into account when converted to Date.

class Application < Rails::Application
  config.time_zone = 'Tokyo'
end

class Person < ActiveRecord::Base
  # birth_date :date
end

expect(Person.new(birth_date: '2015-03-02').birth_date).to eq(Date.new(2015, 3, 2))
expect(Person.new(birth_date: Date.new(2015, 3, 2)).birth_date).to eq(Date.new(2015, 3, 2))
expect(Person.new(birth_date: Time.zone.local(2015, 3, 2)).birth_date).to eq(Date.new(2015, 3, 2))

# But...
expect(Person.new(birth_date: Time.utc(2015, 3, 1, 15)).birth_date).to eq(Date.new(2015, 3, 1))
expect(Person.new(birth_date: '2015-03-01T15:00:00.000Z').birth_date).to eq(Date.new(2015, 3, 1))

There may be several cases that we have to assign Time or time string in different time zone to date attribute. My own case was a Single Page Application built with AngularJS that sends JavaScript's Date object to Rails API. JavaScript's JSON.parse() serializes Date into a string of ISO 8601 format in UTC time zone. This is problematic to the people on the east side of the prime meridian because they get different date when they express their beginning of date in UTC.

JSON.stringify({ date: new Date(2015, 3 - 1, 2) });
// '{"date":"2015-03-01T15:00:00.000Z"}'

I could have controlled front-end code to always send date string like '2015-03-02' or converted the ISO 8601 string with Time.zone.parse in Rails controllers. But those approaches seemed error prone. I wanted to take care of it at the bottom, ActiveRecord model. I created a concern to override date-column mutators like the following. It converts Time and time string to TimeWithZone with the application's time zone.

def birth_date=(value)
  self[:birth_date] = case value
                      when String then Time.zone.parse(value)
                      when Time then value.in_time_zone
                      else value
                      end
end

The concern was extracted as a gem, date_timezone.

gem 'date_timezone'
class Person < ActiveRecord::Base
  include DateTimezone

  # birth_date :date
end

If you are creating Rails application on the east side of the prime meridian and in trouble with the same issue as mine, please try it and share what you think.

Playing with vanilla WebGL API

Feb 15, 2015 - JavaScript, WebGL

Started writing in English on a whim.

I've been learning vanilla WebGL with webgl-workshop since the last weekend. It took some time for me to understand concepts like array buffer and element buffer but it was fun to learn the basics that we stand on.

The team behind webgl-workshop creates a set of utility modules called stackgl. It embraces the UNIX philosophy, "Write programs that do one thing and do it well." Even though the workshop doesn't use stackgl libraries, I liked the way the exercises were organized as small modules.

Before moving to another workshop module, shader-school, I ported my old openFrameworks experiment with vanilla WebGL and Audio User Media in order to check if I could use what I learned so far.

Blurred Cube

Blurred Cube

The hardest part was randomly moving the cube's edges. Because I couldn't randomly move vertices on the array buffer, I had to rewrite buffer data frame by frame. It might be inefficient, but it works well on my laptop at least.

Steps for drawing with WebGL

The exercises on webgl-workshop split steps into two phases, init and draw, like Processing. Here I leave a note on general steps in them for later use.

Init

  1. Compile shaders and link them into a program. If you have attributes in vertex shader, don't forget to set attribute location before linking. If you have uniforms, get their locations after linking for later use.
  2. Create buffers and assign data to them.
  3. Create element buffers and assign data to them if necessary.
  4. Create textures and assign data to them if necessary. You can use textures in fragment shader via texture unit number as uniform.

Draw

  1. Use program, assign data to uniforms. If they don't change frame by frame, you can do this in the setup phase.
  2. Bind buffer. If you have only one buffer, you may have done this in the setup phase.
  3. Draw arrays or elements.

karma-6to5-preprocessor

Nov 13, 2014 - JavaScript

最近 6to5 を使って ES6 を書いています。ES6 を使うには Traceur Compiler なんかもありますが、クライアント側に結構重い runtime を入れる必要があって気が進みません。そこで runtime のいらない 6to5。

今作っているのは Angular アプリなので、テストは Karma。6to5 の Preprocessor が必要です。当然あるだろうと思って検索したのですが、ない。なかったので作りました。

karma-6to5-preprocessor

ES6 で書いたスクリプトを Karma でテストすることができるようになります。Sourcemap を含めた設定例は README を見てみてください。

その後、ES6 Module も使うことにして、Browerify の transform として 6to5 と同じ作者が作っている 6to5-browserify を使うようにしました。すると Karma の方でも Browerify を使えばいいので karma-6to5-preprocessor は使う必要がなくなってしまったのです。結構需要がありそうなのに、何でなかったんだろうと思っていたのですが、これで謎がとけました。

それでも、ES6 Modules は使わないけど ES6 の便利な機能は使いたいという人はいるようで、ちょこちょこダウンロードされています。良かった良かった。