0

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?

Paul Pettengill
  • 4,843
  • 1
  • 29
  • 33
  • The answer is easy - ALWAYS use a proc object in your scopes. – Almaron Sep 20 '13 at 11:07
  • ^ Your answer misses the point entirely. The question is how do you ensure via testing that all of your scopes have procs. I'm looking for a solution that will allow one to set up the proper TDD to go from red to green. – Paul Pettengill Sep 20 '13 at 11:13
  • Then you should've pasted in your tests that are passing when they shouldn't instead of listing a whole story that is a minor secret. – Almaron Sep 20 '13 at 11:17
  • ^ Perhaps pasted test results may have been helpful to some, but I would imagine most people would come to the same conclusion that I did. The rails console statements and responses demonstrated more clearly and succinctly the underlying issue of how the queries were built that led to the false positives than showing the false positives themselves which would have been bereft of any help on why those tests were returning with false positives, and why other potential tests might also be erroneous. – Paul Pettengill Sep 20 '13 at 11:38

1 Answers1

0

Scopes are only syntactic sugar for defining class methods, so by looking at the code it's quite impossible to know whether your scope was a proc/lambda or not.

Only solution I could think of is using RR and proxying the scope method. This way you could raise an exception if body does not respond to call. In your tests you than expect that no exception is raised. But I doubt this is going to work because once you set up the proxy the class has already been loaded and, thus, the scope method has been called.

I guess instead of enforcing Proc usage through test, overwriting the scope method to not allow non-procs/-lambdas at all is a better idea.

For reference: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/scoping/named.rb

lawitschka
  • 745
  • 3
  • 9
  • I agree that making it required by monkey patching the named scope definition would ensure that all named scopes have procs/lambda defined. What I was hoping to find was a simple way to test it, as its still early for people migrating to 4.0, I figured such a test would be helpful to others who want to ensure that their upgrade is correct. – Paul Pettengill Sep 22 '13 at 03:03