Ruby Flare

Your awesome Tagline

Posts tagged rails

0 notes

Order matters

I was tripped up by this today. The behaviour of Rails when it comes to chaining orders seemed counterintuitive to me. Here’s an example…

class Author < ActiveRecord::Base
  has_many :posts, order: 'created_at ASC'
end

class Post < ActiveRecord::Base
  belongs_to :author
end

author = Author.create
post_1 = author.posts.create
post_2 = author.posts.create

author.posts   # => [post_1, post_2]

Now that’s all fine, but what if I want to specify an explicit order (and I don’t care what the original order was). I tried the following thinking it would override the existing order but it doesn’t.

author.posts.order('created_at DESC')   # => [post_1, post_2]

I expected it to reverse the order. What it actually did was to add the order onto the query so that the actual SQL statement looks like this:

SELECT "posts".* FROM "posts" WHERE "posts"."author_id" = 2 ORDER BY created_at ASC, created_at DESC

One solution is to use the reorder method:

author.posts.reorder('created_at DESC')   # => [post_2, post_1]

This replaces out any existing ordering.

In my case, I’m just going to use the reverse_order method:

author.posts.reverse_order   # => [post_2, post_1]

(Source: api.rubyonrails.org)

Filed under rails activerelation

0 notes

scope vs class method

In my last post I talked about the need to use lambdas in all scopes that call an existing scope that uses a lambda. An alternative to this is to use class methods instead of scope. So instead of:

scope :active, lambda { started.unfinished }

Use this:

def self.active
  started.unfinished
end

Now, the active class method doesn’t need to know that started or unfinished scopes use a lambda.

(Source: rubyflare)

Filed under rails scopes

1 note

Beware of chaining lambda scopes

Here’s a gotcha to watch out for! Let’s say you have these scopes in your Rails 3 project:

scope :started, lambda { where("starting_at <= ?", Time.now) }
scope :unfinished, lambda { where("ending_at > ?", Time.now) }

When you call either of these scopes, because you have used lambda, the value of the lambda is re-evaluated each time so that Time.now is dynamic. That’s what you want and what you expect.

Now, when you chain these scopes together:

Event.started.unfinished

The value of Time.now will be re-evaluated each time this is run which is what you want and what you expect.

You end up using this a bit, so you decide to wrap it up into its own scope:

scope :active, started.unfinished

Unfortunately, when you use this new scope, the value of Time.now has now become static which is not what you wanted. You need to use a lambda for the same reason you did with the original scopes.

scope :active, lambda { started.unfinished }

When you think about it, it makes sense. The value of the scope is evaluated at the time the scope is defined. So with the first definition of active, the lambdas in the started and unfinished scopes were evaluated. You need to defer that evaluation, hence the need to wrap those calls inside a lambda as well.

(Source: rails.lighthouseapp.com)

Filed under rails scopes lambda

11 notes

bypass_rescue in RSpec

If you are using rescue_from to handle an exception then the following spec won’t work:

it "should raise an access denied error" do
  expect { get(:new) }.to raise_error(CanCan::AccessDenied)
end

Instead you need to use rspec-rails’s bypass_rescue:

it "should raise an access denied error" do
  bypass_rescue
  expect { get(:new) }.to raise_error(CanCan::AccessDenied)
end

This works in rspec-rails 1.3 but from what I understand it wasn’t originally available in rspec-rails 2. It is about to make a comeback though.

Filed under rspec rails

2 notes

Presence

I just came across this object method in Rails (when did this go in?):

Instead of writing something like:

state = params[:state] if params[:state].present?

you can now do:

state = params[:state].presence

Object.presence is the equivalent of object.present? ? object : nil

See the docs

Filed under rails