Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass an argument to a has_many association scope in Rails 4?

Rails 4 lets you scope a has_many relationship like so:

class Customer < ActiveRecord::Base   has_many :orders, -> { where processed: true } end 

So anytime you do customer.orders you only get processed orders.

But what if I need to make the where condition dynamic? How can I pass an argument to the scope lambda?

For instance, I only want orders to show up for the account the customer is currently logged into in a multi-tenant environment.

Here's what I've got:

class Customer < ActiveRecord::Base   has_many :orders, (account) { where(:account_id => account.id) } end 

But how, in my controller or view, do I pass the right account? With the code above in place when I do:

customers.orders 

I get all orders for account with an id of 1, seemingly arbitrarily.

like image 477
Rob Sobers Avatar asked Apr 17 '14 00:04

Rob Sobers


2 Answers

The way is to define additional extending selector to has_many scope:

class Customer < ActiveRecord::Base    has_many :orders do       def by_account(account)          # use `self` here to access to current `Customer` record          where(:account_id => account.id)       end    end end  customers.orders.by_account(account) 

The approach is described in Association Extension head in Rails Association page.

To access the Customer record in the nested method you just can access self object, it should have the value of current Customer record.

Sinse of rails (about 5.1) you are able to merge models scope with the othe model has_many scope of the same type, for example, you are able to write the same code as follows in the two models:

class Customer < ApplicationRecord    has_many :orders end  class Order < ApplicationRecord    scope :by_account, ->(account) { where(account_id: account.id) } end  customers.orders.by_account(account) 
like image 75
Малъ Скрылевъ Avatar answered Sep 19 '22 20:09

Малъ Скрылевъ


You pass in an instance of the class you have defined. In your case, you would pass in a customer and then get the account.

From the API http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Accessing the owner object

Sometimes it is useful to have access to the owner object when building the query. The owner is passed as a parameter to the block. For example, the following association would find all events that occur on the user's birthday:

class User < ActiveRecord::Base   has_many :birthday_events, ->(user) { where starts_on: user.birthday },      class_name: 'Event' end 

In your example it would be:

class Customer < ActiveRecord::Base   has_many :orders, ->(customer) { where(account_id: customer.account.id) } end 
like image 39
Brad Avatar answered Sep 21 '22 20:09

Brad