As a part of the migration from Rails 3.2 to Rails 4, all named scopes need a proc block. Read more here: http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#active-record
I had missed updating a scope in one of my models, which ended up biting me in production after my migration. So I wanted to figure out how to test this issue, and I found some strange behavior.
In some cases the scopes appear to work fine without the proc, but not in other cases.
# models/offer.rb
class Offer < ActiveRecord::Base
scope :roster, where(:on_roster => true)
scope :commit, where("status_id > 5")
end
If I use each scope option on independent calls in rails console, the queries are built properly and the results come back as one would have expected in Rails 3.2:
$ rails c
2.0.0-p247 :001 > Offer.roster.all.size
Offer Load (1.6ms) SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't'
=> 1
2.0.0-p247 :002 > Offer.commit.all.size
Offer Load (1.6ms) SELECT "offers".* FROM "offers" WHERE (status_id > 5)
=> 3
However if I chain two scope calls together in the rails console, only the constraints from the last scope in the chain is included in each query:
2.0.0-p247 :003 > Offer.roster.commit.all.size
Offer Load (1.4ms) SELECT "offers".* FROM "offers" WHERE (status_id > 5)
=> 3
2.0.0-p247 :004 > Offer.commit.roster.all.size
Offer Load (0.7ms) SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't'
=> 1
Now if I edit my model to add a proc to the second named scope, like so:
class Offer < ActiveRecord::Base
scope :roster, where(:on_roster => true)
scope :commit, -> { where("status_id > 5") }
end
If the named scope with the proc defined is at the end of the chain, it will build the query with both sets of constraints. However, if the named scope without the proc defined is at the end of the chain, the resulting query is built without the constraints of the scope with the proc defined.
$ rails c
2.0.0-p247 :003 > Offer.roster.commit.all.size
Offer Load (1.4ms) SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't' AND (status_id > 5)
=> 0
2.0.0-p247 :004 > Offer.commit.roster.all.size
Offer Load (0.7ms) SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't'
=> 1
So the first result is correct, and loads both scopes, but the second is incorrect and only loads the last scope. Then if you change both scopes to use procs, like so:
# models/offer.rb
class Offer < ActiveRecord::Base
scope :roster, -> { where(:on_roster => true) }
scope :commit, -> { where("status_id > 5") }
end
You finally get the expected behavior:
$ rails c
2.0.0-p247 :002 > Offer.roster.commit.all.size
Offer Load (1.3ms) SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't' AND (status_id > 5)
=> 0
2.0.0-p247 :001 > Offer.commit.roster.all.size
Offer Load (1.7ms) SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't' AND (status_id > 5)
=> 0
One note on this, calling reload!
in the rails console will not update the behavior of the scopes after you've updated and saved your model. You have to end your rails console session and begin a new one to get the proc vs. non-proc to pick up correctly.
The question I have is how to test to ensure that all of my scopes will behave as expected? Chaining scopes together each time I want to test whether they have a proc or lambda block seems very messy. However the simple tests I set up on the scopes, told me all my scopes were passing, and giving false positive results.
Is there an easy way to test via Rspec with Rails4 whether the named scope resides within a proc or lambda block?