Relation on scope on ActiveRecord

activerecord, ruby-on-rails, ruby-on-rails-3.2

Solution

First, what you really want is define a scope on your choices.

class UserOfferChoice < ActiveRecord::Base
  belongs_to :user
  belongs_to :offer

  scope :seen, where(seen: true)
  scope :accepted, where(accepted: true)
end

Which allows you to do this

Offer.find(11).user_offer_choices.seen

and to get the criteria:

Offer.find(1).user_offer_choices.seen.map{|choice| choice.user}.map{|user| user.criterion}

Now, this could be cleaned up with a has many through in the Offer class.

class Offer < ActiveRecord::Base
  has_many :user_offer_choices
  has_many :users, :through => :user_offer_choices
end

but that gets us to the user, but skipping the scope.

Offer.find(1).users

Now, there's a trick you can do with Rails 3 scopes which you could not do with Rails 2.3.5 named_scopes. The named_scopes took a hash as arguments but returned a relation. The Rails 3 scopes take a relation, as from query methods like where. So you can define a scope in users, using the scope defined in your choices class!

class User < ActiveRecord::Base
  has_one :criterion
  has_many :user_offer_choices
  has_many :offers, :through => :user_offer_choices

  scope :seen, UserOfferChoice.seen
  scope :accepted, UserOfferChoice.accepted
end

That allows us to do this:

Offer.find(1).users.seen

The map now looks like this:

Offer.find(1).users.seen.map{|user| user.criterion}

BTW, the plural of criterion is criteria. Hearing criterions in my head when I read it, hurts. You can do this to get Rails to know the plural:

config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
  inflect.plural /^criterion$/i, 'criteria'
end

Problem

Here are my ActiveRecord models, with Rails 3.2 : ``` class User < ActiveRecord::Base has_one :criterion has_many :user_offer_choices end class Offer < ActiveRecord::Base has_many :user_offer_choices def seen user_offer_choices.where(seen: true) end def accepted user_offer_choices.where(accepted: true) end end class Criterion < ActiveRecord::Base belongs_to :user end class UserOfferChoice < ActiveRecord::Base belongs_to :user belongs_to :offer end ``` I want to get all the criterions of the users who have seen an offer. Something like : ``` Offer.find(11).seen.users.criterions ``` but I do not know how to to it with ActiveRecord I know I can do something like : ``` Criterion.joins(user: { user_offer_choices: :offer }).where(user: { user_offer_choices: {accepted: true, offer_id: 11} } ) ``` But I want to be able to use my scopes on offers (seen & accepted). So how can I do it ? Edit : I have found what I was looking for, the merge method of Arel : http://benhoskin.gs/2012/07/04/arel-merge-a-hidden-gem

Original source