Rajesh Omanakuttan
Rajesh Omanakuttan

Reputation: 6918

scopes in Active records using lambda - returns No method error in Rails Console

In my User model class, I have defined 2 scopes as given below.

  scope :condition, lambda { where('updated_at >= ?', 3.day.ago) }

  scope :tardy, lambda {
    joins(:timesheets).group("users.id") & Timesheet.condition
  }

Then I tried running reload!, and thereafter u =User.find 14 and I got result below.

  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 14 LIMIT 1
 => #<User id: 14, login: "janu", password: "janu", password_confirmation: nil, email: "[email protected]", created_at: "2013-01-03 09:47:36", updated_at: "2013-01-03 09:47:36"> 

Then I run, u.tardy.to_sql,but, it returns the following error.

NoMethodError: undefined method `tardy' for #<User:0x00000003cb5ea0>
    from /home/local/rajesh.co/.rvm/gems/ruby-1.9.3-p327/gems/activemodel-3.2.9/lib/active_model/attribute_methods.rb:407:in `method_missing'
    from /home/local/rajesh.co/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-3.2.9/lib/active_record/attribute_methods.rb:149:in `method_missing'
    from (irb):64
    from /home/local/rajesh.co/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands/console.rb:47:in `start'
    from /home/local/rajesh.co/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands/console.rb:8:in `start'
    from /home/local/rajesh.co/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands.rb:41:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

See, When I join both the scopes together as given below, then also I am getting the same error.

  scope :tardy, lambda {
    joins(:timesheets).
    where("timesheets.updated_at >= ?", 3.days.ago).
    group("users.id")
  }

Can you please help me to resolve the same. Thank you.

Upvotes: 2

Views: 1482

Answers (2)

Rajesh Omanakuttan
Rajesh Omanakuttan

Reputation: 6918

The error was on the model's scope definition and meagar's answer is also right for one of the errors.

  scope :tardy, lambda {
    joins(:timesheets).group("users.id") && Timesheet.condition
  }

In the above, I have specified Timesheet.condition where the condition scope has to be declared in the Timesheet model instead of User model since I need to group the users with respect to the time of timesheet updates. So the below given scope has to be there in the Timesheet model like below.

scope :condition, lambda { where('updated_at >= ?', 3.day.ago) }

Run User.tardy, we will get the Users who all are updated their timesheets meeting the condition given.

2nd & Easy way to achieve this is to define a single scope in User model itself:

scope :tardy, lambda {
    joins(:timesheets).
    where("timesheets.updated_at >= ?", 3.days.ago).
    group("users.id")
  }

But, this cross-model scope violates good object-oriented design principles: it contains the logic for determining whether or not a Timesheet is updated, which is code that properly belongs in the Timesheet class. So 1st method is preferable.

Thank you all.

Upvotes: 1

user229044
user229044

Reputation: 239382

You can't invoke a scope on a specific record. When you invoke .find, you're getting one specific record back, not a relation/association to which additional scopes can be chained.

If you want to find any tardy user with id 14, you'll have to reverse the order of method calls:

User.tardy.find(14)

You still won't be able to run .to_sql, as you'll again have a single record. If you really want the SQL back for a specific find-by-id, you could use

User.tardy.where(:id => 14).to_sql

Upvotes: 4

Related Questions