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.
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.
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!
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:
“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.
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.
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.
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.
require 'rails/generators'
Rails::Generators.invoke("model", ["model_name", "arg1", "arg2"])
@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.
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.