Can someone tell me what is the equivalent way to do the following line in Rails 4?
has_many :friends, :through => :friendships, :conditions => "status = 'accepted'", :order => :first_name
I tried the following:
has_many :friends, -> { where status: 'accepted' }, :through => :friendships , :order => :first_name
But I get the following error:
Invalid mix of scope block and deprecated finder options on ActiveRecord association: User.has_many :friends
Needs to be the second arg:
class Customer < ActiveRecord::Base
has_many :orders, -> { where processed: true }
end
http://edgeguides.rubyonrails.org/association_basics.html#scopes-for-has-many
RESPONSE TO UPDATE:
Put the order inside the block:
has_many :friends, -> { where(friendship: {status: 'accepted'}).order('first_name DESC') }, :through => :friendships
While other answers on here are technically correct, they violate encapsulation. The User model should not know that the Friendship model has a column called status
, and that it can have a specific value like accepted
.
If you decide to make a change, to take advantage of Enums in Rails 4, for example, you would have to change both User and Friendship models. This could lead to bugs that maintaining encapsulation avoids.
I would expose a scope in the Friendship model:
scope :accepted, -> { where(status: :accepted) }
I would then use this scope in the User model, hiding any implementation details from User.
has_many :friendships, -> { Friendship.accepted }
has_many :friends, through: :friendships
# Or...
has_many :friends, -> { Friendship.accepted }, through: :friendships
You can go further and rename the scope to accepted_friendships
to be clearer.
has_many :accepted_friendships, -> { Friendship.accepted }
has_many :friends, through: :accepted_friendships
Now you have successfully encapsulated implementation details in their respective models. Should anything change you only have one place to change it, reducing maintenance and increasing robustness.
A Rails 3.2 version of Mohamad's answer would be the following:
class Friend < ActiveRecord::Base
has_many :friendships, :order => :first_name
has_many :friends, :through => :friendships,
:conditions => proc { Friendship.accepted.where_ast }
has_many :pending_friends, :through => :friendships,
class_name => Friend,
:conditions => proc { Friendship.pending.where_ast }
end
class Friendship < ActiveRecord::Base
scope :status, ->(status) { where(:status => status) }
scope :accepted, -> { status('accepted') }
scope :pending, -> { where(arel_table[:status].not_eq('accepted')) }
end
NOTES:
where_ast
is important as it returns the AREL nodes that are required for the condition to work
- within the proc passed to
:conditions
, self
is not always a model instance (e.g. when the association is merged with another query)
- Using raw SQL within your scopes and associations will likely cause issues at some point to do with namespacing of table names... use AREL.
In order to work on Rails 4.1 (my case), i had to put:
has_many :friends, -> { where(friendships: { status: 'accepted' }) }, through: :friendships
Note the S on friendships. It refers directly to the database name.
has_many :friends, -> { where(status: 'accepted').order('frist_name')}, through: :friendships
or
has_many :friends, -> { where(status: 'accepted').order(:frist_name)}, through: :friendships