The Joy of Gems: Cooking up Rails Plugins

Posted by Paul McMahon 13/01/2012 at 08h25

Last night I presented at Cookpad’s Tokyo Rails event. This installment had the largest turn out I’ve seen so far, with at least forty participants. I think word’s gotten out that Tokyo Rails has the best food of any Ruby group in the city (and great people too)! This time we had some great home cooked Korean food.

My presentation was about converting Rails plugins to gems. Fortuitously, there was an announcement last week that vendor/plugins will be deprecated, and gem plugins would become the standard way of building a Rails plugin. So the timing of my presentation worked out great.

Unfortunately, I don’t think my slides showed up so well on the projector. I’ve attached them below, so if anyone is interested, please have a look at them.

Dynamic Methods vs. Method Missing

Posted by Paul McMahon 08/09/2011 at 09h38

For a class I'm organizing on metaprograming in Ruby, we’re using Metaprograming Ruby as a textbook. In the chapter Tuesday: Methods, the book introduces two techniques for removing duplication: dynamically defining methods and using method_missing. It gives examples of applying both techniques to some sample code of which a simplified version is reproduced below:

# initial code
class Computer
  def initialize(data_source)
    @data_source = data_source
  end

  def mouse
    puts "Price: #{data_source.mouse_price}"
  end

  def keyboard
    puts "Price: #{data_source.mouse_price}"
  end

  def monitor
    puts "Price: #{data_source.mouse_price}"
  end
  
  #...
end

# dynamic methods
class Computer
  def initialize(data_source)
    @data_source.methods.grep(/^(.*)_price$/) { Computer.define_component $1 }
  end
  
  def self.define_component(name)
    define_method(name) do
      puts "Price: #{data_source.send("#{name}_price")
    end
  end
end

# method_missing
class Computer
  instance_methods.each do |m|
    undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/
  end

  def method_missing(name, *args)
    super if !respond_to?(name)
    puts "Price: #{data_source.send("#{name}_price")
  end

  def respond_to?(method)
    @data_source.respond_to?("#{name}_price")
  end
end

However, the book doesn't discuss when you should use one technique versus another. Here’s my rule: only use method_missing when it is infeasible to use dynamic methods.

One good example of using method_missing is ActiveRecord's find_by method. With it, you can call a method like find_by_name_and_birthdate. While ActiveRecord could theoretically use the dynamic method technique, generating every possible method would be overkill, thus using method_missing makes sense.

One counter example can be found in an example in the Metaprograming Ruby book itself:

class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize 
    super unless %w[Bob Frank Bill].include? person
    number = 0
    3.times do
      number = rand(10) + 1
      puts "#{number}..."
    end
    "#{person} got a #{number}"
  end
end

In this case, we have three methods we want Roulette to respond to: bob, frank, and bill. As we know these methods in advance, you should use the dynamic_method strategy:

class Roulette
  %w[bob frank bill].each do |name|
    define_method name do
      number = 0
      3.times do
        number = rand(10) + 1
        puts "#{number}..."
      end
      "#{name.to_s.capitalize} got a #{number}"
    end
  end
end

Now back to our initial example with Computer, which technique should we apply? Given that we can apply the dynamic methods technique, we should use it. Oh, and assuming we are always passed an instance of DataStore, I'd refactor it like so:

# dynamic methods
class Computer
  DataSource.methods.grep(/^(.*)_price$/).each do |name|
    define_method(name) do
      puts "Price: #{data_source.send("#{name}_price")
    end
  end
end

PDF generation and Heroku

Posted by Michael 02/08/2011 at 12h07

For 請求書.jp, we wanted to provide our users with a way to generate PDF versions of invoices. We used the PDFKit gem (https://github.com/jdpace/PDFKit), which by itself is a thin wrapper around wkhtmltopdf (http://code.google.com/p/wkhtmltopdf/), to easily generate PDFs. With this tool chain it is possible to generate PDFs for any page, using the HTML and CSS you already have in place.

Installation

Installation is quite simple:

  1. add PDFKit to your Gemfile, install the wkhtmltopdf-binary gem
  2. add the following to your config/application.rb (Rails 3) to hook PDFKit into Rack:
    config.middleware.use PDFKit::Middleware
    
  3. add an initializer (config/initializers/pdfkit.rb) like this:
    PDFKit.configure do |config|
      config.default_options = { page_size: 'A4', print_media_type: true }
    end
    

Basically that’s it, but please see https://github.com/jdpace/PDFKit for more details. Anyway, pretty easy, no?

Getting it to work on Heroku

Well, the tricky part starts when you want to deploy this to Heroku.

First, you’ll need to include a statically linked 64bit Linux version of wkhtmltopdf in your application. You can download it from http://code.google.com/p/wkhtmltopdf/downloads/list (wkhtmltopdf-0.10.0_rc2-static-amd64.tar.bz2 for instance). To use the same binaries in development and production, we removed the wkhtmltopdf-binary gem again, downloaded the binaries for Linux and Mac so all our development environments are covered, and extended config/initializers/pdfkit.rb like this:

PDFKit.configure do |config|
  config.default_options = { page_size: 'A4', print_media_type: true }
  if RUBY_PLATFORM =~ /linux/
    wkhtmltopdf_executable = 'wkhtmltopdf-amd64'
  elsif RUBY_PLATFORM =~ /darwin/
    wkhtmltopdf_executable = 'wkhtmltopdf-osx'
  else
    raise "Unsupported. Must be running linux or intel-based Mac OS."
  end
  config.wkhtmltopdf = Rails.root.join('vendor', 'bin', wkhtmltopdf_executable).to_s
end

After deploying to Heroku, the second issue you are likely to notice is the following: Requesting a PDF page just sits there for a while and then returns an application error. That is because you are most likely using external stylesheets and wkhtmltopdf will request them in order to generate the PDF. But your Dyno is currently busy with generating the PDF, so it can’t respond to the incoming request regarding the stylesheets.

Unfortunately starting a second Dyno or using unicorn on the cedar stack doesn’t really help here, because Heroku’s router doesn’t know which Dynos are busy but rather distributes the workload according to different parameters. So basically doing a request from within your application to your application won’t work reliably. Putting a cache / CDN (like AWS’ cloudfront) in front of your assets mitigates the problem, but users might still see an application error because the cache / CDN needs to request those files from time to time. Serving all your assets altogether from S3 works of course, but makes deployment to Heroku harder.

The solution we employed in the end was to avoid the additional requests entirely and instead embed the printing styles into the HTML itself, using the following code in the layout (HAML):

!!!
%html{:lang => "ja"}
  %head
    %title= 請求書.jp
    - if request_from_pdfkit?
      %style{type: "text/css"}
        = File.read(Rails.root.join("public","stylesheets","print.css"))
    - else
      = javascript_include_tag 'application'
      = stylesheet_link_tag ‘application’

And in the application_helper.rb:

  def request_from_pdfkit?
    # when generating a PDF, PDFKit::Middleware will set this flag
    request.env["Rack-Middleware-PDFKit"] == "true"
  end

The downside of this solution is though that we need to have a static print.css in the public/stylesheets directory and can’t use Rails 3.1’s asset pipeline as before.

The third challenge we had to address was caused by using non-Latin script, Japanese in our case. To have Japanese script in the PDF, you need Japanese fonts installed on the server doing the PDF generation, so it can embed the font. There are no Japanese fonts installed on Heroku though. Fortunately we could come up with a way to install the required font within the Heroku environment. For that to work, we added the font to vendor/fonts in the Rails project and added the following initializer:

if Rails.env.production?
  font_dir = File.join(Dir.home, ".fonts")
  Dir.mkdir(font_dir) unless Dir.exists?(font_dir)

  Dir.glob(Rails.root.join("vendor", "fonts", "*")).each do |font|
    target = File.join(font_dir, File.basename(font))
    File.symlink(font, target) unless File.exists?(target)
  end
end

With this setup we can now reliably generate nice looking PDFs for our users.

A Step Towards Internationalizing the Japanese Ruby Community

Posted by Paul McMahon 20/07/2011 at 17h13

 

Aaron Patterson’s opening keynote at this year’s RubyKaigi once again raised the issue of communication between Japanese and international Rubyists. This topic also came up in Akira Matsuda’s and Shintaro Kakutani’s presentations. I also gave a lightning talk where I presented some ideas for creating globally minded Japanese Rubyists. Helping to address the issues raised in these presentations is a goal I am working towards.

A key part in internationalizing the local community is involving Japan’s foreign residents. At last night’s asakusa.rb meetup, I was impressed that all the speakers gave presentations in English. Having around five foreigners listening was enough of an impetus for Japanese to speak in English, something that wouldn’t have happened if all participants were Japanese.

With Tokyo Rubyist Meetup, we are creating an opportunity for Japanese and non-Japanese Rubyists to get together. However, it will take more than a meetup every couple of months to create real change within the community. With that in mind, I created a survey for “Rubyを使っての英語学習”, asking participants for feedback on how they would like to combine using Ruby and learning English.

Results of「Rubyを使っての英語学習」Survey

The result shows that people are most interested in interactive activities, such as pair programming or a workshop, in a small group or one-on-one basis. As a trial of this, I’ve created Drop in Ruby 英会話, which will have up to four participants come to our office, where we will talk about and use ruby together. By running this event in a casual manner, I hope to be able to adapt the session to the participants individual skill levels, and to get a better idea of how we can continue with this approach in the future.

Like learning a language, the most important thing in making the community more global is to try. As such, I intend to continue to explore different paths we can take to internationalization. Continued feedback from everyone is welcome. You can find me on Twitter as @pwim.

Using the Asset Pipeline under Rails 3.1

Posted by Paul McMahon 29/06/2011 at 10h24

請求書.jp allows Japanese freelancers and small businesses to easily create invoices. We’ve built it using Rails 3.0 in conjunction with Coffee Script and Sass, and host the application on Heroku. Although CoffeeScript and Sass have made developing the service easier, getting them setup on Heroku is a bit of a hassle. However, Rails 3.1 introduces the asset pipeline, which not only makes it easier to use CoffeeScript and Sass, but also handles the packaging of these resources into a single file for increased performance.

Although Rails 3.1 has not been officially released yet, it is out of beta and into the fourth release candidate. Given how attractive the asset pipeline was, I decided to give upgrading 請求書.jp a shot.

The biggest challenge was to find information about how the asset pipeline works. I was able to come across a couple articles and a presentation DHH gave, but overall the information was sparse on how it actually worked. The best success I had in understanding how everything worked was to generate a new rails project with 3.1 and then use the scaffold command to generate a simple resource. By studying the generated code, I was able to figure out how to convert our application. The following is a summary of the asset pipeline specific changes I made.

# Gemfile
gem 'rails', '3.1.0.rc4'
gem 'sprockets', '= 2.0.0.beta.10' # rails 3.1.0.rc4 compatible
gem 'sass-rails', "~> 3.1.0.rc"
gem 'coffee-script'
gem 'uglifier'

# app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require ../../../vendor/assets/javascripts/externals
//= require_tree .

# vendor/assets/javascripts/externals.js:
//= require ./jshashtable-2.1.js
//= require ./jquery.numberformatter-1.2.2.min.js

# app/assets/javascripts/invoices.coffee
// Invoicing specific CoffeeScript that is dependent on jquery.numberformatter

# app/assets/stylesheets/application.css
/*
 *= require_self
 *= require_tree ./web
 */

# app/assets/stylesheets/_compatibility.css.sass
/* Macros for browser compatibility */

# app/assets/stylesheets/web/*.css.sass
@import compatibility
/* Styling of various elements */

# app/assets/stylesheets/print.css.sass
/* Print specific CSS */

# config/application.rb
config.assets.enabled = true

# config/environments/production.rb
config.assets.compress = true
config.assets.js_compressor = :uglifier

After getting everything migrated to the asset pipeline, the rest of the upgrade involved identifying outstanding issues in Rails 3.1. These issues appeared in places where I was doing something that was a bit unusual, such as accessing a relation in an after_initialize block or storing an unsaved ActiveRecord object to the session. Rather than delving into the internals of Rails, I’ve worked around these issues.

After resolving these issues, we deployed it to Heroku. There we discovered one issue - that the New Relic plugin isn’t compatible with Rails 3.1. Once removing the plugin, we had no other issues.

Upgrading 請求書.jp to Rails 3.1 took me a total of eleven hours. In its current state, Rails 3.1 is usable, but upgrading requires a fair amount of independent research and debugging. Once it is actually released, these hiccups should go away, and upgrading your application should go smoothly.

Updating a real world application to Rails 3

Posted by Michael 03/12/2010 at 19h05

We have just finished updating one of our bigger projects from Rails 2.3.8 to 3.0.3 and wanted to share our experience.

The update process:

For the update we performed the following tasks:

  • basic Rails 3 framework update, including new routes and switch to bundler
  • update the gems and plugins, replace some gems / plugins with others or with custom code (see below)
  • resolve Rails and shoulda deprecations
  • adapt to Rails 3 new escaping behavior
  • port all mailers to the new ActionMailer API

For the update process itself, we followed the various advise out there.

Finding replacements for the gems and plugins we used wasn't always straight forward.  The replacements we had to do:

Some numbers:

To give you an impression of the size of the application, here are the sloccount numbers:

  • test: 4047 sloc
  • app: 2497 sloc
  • vendor: 606 sloc
  • config: 332 sloc
  • lib: 188 sloc

Change statistics (taken from git, comparing before starting the upgrade with upgrade finished):

  • all directories excluding vendor:
    268 of 698 files changed with 2191 insertions, 1886 deletions
  • app, config, lib, test only:
    260 of 263 files changes with 1957 insertions, 1801 deletions

As you can see, changes were needed to nearly every file in app, config. The main reason we also had to change so many test cases were the deprecations in shoulda.

In total we spent 51 hours on the upgrade, plus 10 hours debugging a very strange issue.

The basic update and getting our test cases running again took us around 2 days. Another 5 days we then spent mainly on:

  • Resolving deprecations and minor issues which were caused by changed behavior in new Rails or gems.
  • Updating our own plugins and gems. This was rather challenging because for some parts of Rails the documentation is still lacking or outdated.
  • The new escaping behavior wasn't caught by our test cases. So manual testing to find any escaped HTML chunks in was required.
  • Locale in URL handling: The new router still isn't flexible enough and we couldn't DRY this up completely. For simpler sites routing-filter works nicely though.

All in all, the update took a bit longer than we expected (which was about a week). We attribute this to the amount of detail that needed to be addressed.

Seamless Rails integration with jQuery Tools Dateinput

Posted by Paul McMahon 19/11/2010 at 15h31

Rails default date input is functional, but not very user friendly. For instance, the following is what date input previously looked like in Doorkeeper.

However, by using the Javascript library jQuery Tools' Dateinput, I changed improved the user interface to the following.

The issue with Dateinput, and other date selection widgets, is that they don't use the Rails style drop downs for inputs, but rather a text input (or HTML 5's date input in the case of Dateinput). To work around this, there are two general strategies: adapt your Rails application to support text inputs for dates, or adapt the widget to use Rails date drop downs. I prefer the second strategy as it is overall much less intrusive.

To accomplish this, I adapted a snippet I found for adapting jQuery UI's datepicker to Rails to the following which is available as a gist.

// Based on http://snipt.net/boriscy/datetime-jquery-formtastic/

$.tools.dateinput.localize("ja", {
  months: '1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月',
  shortMonths: '1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月',
  days: '日曜日,月曜日,火曜日,水曜日,木曜日,金曜日,土曜日',
  shortDays: '日,月,火,水,木,金,土'
});
$.tools.dateinput.conf.format = 'yyyy-mm-dd';

$(document).ready(function() {
  $.tools.dateinput.conf.lang = $('html').attr('lang');
  $('div.date, div.datetime').each(function(i, el) {
    var sels = $(el).find("select:lt(3)");
    var d = new Date(sels[0].value, parseInt(sels[1].value) - 1, sels[2].value);
    var dateinput = $(">input type="date" /<").dateinput({ value: d} );

    // Without this, the field is initially blank
    dateinput.val(dateinput.data().dateinput.getValue($.tools.dateinput.conf.format));

    dateinput.change(function(event, date) {
      $(sels[0]).val(date.getFullYear());
      $(sels[1]).val(date.getMonth() + 1);
      $(sels[2]).val(date.getDate());
    });
    $(sels[0]).before(dateinput);
    $(sels).hide();
  });
});

By just adding this javascript file, along with the base dateinput widget, you can convert all your date inputs to use the widget. No modification of any internal application or view logic required. The Japanese localization is also included, along with automatic locale switching based on the html's lang attribute.

Announcing Tokyo Rubyist Meetup

Posted by Paul McMahon 02/09/2010 at 15h43

For many of our development projects, we use Ruby as our programming language. The language is not only interesting from a software development perspective, but also from a cultural one. Ruby was originally created by Yukihiro Matsumoto, and many of the core developers are also Japanese.  Although this programming language began in Japan, it is now popular throughout the world. 

Because many of the Japanese ruby developers are not so comfortable with English, there is a bit of a rift between the two communities.  This divide between the international and Japanese Ruby communities came up several times during RubyKaigi.  At the same time, both sides want to work to close this gap.

As a Canadian in Japan I feel I am in an excellent position to help bring these two communities together.  I am both tapped into the English speaking Ruby community around the world through the internet, and the Japanese Ruby community by virtue of my location.

With this in mind, I created Tokyo Rubyist Meetup. This group will give Japanese Rubyists a chance to meet with non-Japanese Rubyists like my self.  Within an hour of the announcement of the group over twitter, we already had 25 people sign up, so it seems there is a demand for such a group.

We are going to have our first meeting towards the end of this month (the exact date is to be decided).  If you are interested in Ruby and living in Tokyo please join us. 

Enabling url parameter based sessions in Ruby on Rails

Posted by Paul McMahon 09/04/2010 at 13h33

Out of the box, Ruby on Rails uses cookies to store a user's session ID. This is fine for most applications, but doesn't work if your application needs to support browsers that don't support cookies, such as some mobile browsers. Instead of putting the session ID in a cookie, it must be put in the URL. This increases the possibility of session fixation attacks, where one user can take over another's session, however if security isn't paramount, this is an acceptable trade off. See the Ruby on Rails Security Guide for more details on these kinds of attacks.

To enable parameter based sessions in Rails, there are a number of changes you need to make. First, in config/initializers/session_store.rb, change the default file from containing something like

ActionController::Base.session = {
  :key         => '_application_session',
  :secret      => 'secret'
}

to

ActionController::Base.session = {
  :key         => '_application_session',
  :secret      => 'secret',
  :cookie_only => false # allow session to be loaded from params
}

# Overwrite default cookie based store
ActionController::Base.session_store = :active_record_store

Now Rails can read the session ID from the URL parameters, and doesn't store the session in a cookie (the default behaviour). Besides the ActiveRecord session store, other server based stores, such as the memcache store, work as well.

In addition to enabling Rails to read the session ID from the URL parameters, the session ID must be added as a parameter. The most basic way of doing this is to define default_url_options in the ApplicationController:

def default_url_options(options = nil)
  { request.session_options[:key] => request.session_options[:id] }
end

This will ensure that the session ID is always set in the URL.

The session ID can also be added conditionally, by doing something like the following:

def default_url_options(options = nil)
  if cookies_supported?
    super
  else
    { request.session_options[:key] => request.session_options[:id] }
  end
end

If the session ID is not included in the parameters, it will fall back to the cookie.

mobalean releases Japanese WURFL patch, ruby libraries 2

Posted by Paul McMahon 10/06/2009 at 07h45

mobalean is a strong believer in open source.  Collaboration and sharing are at the core of our philosophy.  So we are proud to announce three contributions to the developer community: a WURFL patch file containing about 700 Japanese handsets, a ruby script for parsing the Japanese carrier data and converting it to the WURFL patch file format, and a major update to the ruby WURFL api.

The WURFL is an XML file containing mobile device information such as supported markup types, screen dimensions, and flash lite support.  While the WURFL has a lot of devices in it, including some Japanese ones, the data for them is both poor in quality, and incomplete.  To remedy this, mobalean has created a WURFL patch containing data on all handsets available from the major Japanese carriers (docomo, au, and SoftBank). 

This patch contains data on approximately 700 handsets and has the carrier values for the WURFL capabilities resolution_height, resolution_width, max_image_width, colors, brand_name, model_name, flash_lite_version, xhtml_table_support, and preferred_markup.  While the base WURFL contains additional capabilities for some Japanese handsets, the values of these capabilities are often wrong.  Rather than trying to validate the data in the base WURFL, this patch takes a blank slate approach, and ignores the devices in the base WURFL (with the exception of fallbacks).  All the data in this patch comes from the carrier's official data, and as such is believed to be correct.

mobalean releases this patch to the community in the hope that other members of the community can help improve it.  As with the base WURFL, you are free to use this patch in any manner you so choose.  Our only request is that if you improve the data within, that you also contribute back this data.  Additionally, we hope this patch can eventually be merged back into the core WURFL so that all WURFL users may benefit from it.

To generate this patch, we scraped the carriers' data using a ruby script.  The script transforms the data into an intermediary result, and from that result into a WURFL patch file.  In addition to the WURFL patch file, we have also released our parsing script.  By open sourcing this script, we believe others in the Japanese mobile community, even those who are not using the WURFL, can benefit.  Additionally, we hope that modifications to the WURFL patch be made via this script instead of directly to the patch.  This way, we believe we can more easily keep the patch up to date with new handsets.

In the process of generating the patch, we wanted a way to test the resultant patch file.  We did not want to parse the XML directly, as that would not take in to account the fallback structure.  So we turned to the ruby WURFL API, but found that it did not work out of the box.  As no one else was currently maintaining this API, we decided to take over it.  In doing so, we've turned it into a ruby gem, and have released version 1.1.0 of it.  We hope that this new, easier to install version will encourage further WURFL development within the ruby community.

mobalean hopes these contributions will be useful to other developers.  If you have any questions about these projects, or anything else, don't hesitate to contact us.