Ruby Tips

Aug 1 2011

Prepending a field to an order clause

Ever had an order you want to reorder? ARel makes this really simple. Imagine that we’re currently ordering people by their first name:

people = Person.order("first_name ASC")

But now we’ve advanced to a point where we want to re-order these people by their last name first, then their first name and we’re not allowed to modify the initial scope call to order. Let’s assume that this initial scope is kept in the people variable still:

people.reorder(people.order_values.unshift("last_name ASC").join(','))

This will now re order the people by their last name first, then still maintain the first_name ordering defined by the initial scope.

1 note

Dec 10 2010

respond_to - undefined method `call’ for nil:NilClass

Today I helped somebody solve the issue for this gist.

Because of how respond_to works, if you have no response set up for the format that the request is in, you’ll get this error. All this guy needed to do was to change this line in his gist:

  redirect_to invoice_path
 

To this:

  format.html { redirect_to invoice_path }
 

The format.html call sets up a response for the html formatted request for this action. Without *any* response set up then the list of possible responses will be empty, thereby resulting in the above error.

Dec 5 2010

uninitialized constant Rack::Session::Abstract::SessionHash

You’re using edge Rails, right? You already did this in your Gemfile:

  gem "rails", :git => "git://github.com/rails/rails.git"

You’ve run bundle install and then perhaps rails console or rails server

uninitialized constant Rack::Session::Abstract::SessionHash 

This is because edge Rails also requires edge Rack. Put this line after Rails in your Gemfile and you’ll be on the edge:

gem "rack", :git => "git://github.com/rack/rack.git"

Done!

Nov 25 2010

Testing Rails Requests

This fine evening my friend Bo Jeanes messaged me and asked:

got any thoughts on how to test constrained routes in rails 3?

Alas, I had no thoughts on that particular subject.

Bo presented me with code similar to this in his config/routes.rb file that he wanted to write tests for:

  constraints(NonApiSubdomain) do
    root :to => redirect("http://example.com")
    
    match '/*path', :to => redirect { |params| "http://example.com/#{params[:path]}" }
  end

With the NonApiSubdomain constant being defined like this:

  module NonApiSubdomain
    def self.matches?(request)
      request.subdomain.present? &&
      request.subdomain != 'api' &&
      !Rails.env.development?
    end
  end

Oh dear! How do you test something like that?! Well, we combined our powers and came across the ActionDispatch::TestRequest class within Rails, which has an env method that basically returns the result of Rack::MockRequest.env_for('/') which is quite beautiful. This is exactly what it says on the box: a MockRequest object. Perfect for what we need.

“But how does this help us?” I hear you cry. As you’d know because you’ve been paying super amounts of attention recently, a Rails 3 application is now just a Rack application. This means it has a call method, which returns an array of three things:

  • A HTTP status code (200 is my favourite, 500 is my anti-thesis)
  • A Hash representing the headers returned by the response
  • The response itself

“Oooh! Tell me more!” you say, and so I will. You can do this from inside the console of your favourite Rails 3 application:

  Rails.application.call(Rack::MockRequest.env_for('/'))

Then hey presto, a response array! “But how does this help us test it?” you ask, almost pleadingly. It’s elementary, my dear Watsons. Bo suggested that we create a spec/requests directory and put our specs in there, which isn’t a terrible idea. Our spec to test this particular route goes in this directory and looks like this:

require 'spec_helper'

describe "non-api" do
  
  let(:env) { Rack::MockRequest.env_for('/') }
  
  def request!(other_env)
    @status, @headers, @response = Rails.application.call(env.merge(other_env))
  end
  
  it "redirects to address sans www" do
    request!("HTTP_HOST" => "www.example.com")
    @status.should eql(301)
    @headers["Location"].should eql("http://example.com")
  end

Pretty neat, pretty simple and works for this purpose.

Nov 23 2010

Strange Hash Syntax

Today I learned that this:

h = {}
h[1] = 2,
h[2] = 3

is perfectly valid Ruby. This creates an empty hash, then two keys in it. Key #1 gets the value of [2,3], whilst key #2 gets the value of 3.

Of course this is valid Ruby syntax, because it’s the same as doing: 

h = {}
h[1] = 2, h[2] = 3

This is because in Ruby, you can form an array without even using the square brackets if it’s the right value of an assignment. We can add them here to show exactly what this is doing:

h = {}
h[1] = [2, h[2] = 3]

The first element of the array is the number 2. Well, that’s easy enough. Then the second element is going to be the outcome of whatever h[2] = 3 is. That, of course, is going to be 3.

The benefit of doing it this way is unknown. If only I could find a use for something this strange.

Nov 17 2010

Rails 3: Validators for an object

To work out what kind of validations are going to happen on an object, you can call the _validators method on any ActiveRecord::Base descendant:

  >> t = Ticket.new
  >> t._validators
  => {:title => 
      [#<ActiveModel::Validations::PresenceValidator:[id] @attributes=[:title], @options={}>]

As you can see here, it returns a hash where the keys are attributes and the values are arrays of objects in the ActiveModel::Validations class in Rails.

Nov 3 2010

LEFT OUTER JOIN with ARel

To eager load records in Rails 3 use the includes method like so:

  Event.includes(:user)

This will load all the users who have events by using these queries:

  Event Load (2.3ms)  SELECT "events".* FROM "events" ORDER BY events.id DESC
  User Load (0.8ms)  SELECT "users".* FROM "users" WHERE ("users"."id" IN (1))

To then perform a LEFT OUTER JOIN on the users table and load everything in the one query, you must perform a query that accesses the users table, like this:

  Event.includes(:user).where(:users => { :username => "ryan" })

This will execute this query:

   SELECT "events"."id" AS t0_r0, [...] "users"."single_access_token" AS t1_r25 
   FROM "events" LEFT OUTER JOIN "users" ON "users"."id" = "events"."user_id" 
   WHERE ("users"."username" = 'ryan') 

So remember, kids - if you want to do an INNER JOIN, use joins; if you want to do a LEFT OUTER JOIN, use includes.

If you’re looking to do this in Rails 2.0 the code is this:

Event.all(:include => :user, :conditions => { :user => { :username => 'ryan' } } )

The query executed by this is pretty much identical to the Rails 3 version, but the syntax is just that tiny bit longer.

For more shiny stuff, take at a look at this blog post about the features in ARel 2.0.

Hat tips to Jason Weathered (@jasoncodes) for going through this with me and for Lucas Willet (@ltw_) for proof-reading this post.

Nov 2 2010

Running a Rails Generator

  require 'rails/generators'
  Rails::Generators.invoke("model", ["model_name", "arg1", "arg2"])

Nov 2 2010

prototype_legacy_helper

@jasoncodes and I patched prototype_legacy_helper to work with the latest version of Rails 3.

If you’re using form_remote_tag or form_remote_for or any method like that in a Rails 2 project which you’re looking to upgrade to Rails 3, this plugin will continue to support those methods.

Nov 2 2010

Don’t use the nested-layouts with Rails 3, it doesn’t work.

Instead, reference this section of the official “Layouts and Rendering” Ruby on Rails guide.