Archive for April, 2009

Chill.

Wednesday, April 29th, 2009

There are two things right now that I’m tired of hearing about one more fearful than the other. The first is this “Swine Flu” thing. With one of the guys doing work on the house next door coughing his lungs up I’m sure to close my windows “just in case” and leave extra-early.

The second is this whole porn presentation argument.

Guess which I fear more? Yeah, the porn presentation argument. I fear that it will rip a community apart. A community that should be working together on getting past this issue and bettering themselves, not regressing to childish bickering. That’s what gives this community an “immature” stamp by the [insert other programming language here] groups.

Matt “made a mistake” and he’s “apologised”. What’s been done cannot be undone and what we have to do as a community is work past this and rebuild whatever part of our reputation has been damaged by Matt’s actions and the subsequent bickering.

All I ask for you guys is to…

Chill.

How Rails Works #2: Mime Types & respond_to

Saturday, April 25th, 2009

THIRD COPY This guide is available on Github

A Refresher

respond_to is a method that defines different formats that your actions, well, respond to. For those who are unfamiliar with respond_to or simply forgot about it, here’s a refresher.

Take this example:

class BlogsController < ApplicationController
  def index
    @blogs = Blog.all(:limit => 10)
    respond_to do |format|
      format.html
      format.xml
    end
  end
end

It’s an index action in a controller called BlogsController and we have a collection of Blog objects stored in @blogs. We then call respond_to and specify the block with a single argument of format. On format we call html and xml which will render app/views/blogs/index.html.erb and app/views/blogs/index.xml.erb respectively. In those templates we can iterate over the @blogs collection and do whatever with it that we fancy. There’s a shorter way to write the respond_to for this:

respond_to :html, :xml

If we want some formats to respond one way, and some others to respond another way we can do this:

respond_to do |format|
  format.html { @blogs = Blog.all(:limit => 10) }
  format.any(:atom, :rss) { @blogs = Blog.all }
end

In this example index.html.erb‘s @blogs will contain 10 objects, where index.atom.erb‘s and index.rss.erb‘s @blogs will contain all the blogs record.

Diving In

My first post in this series was my Timezone Overview for Rails 2.2. Today I would like to cover how mime types and respond_to work in Rails 2.3. The reason I choose both of these instead of just respond_to is because they tie in close together.

I’ve tried to make all the methods in this guide clickable with links to the source on Github.

Firstly I’ll talk about MIME types in Rails.

The default MIME types are loaded when Rails starts up by the final line in actionpack/lib/actioncontroller/mimetype.rb:

require 'action_controller/mime_types'

This loads the actionpack/lib/actioncontroller/mimetypes.rb file and registers the default MIME types:

Mime::Type.register "*/*", :all
Mime::Type.register "text/plain", :text, [], %w(txt)
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
Mime::Type.register "application/atom+xml", :atom
Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )

Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form

# http://www.ietf.org/rfc/rfc4627.txt
# http://www.json.org/JSONRequest.html
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )  

You may recognise the syntax used in mimetypes.rb from your app’s config/initializers/mimetypes.rb. This file is used to define new MIME types for your application and is loaded on initialization of your application by this line in initializer.rb

load_application_initializers

and the corresponding load_application_initializers method:

def loadapplicationinitializers
  if gemsdependenciesloaded
    Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer|
      load(initializer)
    end
  end
end

which is responsible for loading all your application’s initializers, including config/initializers/mime_types.rb.

The MIME types defined by Rails and your initializers are the methods that you call on the block object format or the symbols that you pass to respond_to. You may recognise these from the symbols passed to the Mime::Type.register calls earlier.

respond_to‘s code may look a bit intimidating at first but it’s not really all that bad:

def respond_to(*types, &block)
  raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
  block ||= lambda { |responder| types.each { |type| responder.send(type) } }
  responder = Responder.new(self)
  block.call(responder)
  responder.respond
end

If we use the block syntax this will just call the block object with the argument of responder which is defined as Responder.new.

If we use the single line syntax. this will just pass in the types as an array. This method then defines a lambda which is a Proc object just like a usual block. This takes one argument, called responder and then calls the types on the responder object.

The ||= on the block definition means “set this variable only if it hasn’t been set already”. The next line is defining the responder object which triggers initialize method for Responder:

class Responder
  def initialize(controller)
    @controller = controller
    @request    = controller.request
    @response   = controller.response
  
    if ActionController::Base.use_accept_header
      @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
    else
      @mime_type_priority = [@request.format]
    end

    @order     = []
    @responses = {}
  end
  ...
end

This defines a couple of key variables, namely @mime_type_priority and @responses.

The first, @mime_type_priorty calls Mime::Type.lookup_by_extension(@request.parameters[:format]) which looks like:

def lookup_by_extension(extension)
  EXTENSION_LOOKUP[extension]
end

EXTENSION_LOOKUP is defined on line #5 of mime_type.rb as a hash:

EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }

What happens when a key is looked for on this hash it calls Mime::Type.new(key) which calls the initialize method for Mime::Type:

def initialize(string, symbol = nil, synonyms = [])
  @symbol, @synonyms = symbol, synonyms
   @string = string
end

We eventually understand that all our original @mime_type_priority is simply a Mime::Type object. If we requested HTML, the MIME type would be: #&lt;Mime::Type:0x2624380 @synonyms=["application/xhtml+xml"], @symbol=:html, @string="text/html"&gt;. This element is made into an array and then used in the respond method.

The second variable, @responses is defined as an empty hash. The magic happens in the respond method:

def respond
  for priority in @mime_type_priority
    if priority == Mime::ALL
      @responses[@order.first].call
      return
    else
      if @responses[priority]
        @responses[priority].call
        return # mime type match found, be happy and return
      end
    end
  end

  if @order.include?(Mime::ALL)
    @responses[Mime::ALL].call
  else
    @controller.send :head, :not_acceptable
  end
end

This method iterates over @mime_type_priority and then checks firstly if the element is a Mime::ALL This can be achieved by making the format of the URL “all”, such as http://localhost:3000/blogs.all. If the element is not Mime::ALL this continues iterating through until it finds a format that is defined. It does this by checking if there is a key in the @responses hash… but it’s empty. So I first thought, too. Buried just over halfway is this method which is called when the file is loaded and this calls generate_method_for_mime which does some funky class_eval‘ing:

def self.generate_method_for_mime(mime)
  sym = mime.is_a?(Symbol) ? mime : mime.to_sym
  const = sym.to_s.upcase
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
    def #{sym}(&block)                # def html(&block)
      custom(Mime::#{const}, &block)  #   custom(Mime::HTML, &block)
    end                               # end
  RUBY
end

Remember back when we were calling responder.send(type)? This is what it calls, this generated method.

The generated methods take an optional block, as shown by the code for custom:

def custom(mime_type, &block)
  mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)

  @order << mime_type

  @responses[mime_type] ||= Proc.new do
    @response.template.template_format = mime_type.to_sym
    @response.content_type = mime_type.to_s
    block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
  end
end

If the first argument given to custom is not a Mime::Type object then it will do a lookup and find it. The method will then concatenate into @order mime_type. This variable is used for when Mime::ALL is specified as the mime type, it will use the first one in this list, which will be the first one you’ve called in your respond_to. If you’ve define your respond_to like this:

def index
  @blogs = Blog.all(:limit => 10)
  respond_to do |format|
    format.html 
    format.json { render :json => @blogs }
    format.any(:atom, :xml) { @blogs = Blog.all }
  end
end

The all will call html because it was defined first. Following on with this example, the format.html method isn’t passed a block, but the format.xml is. This determines what this line does in custom:

block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)

When we don’t pass a block for the format.html it will render the action, looking for index.html.erb which, in the default scaffold, will list all the blogs. Rails does this by calling:

@controller.send(:render, :action => @controller.action_name)

It calls send because render is a protected method. This passes a single argument to render, { :action => @controller.action_name } which in this example is “index”. :action option:

elsif action_name = options[:action]
  render_for_file(default_template(action_name.to_s), options[:status], layout)

The method we’re interested here is not render_for_file but default_template:

def default_template(action_name = self.action_name)
  self.view_paths.find_template(default_template_name(action_name), default_template_format)
end

This calls a number of methods, but what we’re interested in here firsty is the default_template_format method. This method looks like this:

def default_template_format
  response.template.template_format
end

and template_format looks like:

def template_format
  if defined? @template_format
    @template_format
  elsif controller && controller.respond_to?(:request)
    @template_format = controller.request.template_format.to_sym
  else
    @template_format = :html
  end
end

Our request will match the elsif in this code which will call another template_format method, this time on the request object of our controller. This method looks like this:

def template_format
  parameter_format = parameters[:format]
  if parameter_format
    parameter_format
  elsif xhr?
    :js
  else
    :html
  end
end

Yes! After all this time it does call parameters[:format]! This is also known as params[:format] and, to the well trained "html". So way back in our initial default_template call:

def default_template(action_name = self.action_name)
  self.view_paths.find_template(default_template_name(action_name), default_template_format)
end

We were firstly interested in default_template_format which is what we just covered. Now we’re interested in find_template:

def find_template(original_template_path, format = nil, html_fallback = true)
  return original_template_path if original_template_path.respond_to?(:render)
  template_path = original_template_path.sub(/^\//, '')

  each do |load_path|
    if format && (template = load_path["#{template_path}.#{I18n.locale}.#{format}"])
      return template
    elsif format && (template = load_path["#{template_path}.#{format}"])
      return template
    elsif template = load_path["#{template_path}.#{I18n.locale}"]
      return template
    elsif template = load_path[template_path]
      return template
    # Try to find html version if the format is javascript
    elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"]
      return template
    elsif format == :js && html_fallback && template = load_path["#{template_path}.html"]
      return template
    end
  end

  return Template.new(original_template_path) if File.file?(original_template_path)

  raise MissingTemplate.new(self, original_template_path, format)
end

Nothing inside the each block will match, so it will not return anything. Instead, what is returned is the line after the each block:

  return Template.new(original_template_path) if File.file?(original_template_path)

This returns a Template object like this:

#<ActionView::Template:0x2193258 @format=nil, @base_path=nil, @template_path="blogs/", @extension=nil, @locale=nil, @name=nil, @load_path=nil>

render_for_file takes this object and uses it to render the appropriate template that the user requested.

When we do pass a block to our format.xml, this is much simpler in the call to render:

elsif xml = options[:xml]
  response.content_type ||= Mime::XML
  render_for_text(xml.respond_to?(:to_xml) ? xml.to_xml : xml, options[:status])
end

This code calls to_xml on our collection and converts it to an XML document.

Finally, the any is another method defined on the Responder object. You can pass to this a collection of mime types and for these mime types it will perform the block. In this example we’ve used:

  format.any(:atom, :xml) { @blogs = Blog.all }

but you can also do:

  format.any { @blogs = Blog.all }

which will define the response for all currently undefined mime types.

any goes a bit like this:

def any(*args, &block)
  if args.any?
    args.each { |type| send(type, &block) }
  else
    custom(@mime_type_priority.first, &block)
  end
end 

We’ve passed args to ours, so it’ll just call the methods atom and xml on the Responder object and then render the appropriate views for these.

If we don’t pass arguments to any, any MIME type that we don’t have a respond_to line for will call the any block instead. Take for example, if we had this:

def index
  @blogs = Blog.all(:limit => 10)
  respond_to do |format|
    format.html 
    format.json { render :json => @blogs }
    format.any { @blogs = Blog.all }
  end
end

And we requested an XML page, http://localhost:3000/blogs.xml, this will trigger the code in the any block to be ran.

So that wraps up this guide on respond_to. Thanks for reading!

When you didn’t think it possible…

Thursday, April 23rd, 2009

TechFail stoops to new lows.. I honestly though the local “news” (here’s looking at you news.com.au) reporting was bad. My god. Do your damn research. You quoted SoftMind. I am using short sentences. This is how I indicate I am in disbelief. It stuns me. Incredibly so. Let me just reiterate that:

YOU QUOTED SOFTMIND QUITE POSSIBLY THE GREATEST (AND BY THAT I MEAN WORST) NOOB THAT EVER LIVED

Ok. I’ll calm down now. Honestly guys, SoftMind even talks to himself using an alternative twitter account and prattles on about the most mundane shit ever. He surely cannot be your only source of news. He should never be anyone’s source of anything. Just ignore him and we can all pray together that he goes away (read: gets hit by a rapidly moving vehicle).

Please tell me you got the work experience kid to do this and he meant it as a joke, but you guys took it seriously.

Wow. Stunned.

Pretty-ifying URLs

Saturday, April 18th, 2009

In today’s modern world we are not limited (cough, FORTRAN) to storing information in just a couple of bits. Some say that having pretty URLs is good SEO. I say it’s just common sense. I want the URL to tell a story, not give me some number that is only important to the system it’s coming from.

This is where to_param comes in.

By default this is defined like this:

def to_param
  id
end

Pretty high-tech stuff there. I won’t explain it to you. Give it a moment to sink it. et-cetera. This method is called on an object when it is passed to a url helper such as link_to or redirect_to. It is important to note that you should NEVER call @object.id as that will give the id of the object, not the to_param version of that name.

You can override this in your model in order to give you pretty urls! The way you do this is:

def to_param
  "#{id}-#{name.parameterize}"
end

Where id is the id of the model and name is the text you want into the URL. We call parameterize on this text in order to make it URL friendly.

The reason why we put in the id in the URL is two-fold. Firstly, (/forums/1-best-forum-ever) is so that when it gets passed to our controller as params[:id] or similar, we’re able to pass it to a finder:

Forum.find(params[:id])
# will be passed in as...
Forum.find("1-best-forum-ever")

Now you may go “Hey… wait a second! I don’t have an ID in my database that says ’1-best-forum-ever’!” and you’d be right, you don’t. But you do have an ID of 1 for a record somewhere. Rails will call to_i on this value and convert it simply down to 1 and you’ll be able to treat the pretty URL as if it were a real ID.

Secondly, if two objects in your database have the same name they won’t clash for the parameterized versions, since the id will always be unique.

But I don’t want my ID in my URL

Hey, that’s cool too! Just a little tougher… Before you save your records you’ll want to write out the permalink as a new field, so define a field called permalink in your table if you want to take this route. Then you do a before_save and define to_param like this:

before_create :set_permalink

def set_permalink
  self.permalink = name.parameterize
end

def to_param
  permalink
end

Now when you call the finder you’re going to need to find_by_permalink! instead of just find:

Forum.find_by_permalink!(params[:id])

We have to use the bang version of find_by_permalink because this will raise an ActiveRecord::RecordNotFound exception if the record is not found, just like find. (Thanks to Yarsolav Markin for mentioning this)

So there you have it. to_param overriding for pretty URLs.

Making an appearance

Monday, April 6th, 2009

We interrupt the slew of technical bloggings to bring you this announcement:

For the Radar fans out there living in Adelaide you’ll be glad to know that I’m coming back down (up?) there on Wednesday! That’s right, for a limited time only I shall return and hang out with as many of you as humanly possible. Just because I love you all.

On Wednesday I’ll be waking up at some ungodly hour, phoning a cab and then packing my crap. Then I’ll take the cab (and my crap) to the airport and I will then catch a flight out to Adelaide and arrive there at around 8:50am. From there on it’s fun & festivities with family and friends! Cannot wait to see you all again! Be there!

Testing Gems with multiruby

Thursday, April 2nd, 2009

A couple of days ago I was requested politely and with acknowledgement of my humanity, by Dr Nic to write a post about testing gems on 1.9 and because he’s my boss I’m obliged to do the things he tells me to do. He told me to read his post about Future-proofing Your Ruby Code and in particular the instructions about installing multiruby.

How to install multiruby

You can install multiruby, different versions of ruby, rubygems and useful gems using these commands:

sudo gem install ZenTest
multiruby_setup mri:svn:tag:v1_8_6_114
multiruby_setup mri:svn:tag:v1_8_7_153
multiruby_setup mri:svn:tag:v1_9_1_0

multiruby_setup update:rubygems
multiruby -S gem install --no-ri --no-rdoc --development test-unit

This will:

  1. Install ZenTest (or the latest version)
  2. Install Ruby 1.8.6, build 114
  3. Install Ruby 1.8.7, build 153
  4. Install Ruby 1.9.1, build 0
  5. Setup Rubygems for all three versions and;
  6. install test-unit for all three versions!

You may also want to multiruby -S gem install –no-ri –no-rdoc –development rails mocha rspec.

multiruby is awesome because it sticks this all in your home directory in a folder called .multiruby, so don’t worry about it clashing with your existing version(s) of ruby. Different versions go in different directories inside .multiruby/versions.

numero!

Today we’re going to be testing a gem that I recently made use of on this site. For those of you that visited the site yesterday you would’ve seen something like this:

  112.117.116.115.32.34.104.101.108.108.111.32.119.111.114.108.100.34

This is numero! (exclamation mark compulsory). I don’t know where the idea came from. According to the MSN logs I keep, at 8:03am Friday morning I went and made breakfast and then at 8:13am I had the idea. But it’s awesome. The “language” uses the ASCII representation of all your favourite characters and separates them using a dot. This is numero! script. You run it using the numero! gem. This is how you get it:

sudo gem install radar-numero --source http://gems.github.com

Test it out by running it on your favourite ruby script!

numero favourite.rb

This will generate a file called favourite.numero and then evaluate the newly generated numero! code. Now you can throw away your ugly ruby file and bathe in the glory that is numero by then running:

numero favourite.numero

So it appears we’ve gone off on a tangent here! Fear not! For I want you to test my gem on your new multiruby setup:

git clone git://github.com/radar/numero
cd numero
git checkout -b broken origin/broken

Now this sensible naming schema will probably dissolve the mystery surrounding what’s going to happen next. You’re going to be testing my gem, and it’s going to break.

Testing numero!

When I wrote this particular branch of numero!, I used “a”[0] to get the ASCII representation (97) of the letter a. Generally, I would’ve used use a variable rather than “a”, but this is just an example. The problem is that this doesn’t work the same way in Ruby 1.8.* and Ruby 1.9. If we run multiruby -S test/test_numero.rb we see that it passes on both versions of Ruby 1.8, but fails on Ruby 1.9!

Ruby 1.8.*

Loaded suite test/test_numero
Started
.
Finished in 0.001186 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Ruby 1.9 Loaded suite test/test_numero Started F

Finished in 0.025228 seconds.

1) Failure: testnumerocompiles(TestNumero) [test/test_numero.rb:9]: <”[\"p.u.t.s. .\\".h.e.l.l.o. .w.o.r.l.d.\\"\"]“> expected but was <”112.117.116.115.32.34.104.101.108.108.111.32.119.111.114.108.100.34″>.

1 tests, 1 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications

Not good! This is something I wouldn't have found if I didn't test the functionality on multiple Rubies. Using multiruby is essential to ensure that your code works for everybody. We don't want to end up like Internet Explorer.

The solution, of course, is to use str.unpack("C").first instead. You have two options: 1) change the code yourself in lib/numero.rb to use the afore-mentioned example or 2) git checkout master. Now we run the tests again and now Ruby 1.9 passes:

Ruby 1.8.7

Finished in 0.001109 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Ruby 1.9.1

Finished in 0.001353 seconds.

1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications

Lovely!