Missing Feature Labs http://www.missingfeature.com This is Michael Rykov posterous.com Thu, 22 Sep 2011 14:20:02 -0700 Custom-class serialization in ActiveRecord 3 - it's not what you think! http://www.missingfeature.com/custom-class-serialization-in-activerecord-3 http://www.missingfeature.com/custom-class-serialization-in-activerecord-3

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.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Mon, 23 Nov 2009 13:04:00 -0800 Serializing custom ActiveRecord attributes as JSON http://www.missingfeature.com/serializing-activerecord-associations-as-json http://www.missingfeature.com/serializing-activerecord-associations-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.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Thu, 19 Nov 2009 19:43:00 -0800 Scheduling Crons with DelayedJob http://www.missingfeature.com/scheduling-crons-with-delayedjob http://www.missingfeature.com/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.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Fri, 23 Oct 2009 08:17:39 -0700 Untitled http://www.missingfeature.com/7676318 http://www.missingfeature.com/7676318

Tumblr_krzkhffzzj1qzcawgo1_250

Interesting day…

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Sat, 22 Aug 2009 19:56:00 -0700 jRuby on Rails - Google App Engine Performance Tip http://www.missingfeature.com/jruby-on-rails-google-app-engine-performance-0 http://www.missingfeature.com/jruby-on-rails-google-app-engine-performance-0

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.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Tue, 28 Jul 2009 15:57:34 -0700 Untitled http://www.missingfeature.com/7676324 http://www.missingfeature.com/7676324
The top 5% of programmers probably write 99% of the good software.

http://www.paulgraham.com/wealth.html

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Mon, 27 Jul 2009 18:29:40 -0700 Untitled http://www.missingfeature.com/7676326 http://www.missingfeature.com/7676326
Stress primarily comes from not taking action over something that you can have some control over.

Jeff Bezos in an Inteview with the Academy of Achievement. See also: regret minimalization. (thanks @msg) (via jackcheng)

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Sat, 25 Jul 2009 16:29:00 -0700 Untitled http://www.missingfeature.com/7676331 http://www.missingfeature.com/7676331

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

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Thu, 23 Jul 2009 16:38:00 -0700 DataMapper and Rails' TimeWithZone http://www.missingfeature.com/datamapper-and-rails-timewithzone http://www.missingfeature.com/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…

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Sun, 12 Jul 2009 10:44:00 -0700 jQuery Visualize: Accessible Charges & Graphs http://www.missingfeature.com/jquery-visualize-accessible-charges-and-graph-0 http://www.missingfeature.com/jquery-visualize-accessible-charges-and-graph-0 http://www.filamentgroup.com/lab/jquery_visualize_plugin_accessible_charts_gr...

Always in search of a simple lightweight charting library, I figured I’ll publicly bookmark this gem for my (and your) future use.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Fri, 29 May 2009 07:00:13 -0700 Auto-track outgoing links with Google Analytics http://www.missingfeature.com/auto-track-outgoing-links-with-google-analyti-0 http://www.missingfeature.com/auto-track-outgoing-links-with-google-analyti-0

Google Analytics Help does not track outgoing links unless you embed onclick handlers in every link.  This seemed repetitive, so here’s what I came up with to automatically listen for outgoing click events and send them to GA.

This code is written for jQuery and Ruby on Rails, but can be adapted for your purpose.

$(function() {
  $('a[href^=http]').click(function() {
    var link = $(this).attr('href');
    if(pageTracker && 0 > link.indexOf('<%= request.domain %>')) {
      pageTracker._trackPageview('/outgoing/' + link.replace(/https?:\/\/([^\?]*).*/i, '$1'));
    }
  })
});

Things to note:

  • jQuery attribute selector [href=^http] filters only absolute links
  • pageTracker existence check is for development mode where Google Analytics is disabled (in my case)
  • Replace regular expression captures “domain/path” of the link without the query string.  Modify according to your needs.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Fri, 29 May 2009 03:51:00 -0700 ActiveSupport-JCache: Rails Cache on Google App Engine http://www.missingfeature.com/activesupport-jcache-rails-cache-on-google-ap http://www.missingfeature.com/activesupport-jcache-rails-cache-on-google-ap

ActiveSupport-JCache is a jRuby gem that lets you use Rails.cache in a jRuby on Rails on Google App Engine application.  The library uses the Java Memcache API to store/restore values.

Given that the library uses a standard JCache API, it should also work with other Java caches that offer that interface (i.e. Ehcache).  This, however, might change when I dive into the low-level Google App Engine APIs to implement explicit per-key expiration (expires_in).

Highlights

  • Rails Cache that works with Google App Engine
  • Wraps JCache API as JcacheStore
  • No per-key expiration (:expires_in) support

Installing

sudo jruby -S gem install activesupport-jcache

Configuring

In your config/environment.rb, add the following:

config.gem "activesupport-jcache", :lib => 'jcache_store'
config.cache_store = :jcache_store

Source:

The source is at http://github.com/rykov/activesupport-jcache

Demo on Google App Engine:

Try the demo at http://pagerduty.missingfeature.com

Without caching, the slowest part of this app was normalizing a list of latencies for display via Google Charts API.  When I started caching that data, render times went from over 3 seconds to around 500-600ms.  More information about the demo.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Wed, 27 May 2009 09:59:46 -0700 Pager Duty: Free Ping Check Application http://www.missingfeature.com/pager-duty-free-ping-check-application http://www.missingfeature.com/pager-duty-free-ping-check-application

In addition to some time well-spent in the sun, I have spent a bit of the Memorial Day weekend behind a keyboard.  Pager Duty is where I ended up.

The concept is simple and unoriginal: “Email me when my website is not behaving”.  You enter a URL and an email address, and we will notify you if your site stops returning successful (200) responses.

Pager Duty is a jRuby on Rails app and runs on Google App Engine.  Some of the tech it uses:

Check it out at http://pagerduty.missingfeature.com/ and let me know if you want to get your site onto the home page.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Fri, 15 May 2009 06:01:00 -0700 ActionMailer-JavaMail: Rails E-Mail on Google App Engine http://www.missingfeature.com/actionmailer-javamail-rails-e-mail-on-google http://www.missingfeature.com/actionmailer-javamail-rails-e-mail-on-google

ActionMailer-JavaMail is a jRuby gem that lets you send emails from your jRuby on Rails on Google App Engine application, but also works in standard SMTP and SMTPS scenarios. The greater intent of this gem also includes utilizing JavaMail extensions that don’t have equivalents in Ruby.

I originally created this gem to bulletproof my emails from spam filters by adding DKIM signing.  A few big email providers use DomainKeys Identified Mail (DKIM) as part of their anti-spam strategy.  I found and used a Java DKIM library, because I did not find one for Ruby.

Features Overview

  • Send email via SMTP or SMTPS
  • Send emails via Google App Engine
  • DKIM message signing

Installing

sudo jruby -S gem install actionmailer-javamail

Configuring for SMTP over SSL (in a config/initializers file)

ActionMailer::Base.delivery_method = :javamail
  ActionMailer::Base.javamail_settings = {
    :protocol  => :smtps,
    :address   => 'smtp.gmail.com',
    :port      => 465,
    :domain    => 'mydomain.com',
    :user_name => 'user@gmail.com',
    :password  => 'password',
  }

Configuring for Google App Engine (Mail API):

ActionMailer::Base.delivery_method = :javamail
  ActionMailer::Base.javamail_settings = { :protocol => :gm }

Requirements:
Please make sure that you have a recent version of the JavaMail JARs in your CLASSPATH for non-Google App Engine use. I didn’t include them in the package to avoid conflicts with Sun’s license and Google App Engine’s internal libraries.

Source:
The source is at http://github.com/rykov/actionmailer-javamail

Demo on Google App Engine:
Try the demo at http://am-javamail.missingfeature.com/test/email
The source is at http://github.com/rykov/yarbl (based Ola Bini’s example)
Note: refresh a few times if the demo throws errors while warming up

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Sat, 09 May 2009 11:01:00 -0700 Guessing Time Zone with Javascript http://www.missingfeature.com/guessing-time-zone-with-javascript http://www.missingfeature.com/guessing-time-zone-with-javascript

I’ve spent a bit of time searching for a simple solution to auto-detecting the user’s time zone.  This obviously has many benefits in “local web” applications, the most basic being: to reduce friction (number of clicks/entries).

The most accurate solution (without prompting the user) could be to use a server-side IP-based solution like MaxMind’s GeoIP.  It’s effective, but also complex.  Instead, I chose a simpler route via Javascript:

  1. Compute GMT offset
  2. Select the most likely option in the drop-down
  3. Show selection to user to correct as needed

The only tricky parts is making sure to account for Daylight Savings Time, which we do by using January 1st of the current year, rather than current day.  The core of the code becomes:

var firstYearDay = new Date((new Date()).getFullYear(), 0, 1, 0 ,0 ,0 ,0 );
var tzOffset = -1 * firstYearDay.getTimezoneOffset() / 60;

Your select options probably have strings like “(GMT-08:00) Pacific Time (US & Canada)” for either names or values, with “GMT-08:00” being the important part.  To convert the offset to that string, I did:

var offset = Math.abs(tzOffset);
var hours = String("0" + Math.floor(offset)).slice(-2);
var mins  = String("0" + ((offset % 1) * 60)).slice(-2);
var gmtStr = String("GMT" + (tzOffset < 0 ? '-' : '+') + hours + ':' + mins);

Then it’s just a matter of changing the selection of the drop-down.

Here is the whole script:

var tzOffsetToGMTString = function(tzOffset) {
  var offset = Math.abs(tzOffset);
  var hours = String("0" + Math.floor(offset)).slice(-2);
  var mins  = String("0" + ((offset % 1) * 60)).slice(-2);
  return String("GMT" + (tzOffset < 0 ? '-' : '+') + hours + ':' + mins);
}

var getUserGMTOffsetString = function() {
  var firstYearDay = new Date((new Date()).getFullYear(), 0, 1, 0 ,0 ,0 ,0 );
  var tzOffset = -1 * firstYearDay.getTimezoneOffset() / 60;
  return tzOffsetToGMTString(tzOffset);
}

/* jQuery required for this part */
$(function() {
  var tzStr = getUserGMTOffsetString();
  var match = $('select#time_zone option:contains("' + tzStr + '"):first');
  if(match.size() > 0) { $('select#time_zone').val(match.val()); };
});

You’ll notice that I’m choosing the first match for “GMT-08:00”.  I complemented that with putting all the US time zones at the top of the drop-down list for a more likely match.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov
Sun, 03 May 2009 16:29:00 -0700 Announcing Missing Feature Labs http://www.missingfeature.com/announcing-missing-feature-labs http://www.missingfeature.com/announcing-missing-feature-labs

Welcome to the first post - the Labs are now open for business.  I decided to start a tumblelog to organize my thoughts and to share code, tips, & tricks that I didn’t find anywhere else.  I am an inventor and a software developer, so most of the posts will be about:

  • My projects and open source contributions
  • Ruby on Rails
  • Javascript & jQuery
  • jRuby (yes, both Java and Ruby)

I’ll try to keep off-topics posts to other areas of the Interwebs, but I’m not making any promises.  But if you’re into that sort of thing, make sure to follow me on Twitter.

Stay tuned…

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/168907/twprofile.jpg http://posterous.com/users/Q2MwiFb8uR Michael Rykov rykov Michael Rykov