Posterous theme by Cory Watilo

Custom-class serialization in ActiveRecord 3 - it's not what you think!

Short story:
Keep using composed_of to serialize attributes if you're looking to serialize attributes as JSON or another non-YAML format.

Long story:
With the introduction of Rails 3, we are now able to specify a custom class to ActiveRecord's serialize method.  At first, I was ready to concede and deprecate my composed_of attribute-serialization trick, but the behavior of serialize still uses YAML to encode your attributes.

The new custom-class serialization is not as pure as I originally thought.  Even thought it calls dump on the custom class, which can product JSON, the results are still stored as YAML.  Here's what I mean.

Say I create a settings class that supports ActiveRecord serialization:

class Settings < Hash
  def self.dump(settings)
    settings.to_json
  end
  
  def self.load(data)
    JSON.parse(data)
  end
end

And store it in my model:

class User < ActiveRecord::Base
  serialize :settings, Settings
end

Once I save the model, here's what goes into the database:

'--- !map:Settings {}\\n\\n'

So no, I've learned that the new serialize in Rails does not give you full control of attribute serialization and if you want to bypass YAML and keep full-control, keep using composed_of.

Serializing custom ActiveRecord attributes as JSON

I'm not a big fan of ActiveRecord's serialize. But I have kept it together until I learned about DataMapper's custom serializers for custom types.  At that point, I was ready to cry.  I was saved by the following inspiration.

I've been looking for a fast (no YAML) and framework agnostic (no classname stored) way to serialize a custom object into an ActiveRecord attribute.  Here's my simple hack to create custom serializers in Rails using composed_of.

class User < ActiveRecord::Base
  composed_of :settings, :class_name => 'Settings', :mapping => %w(settings to_json),
                         :constructor => Settings.method(:from_json),
                         :converter   => Settings.method(:from_json)

  after_validation do |u|
    u.settings = u.settings if u.settings.dirty? # Force to serialize
  end
end

Yes. It's that simple. To clarify, after_validation self-assignment is needed to make sure that if you edit the Settings object in-place, that it gets serialized into the attribute. Note: I've tried the user.settings_will_change! option and it didn't work -- it forces the save, but not the serialization.

Now you can get crazy like a fox (ie. FriendFeed) and store schema-less data via Rails.

Scheduling Crons with DelayedJob

I didn't want to run & monitor a separate system to manage my periodic tasks, so I wrote a few lines of code to reschedule jobs every so often.  It goes something like this.

class DjCron
  def self.step(action, period, change = {})
    next_at = Time.now.change(change) + period

    puts "Performing #{action.to_s} and rescheduling at #{next_at.to_s}"

    self.reschedule_at(next_at, action, period, change)
  end
  
  def self.reschedule_at(send_at, action, period, change = {})
    method = Delayed::PerformableMethod.new(self, :step, [action,period, change])
    Delayed::Job.enqueue(method, 0, send_at)
  end
end

Once a single task is scheduled, it will perform the job and reschedule itself in period time. To keep it robust, rather than performing the whole job instead of the puts statement, I suggest scheduling another job. That will keep it simple and avoid any headaches with retries.

jRuby on Rails - Google App Engine Performance Tip

The process of running jRuby on Rails on Google App Engine is still a bit rough around the edges and I’m discovering new tricks every day.  This time, I found how to improve performance of an operation by over 100%.

I was looking at Pager Duty taking unreasonably long to load the home page.  Some investigation showed that the bottleneck was loading the list of latencies for the graphs, which are stored as an array of Longs in the Google App Engine Datastore. Particularly, the culprit was the conversion from a Java List to a Ruby Array.

The data comes back from the Datastore as a Java ArrayList.  At first, I used:

array = list.to_a

after some experimentation, I instead tried:

array = list.toArray.to_a

Surprisingly, this increased performance of that operation by over 100%!!!

user     system      total        real
        list.to_a   1.875000   0.000000   1.875000 (  1.875000)
list.toArray.to_a   0.916000   0.000000   0.916000 (  0.916000)

Yes, I agree, it’s strange enough to be reported.

Ixaj31a8kqcvkm5rcgf8c4fto1_1280

Stopped by Starbucks’ recently-opened “experiment” called 15th Ave Coffee & Tea today and got some photos and scoop.  Looks like SB has done their homework because, if it were not for a few “Inspired by Starbucks” labels around the shop, one could easily mistake the place for a local operation.

The place has a rustic feel similar to the 1st & Pike location, but seems to cater to a more-seasoned caffeinoisseur with around 20 varieties of coffee.  Moving on the growing tea trend, the number of tea varieties is on par.  Wine (by the glass) and beer (bottled) is also something that’s new to Starbucks.  The staff come off as Jedi of the Barista Arts with their insightful recommendations and tales of tea-tasting travels.

Everything appears up a notch… or maybe it’s just the new-store smell.

15th Ave Coffee & Tea (twitter)
328 15th Ave
Seattle, WA 98122

DataMapper and Rails' TimeWithZone

Experimenting with Datamapper, I found it lacking the time zone support of Rails and ActiveRecord.  Here’s how far I got…

class DataMapper::Types::TimeWithZone < DataMapper::Type
  primitive Time
  def self.load(value, property)
    Time.utc(*value.to_a).in_time_zone
  end
   
  def self.typecast(value, property)
    value.utc
  end
end

Unfortunately, dm-timestamps doesn’t auto-populate these types…