Skip to content

ruby on rails

iPads are just made for Screencasts

Killer app for iPads

Trying to justify getting yourself a new iPad3? My favourite use for my iPad lately has been watching quality screencasts by Gary Bernhardt (Destroy All Software) and Ryan Bates (RailsCasts). Each offer a premium subscription model where you pay $9 a month for access to their full catalog along with all new videos.

I've never really been into Screencasts but I've only recently discovered the secret seems to be in keeping them short. Here are two I've been really getting into.

Worth Paying For

How much do you earn each month from your craft? I wouldn't think twice about paying $9 a month to the individuals who create such valuable art. To be honest I think we're getting it cheap.

Destroy All Software

Gary Bernhardt has a communication style I can only describe it as "performance vim". You spend most of the ten or so minutes watching him make text dance in a full screen terminal. It's like pairing with someone you can really learn something from. The iPad is just the right size to make it work.

Destroy All Software Screencasts Destroy All Software Screencasts

RailsCasts

Ryan Bates has been producing short (5-12 minute) screencasts covering all manner of topics of interest to "Ruby People" for years now and making them available for free. Ryan makes his screencasts available via RSS which means they automatically find their way to my iPad.

Too many in our community give of themselves without making it easy enough for us to give back. I contacted Ryan a while back to ask how I could donate. I'm glad he has now made restructured things to make his ongoing work sustainable.

Ryan Bates has been producing quality RailsCasts for years now Ryan Bates has been producing quality RailsCasts for years now

Rails Web API Versioning

[2013-12-18 Why was so I attracted to the idea of versioning web APIs? I've never needed this.]

One way to make different versions of a web service available from the same webserver is to allow clients to prepend a version identifier to the URI path. There's some discussion about the best way to use this but I like to default to no version identifier in URIs.

Passenger makes it easy to run different versions of your Rails web api alongside each other.

  • http://brandish.local/customers.json
  • http://brandish.local/v1/customers.json
  • http://brandish.local/v2/customers.json

Update Your Webserver Config

Passenger allows you to direct traffic to different instances of your app. Here I'm serving brandish_v2 if no version identified is included in the URI.

<code># /etc/apache2/sites-available/brandish
<VirtualHost *:80>
  ServerName brandish.local
  CustomLog /var/log/apache2/brandish_access_log combined
  RackEnv development

  DocumentRoot /srv/brandish_v2/public
  RackBaseURI /v1
  RackBaseURI /v2
  <Directory /srv/brandish_v2/public/v1>   
    Options -MultiViews  
  </Directory>
  <Directory /srv/brandish_v2/public/v2>   
    Options -MultiViews  
  </Directory>
</VirtualHost>
</code>

This is the only tricky bit. Passenger expects the RackBaseURI's you used in the web config to point at symlinks to the public/ directories of the different versions of your apps.

<code>ln -s /srv/brandish_v1/public -> /srv/brandish_v2/public/v1
ln -s /srv/brandish_v2/public -> /srv/brandish_v2/public/v2
</code>

Note Passenger was failing for me because it expected application.rb to be in controllers directory. I fixed it by creating a symlink from application_controller.rb to application.rb. I'm a bit confused as I thought that had been fixed in recent versions of Passenger. Maybe the RailsBaseURI code is not up to date.

JSON with Ruby and Rails

JSON is a beautiful format for storing objects as human readable text. It’s succeeded where XML has failed. Not only is it not shit, it’s actually quite good! But don’t just take my word for it, have a look at some of the “cool” ways you can generate and consume JSON.

Ruby support for JSON

Ruby's JSON library makes parsing and generating JSON simple.

Converting between hash and json in Ruby

$ irb
>> require 'json'
=> true
>> json_text = { :name => 'Mike', :age => 70 }.to_json
=> "{\"name\":\"Mike\",\"age\":70}"
>> JSON.parse(json_text)
=> {"name"=>"Mike", "age"=>70}

HTTParty helps with communicating with RESTful services

Here we grab a record from Facebook.

Retrieve a JSON Resource

$ irb
>> require 'awesome_print'
=> true
>> require 'json'
=> true
>> require 'httparty'
=> true
>> ap JSON.parse HTTParty.get('https://graph.facebook.com/Stoptheclock').response.body
{
                  "about" => "Abolish the 28 Day Rule for Victorian Shelters\n\nhttp://stoptheclock.com.au\n\ninfo@stoptheclock.com.au",
               "category" => "Community",
                "founded" => "2010",
           "is_published" => true,
                "mission" => "To bring an end to the law requiring Victorian shelters to kill healthy adoptable cats and dogs after four weeks.",
    "talking_about_count" => 3,
               "username" => "Stoptheclock",
                "website" => "http://stoptheclock.com.au",
        "were_here_count" => 0,
                     "id" => "167163086642552",
                   "name" => "Stop The Clock",
                   "link" => "http://www.facebook.com/Stoptheclock",
                  "likes" => 5517
}
=> nil

HTTParty gets Classy

Creating a simple class allows you to DRY things up a bit

$ irb
>> require 'httparty'
=> true
>> class Facebook
>>   include HTTParty
>>   base_uri 'https://graph.facebook.com/'
>>   # default_params :output => 'json'
?>   format :json
>>
?>   def self.object(id)
>>     get "/#{id}"
>>   end
>> end
=> nil
>>
>> require 'awesome_print'
>> ap Facebook.object('Stoptheclock').parsed_response
{
                  "about" => "Abolish the 28 Day Rule for Victorian Shelters\n\nhttp://stoptheclock.com.au\n\ninfo@stoptheclock.com.au",
               "category" => "Community",
                "founded" => "2010",
           "is_published" => true,
                "mission" => "To bring an end to the law requiring Victorian shelters to kill healthy adoptable cats and dogs after four weeks.",
    "talking_about_count" => 3,
               "username" => "Stoptheclock",
                "website" => "http://stoptheclock.com.au",
        "were_here_count" => 0,
                     "id" => "167163086642552",
                   "name" => "Stop The Clock",
                   "link" => "http://www.facebook.com/Stoptheclock",
                  "likes" => 5517
}
=> nil

Rails support for JSON

ActiveSupport::JSON knows how to convert ActiveRecord objects (and more) to JSON. Simone Carletti explains how this differs from the standard lib.

## Encode
json = ActiveSupport::JSON.encode(object) # extra methods like :include
json = Offering.first.to_json(:include => :outlet, :methods => [:days_waiting])

## Decode
ActiveSupport::JSON.decode(json)

Rails3 niceness

Adding JSON to your Rails3 app doesn't require a lot of extra code. You can specify method calls and associated objects to include as well as restrict the attributes returned. Simple eh?

class PostController < ApplicationController
  respond_to :json, :html, :jpg, :xml

  def index
    respond_with(@posts = Post.all),
                   :methods => [:average_rating],
                   :include => :comments
  end

  def show
    respond_with(@post = Post.find(params[:id])), :only => [:name, :body]
  end

end

Posts in this series

Asset Fingerprinting with Paperclip

Update: My Asset Fingerprinting patch was included in paperclip-2.3.4 in Oct 2010. Thanks jyurek!

One of the tricks to improving the performance of your website is to optimize caching. By instructing browsers and proxies to cache assets that don't change very often (css, images, javascript), we can reduce page load time as well as bandwidth costs.

By instructing browsers and proxies to never check for updated versions of cached assets we can further speed things up. Google recommend the use of fingerprinting to enable dynamic updating of cached content. This simply means including a fingerprint of the resource in it's URL so the URL gets updated when the resource changes.

paperclip

Paperclip is a popular file attachment plugin for Ruby on Rails. It was a perfect candidate for fingerprinting support as it generates resource URLs from stored data.

Enabling it is as simple as:

  1. Install paperclip-2.3.4
  2. Add an :attachment_fingerprint column to your database
  3. Include :fingerprint in the path for your attachment (see below)
has_attached_file :avatar,
  :styles => { :medium => "300x300>", :thumb => "100x100>" },
  :path => "users/:id/:attachment/:fingerprint-:style.:extension",
  :storage => :s3,
  :s3_headers => {'Expires' => 1.year.from_now.httpdate},
  :s3_credentials => "#{RAILS_ROOT}/config/s3.yml",
  :use_timestamp => false</code>

This enables us to set far future expire headers so that browsers don't need to check for a newer version. If a change does occur, say because a user uploads a new avatar, the new filename will be rendered in your html and the cached version will be ignored.

The example above will set Expires headers in S3. If you're using local storage you can configure your webserver to do something similar.

We disable the timestamped query string because some proxies refuse to cache items with query strings.

Fingerprinting support was added to paperclip-2.3.4 on 6 Oct 2010.

For more info on optimizing for caching:

http://code.google.com/speed/page-speed/docs/caching.html

How to use deprec-1 when deprec-2 gem is released

[2013-12-19 I've not done any work on deprec in the past few years]

deprec-2 preview has been available at www.deprec.org for a while now and is quite stable. When I release it as a new version of the deprec gem it's going to cause users of deprec-1 to see errors. I recommend using deprec-2 for new installations but understand that some people will want to continue using deprec-1 for legacy systems. In preparation for release of deprec-2.0.0 I've some minor modifications so that deprec1 and deprec2 co-exist on the same machine.

To continue using deprec-1.x, install deprec-1.9.3:

$ sudo gem install --version 1.9.3 deprec

Update your projects deploy.rb:

require 'rubygems'
gem 'deprec', '< 1.99'
require 'deprec'

Adding the following to your .caprc will load the correct version of deprec based on the version of Capistrano you're using:

require 'rubygems'
gem 'deprec', '< 1.99' unless respond_to?(:namespace)
gem 'deprec', '>= 2.0.0' if respond_to?(:namespace)
require 'deprec'

You can call capistrano 1 with the command:

cap _1.4.2_ show_tasks

or create an alias by putting the following in your .profile:

alias cap1="`which cap` _1.4.2_"

This will allow you to use the following:

cap1 show_tasks

If everything's working you'll see the deprec tasks listed.

Getting monit to restart mongrel after a crash

An annoying aspect of the Mongrel webserver is that it refuses to start if it detects a stale pidfile. This causes real problems when you're trying to use something like Monit to automatically restart mongrel after a crash.

Most daemons check whether the process_id in the pidfile is running. Ezra has indicated that a future release of mongrel will do this but in the meantime, we can use mongrel_cluster with the --clean option to remove stale pidfiles before starting mongrel.

Update /etc/init.d/mongrel_cluster to include the --clean option in the start and restart commands.

  start)
    # Create pid directory
    mkdir -p $PID_DIR
    # chown $USER:$USER $PID_DIR

    mongrel_cluster_ctl start --clean -c $CONF_DIR
    RETVAL=$?
;;
  stop)
    mongrel_cluster_ctl stop -c $CONF_DIR
    RETVAL=$?
;;
  restart)
    mongrel_cluster_ctl restart --clean -c $CONF_DIR
    RETVAL=$?

Update your monit config to use mongrel_cluster. Note that monit sets a restricted path (PATH=/bin:/usr/bin:/sbin:/usr/sbin) and the internals of the mongrel_cluster gem call mongrel_rails without specifying the path. mongrel maintainers [http://mongrel.rubyforge.org/ticket/31#comment:1 suggest] using env in the monit command and said this is already fixed in a svn. I've found creating a symlink from /usr/local/bin/mongrel_rails to /usr/bin/mongrel_rails does the trick. Then update your monit config to look something like this:

check process mongrel-8010 with pidfile /var/www/apps/tubemarks/shared/pids/mongrel.8010.pid
group mongrel
start program = "/usr/local/bin/ruby /usr/local/bin/mongrel_rails cluster::start --only 8010 -C /etc/mongrel_cluster/tubemarks.yml"
start program = "/usr/local/bin/ruby /usr/local/bin/mongrel_rails cluster::stop --only 8010 -C /etc/mongrel_cluster/tubemarks.yml"

The nice part here is the --only option which allows you to restrict the command to a single mongrel process (as defined in the config file).

I've deprec-1.99.16 has been updated to use mongrel_cluster as described above to clean up stale pidfiles before starting mongrel. As a side note, I was glad to see mongrel has a new website and is being fed and cared for by it's new owners.

Capistrano and Rake

Last night I read the chapter on automation in Tom Limoncelli's book, Time Management for System Administrators. He spent a lot of time extolling the virtues of Make and how useful it can be in automating sysadmin tasks. Rails makes good use of Rake (think 'ruby-make') to specify administrative tasks. Make/Rake let you specify dependencies in your tasks and by checking file timestamps you can avoid running tasks unnecessarily. This has given me an idea for a change to the way deprec works.

It would be nice if I could restart mongrel on the server using the same command as I use in Capistrano. I'm not suggesting using Rake from my workstation but rather to have many of deprecs cap tasks call a Rake task on the server. So 'cap deprec:mongrel:restart' would call 'rake deprec:mongrel:restart' on the server(s).

There would be two main benefits to this.

Firstly, all deprec tasks would be available from the command line on the target host (obviously some bootstrapping would be required to install Rake, Ruby, etc initially). While I prefer not to have to log in manually to each of the servers in a cluster, there are times when I'm logged in and would like to be able to run tasks locally.

A second benefit is that we could take advantage of dependencies. Compiling PHP, after Apache has already been installed, will no longer cause Apache to be recompiled. Installing Subversion, when Apache has not already been installed, will cause it to be installed. This will reduce the amount of time tasks take to run (which is non-trivial when it involves compiling the likes of openssl!)

Back in May 2007, Bradley Taylor of Railsmachine released a nice gem called Machinify. It's a set of Rake tasks that will install a Rails stack on Ubuntu. It's very nicely written and quite readable. I considered whether I should make deprec dependent on Machinify but as it lacks some of the tasks I want (install, nginx, postgres, etc) I think it would be better for deprec come with its own Rake tasks.

So, it's very tempting to re-architect deprec2 but another thing to consider is this: would we be better off with a working deprec2 next week or a re-designed deprec2 at some later point? I think working code is a better result than ideas that won't get implemented in the available timeframe.

One issue with calling remote rake tasks via Cap is dealing with interactive dialogs. deprec currently deals with this by listening for certain output from the remote call. I can't see why deprec couldn't simply call the remote rake task and listen for the same output. This would allow deprec tasks to be extracted into rake tasks.

So my current thinking is to press on with deprec2 development and then extract the tasks into Rake tasks at some future point.

Using rails-1.2.3 with Rails 2.0 Preview release installed

Rails 2.0 Preview has been released, along with a great summary of changes it includes:

http://weblog.rubyonrails.org/2007/9/30/rails-2-0-0-preview-release

While I was excited to install the gem (gem install rails --source http://gems.rubyonrails.org) this caused problems went I got back to developing an existing app even though I had rails-1.2.3 specified in my environment.rb.

It turns out that the preview release is version 1.2.3.7707 and Rails considers that to be the most appropriate version to use when I specify 1.2.3. Not what I was expecting!

A quick fix so your existing apps will still use 1.2.3 is to change this line in their config/boot.rb.

-rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last
+rails_gem = Gem.cache.search('rails', "#{version}.0").sort_by { |g| g.version.version }.last`

Update 2007-10-05 This morning the very entertaining new Rails Envy Podcast informed me about r2check, a script that checks your existing Rails app and let's you know what you need to do to make it Rails2.0 ready. It worked for me. Thanks to Mislav Marohnić for saving me time. :-)