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
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
Related
What is 'as' in ruby on rails model? and how does it work?
e.g.
has_many :something, as: :reasonable
Is it polymorphic?
Yes, this is a polymorphic association which allows a model to belong to multiple models. There should be
class Something < ApplicationRecord
belongs_to :reasonable, polymorphic: true
end
And then any model can have many of these as reasonable without adding another column to Something.
class Thing < ApplicationRecord
has_many :somethings, as: :reasonable
end
class Stuff < ApplicationRecord
has_many :somethings, as: :reasonable
end
Something stores both the class and ID of what its associated with allowing it to be polymorphic.
I have the following models set up:
class Location < ActiveRecord::Base
has_many :location_parking_locations
has_many :parking_locations, through: :location_parking_locations
end
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
end
class ParkingLocation < ActiveRecord::Base
has_many :location_parking_locations
has_many :locations, through: :location_parking_locations
end
The LocationParkingLocation has an integer field called upvotes. I would like to create a 'by_votes' scope that I can add to a query to order the results by this upvotes field. Where and how do I define this scope, so that I can call it like this:
location.parking_locations.by_votes
I can't define it like this, because then it's not a valid method on parking_locations:
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
scope :by_votes, -> { order("upvotes DESC") }
end
Should it be defined in the 'ParkingLocation' class? If so, how do I tell it that I want to order by a field on the location_parking_locations table?
I think you might be able to use merge here.
You can leave your scope in the LocationParkingLocation class, and the result would look like:
location.parking_locations.merge(LocationParkingLocation.by_votes)
I just read a little about it in this blog post.
I am new to ROR and I am trying to understand scopes. In my current implementation I am getting all the Processors and displaying it in the view.
class ProcessorsController
def index
#processors = Processor.all
end
end
I want to modify this so I can get only the processors where the user is admin. This is how my relations are set up.
class Processor
belongs_to :feed
#SCOPES (what I have done so far)
scope :feed, joins(:feed)
scope :groups, joins(:feed => :groups).join(:user).where(:admin => true)
end
class Feed < ActiveRecord::Base
has_and_belongs_to_many :groups
end
class Group < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :groups
scope :admin, where(:admin => true)
end
I was able to do this in my pry
pry(main)> Processor.find(63).feed.groups.first.user.admin?
PS: could someone provide some good resources where I could learn how to use scopes if the relationships are complex.
scope :with_admin, -> { joins(:feed => { :groups => :user }).where('users.admin' => true) }
As for the resources, have you gone through the official documentation on ActiveRecord joins?
you do not need scopes... you can get only the processors where the user is admin using relations and conditions:
class Feed < ActiveRecord::Base
...
has_one :user, through: :groups
end
class Processor
...
has_one :admin, through: :feed, source: :user, conditions: ['users.admin = 1']
end
This seems to be a fairly common problem over here, yet there is no definitive solution. To restate once again, say I have a model:
def Model < ActiveRecord::Base
has_many :somethings, ...
has_many :otherthings, ...
end
The question is then how to add a third association :combined that combines the two? I know this can be done with :finder_sql and similar result can be achieved with a scope, but neither of these gives me an actual association. The whole point of this is to be able to use it for another association with :through and things like Model.first.combined.some_scope.count
EDIT: the relevant portions of the actual code
class Donation < ActiveRecord::Base
# either Project or Nonprofit
belongs_to :donatable, :polymorphic => true
belongs_to :account
end
class Project < ActiveRecord::Base
belongs_to :nonprofit
end
class Nonprofit < ActiveRecord::Base
has_many :projects
# donations can be either direct or through a project
# the next two associations work fine on their own
# has_many :donations, :as => :donatable, :through => :projects
# has_many :donations, :as => :donatable
has_many :donations, .... # how do I get both here,
has_many :supporters, :through => :donations # for this to work?
end
Thanks.
If Something and Otherthing are sufficiently similar, use STI:
def Model < ActiveRecord::Base
has_many :somethings
has_many :otherthings
has_many :genericthings
end
def Genericthing < Activerecord::Base
# put a string column named "type" in the table
belongs_to :model
end
def Something < Genericthing
end
def Otherthing < Genericthing
end
Let's say I have:
class ForumTopic < ActiveRecord::Base
has_many :forum_posts
named_scope :number_of_posts, ??????
end
class ForumPost < ActiveRecord::Base
belongs_to :forum_topic
end
What should I put in ????? to allow searchlogic query like:
ForumTopic.descend_by_number_of_posts
Any help would be very greatly appreciated!
Do you want to order by number of posts, right?
I think it's easier if you use :counter_cache because if you do, you can order just like this:
class ForumTopic < ActiveRecord::Base
has_many :forum_posts
named_scope :by_number_of_posts, :order => "forum_posts_count"
end
# controller
ForumTopic.by_number_of_posts.all
To use :counter_cache you'll need to change the association
class ForumPost < ActiveRecord::Base
belongs_to :forum_topic, :counter_cache => true
end
and create a forum_posts_count column on forum_topics table.
I believe this is it.