Use a scope by default on a Rails has_many relationship - ruby-on-rails-3

Let's say I have the following classes
class SolarSystem < ActiveRecord::Base
has_many :planets
end
class Planet < ActiveRecord::Base
scope :life_supporting, where('distance_from_sun > ?', 5).order('diameter ASC')
end
Planet has a scope life_supporting and SolarSystem has_many :planets. I would like to define my has_many relationship so that when I ask a solar_system for all associated planets, the life_supporting scope is automatically applied. Essentially, I would like solar_system.planets == solar_system.planets.life_supporting.
Requirements
I do not want to change scope :life_supporting in Planet to
default_scope where('distance_from_sun > ?', 5).order('diameter ASC')
I'd also like to prevent duplication by not having to add to SolarSystem
has_many :planets, :conditions => ['distance_from_sun > ?', 5], :order => 'diameter ASC'
Goal
I'd like to have something like
has_many :planets, :with_scope => :life_supporting
Edit: Work Arounds
As #phoet said, it may not be possible to achieve a default scope using ActiveRecord. However, I have found two potential work arounds. Both prevent duplication. The first one, while long, maintains obvious readability and transparency, and the second one is a helper type method who's output is explicit.
class SolarSystem < ActiveRecord::Base
has_many :planets, :conditions => Planet.life_supporting.where_values,
:order => Planet.life_supporting.order_values
end
class Planet < ActiveRecord::Base
scope :life_supporting, where('distance_from_sun > ?', 5).order('diameter ASC')
end
Another solution which is a lot cleaner is to simply add the following method to SolarSystem
def life_supporting_planets
planets.life_supporting
end
and to use solar_system.life_supporting_planets wherever you'd use solar_system.planets.
Neither answers the question so I just put them here as work arounds should anyone else encounter this situation.

In Rails 4, Associations have an optional scope parameter that accepts a lambda that is applied to the Relation (cf. the doc for ActiveRecord::Associations::ClassMethods)
class SolarSystem < ActiveRecord::Base
has_many :planets, -> { life_supporting }
end
class Planet < ActiveRecord::Base
scope :life_supporting, -> { where('distance_from_sun > ?', 5).order('diameter ASC') }
end
In Rails 3, the where_values workaround can sometimes be improved by using where_values_hash that handles better scopes where conditions are defined by multiple where or by a hash (not the case here).
has_many :planets, conditions: Planet.life_supporting.where_values_hash

In Rails 5, the following code works fine...
class Order
scope :paid, -> { where status: %w[paid refunded] }
end
class Store
has_many :paid_orders, -> { paid }, class_name: 'Order'
end

i just had a deep dive into ActiveRecord and it does not look like if this can be achieved with the current implementation of has_many. you can pass a block to :conditions but this is limited to returning a hash of conditions, not any kind of arel stuff.
a really simple and transparent way to achieve what you want (what i think you are trying to do) is to apply the scope at runtime:
# foo.rb
def bars
super.baz
end
this is far from what you are asking for, but it might just work ;)

Related

Rails 3: has_many through query

I have these three Active Record models:
class Event < ActiveRecord::Base
has_many :event_categories, inverse_of: :event
has_many :categories, through: :event_categories
end
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :event_categories
has_many :events, through: :event_categories
end
I think the relations are good.
If I want to know what Events have a Category, for example id=5.. I do:
Category.find(5).events
But, if I want to know all Events for more than one category, for example:
Category.where(:id => [3,5]).events
It isn't working. Any ideas?
Please note, when you do has_many :events in a model, Active Record defines a method of name events for that class.
When you do Category.find(5).events, you get events associated with one object (i.e. Category.find(5)) , however Category.where(:id => [3,5]) returns an array of Category objects, so you can't use events function on an array, Only way to get events for all search results is iterate over them and access them individually, something like following:
all_events = Category.where(:id => [3,5]).inject([]) {|res,cat| res << cat.events}
Above code will do one query per iteration, to avoid this, we can include events, in the first query itself, like following, which will provide result in only one query:
all_events = Category.includes(:events).where(:id => [3,5]).inject([]) {|res,cat| res << cat.events}

Rails: How can I tell if a record has a child record in any one of its child tables?

I'm working in Rails 3 and have a table with multiple child tables, e.g.
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
end
class Thing < ActiveRecord::Base
belongs_to :foo
end
class Item < ActiveRecord::Base
belongs_to :foo
end
class Widget < ActiveRecord::Base
belongs_to :foo
end
Is there a simple way for me to check to if a given Foo has a child record in one or more of the tables? Basically, is there a better way to do this:
if !foo.things.empty? or !foo.items.empty? or !foo.widgets.empty?
puts "This foo is in use!"
emd
Well, I think you're on the right track, but maybe just put that as a method in your Foo model
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Then just say, Foo.first.children? and get true if the Foo instance has any children.
This is what any? is for.
class Foo < ActiveRecord::Base
def children?
things.any? || items.any? || widgets.any?
end
end
Since this has become a topic of contention, I present to you:
> foo = Foo.last
Foo Load (0.6ms) SELECT "foos"......
> foo.items
Item Load (0.9ms) SELECT "items".*.......
> foo.items.any?
=> true #OH, WHAT's that? NO SQL CALLS? GEE WILLICKERS
> foo.items.exists?
Item Exists (0.5ms) #Hmmmmmm....
=> true
The point here is that under any circumstances, exists makes a DB call, where as any? will not, if spaces is always loaded into memory. Now as I said, many times, the importance is not the efficiency of the DB call (AND YES, the SQL call exists? makes is more efficient), but the fact that any? won't necessarily make a call to the DB, which is a HUGE advantage. Look for yourself:
[20] pry(main)> Benchmark.measure { foo.item.exists? }
Item Exists (0.5ms) SELECT 1 AS one FROM "items" ...
=> #<Benchmark::Tms:0x007fc1f28a8638
#cstime=0.0,
#cutime=0.0,
#label="",
#real=0.002927,
#stime=0.0,
#total=0.00999999999999801,
#utime=0.00999999999999801>
[21] pry(main)> Benchmark.measure { foo.items.any? }
=> #<Benchmark::Tms:0x007fc1f29d1aa0
#cstime=0.0,
#cutime=0.0,
#label="",
#real=7.6e-05,
#stime=0.0,
#total=0.0,
#utime=0.0>
For a more concise timing, look at this:
> Benchmark.measure { 1000.times {foo.items.exists?} }.total
=> 2.5299999999999994
> Benchmark.measure { 1000.times {foo.items.any?} }.total
=> 0.0
Now as I said, many times, it depends on circumstance -- you could have many circumstances where these items aren't loaded into memory, but many times, they are. Choose which one works best for you depending on how you're calling it.
This should work for any given model.
class Foo < ActiveRecord::Base
def children?
has_associated_records = self.class.reflect_on_all_associations.map { |a| self.send(a.name).any? }
has_associated_records.include?(true)
end
end
You could subclass Thing Item and Widget. Or add a polymorphic join table to keep track of it. Not ideal, I know.
You could at least do this, so it would read a little better.
if foo.things.exists? || foo.items.exists? || foo.widgets.exists?
puts "This foo is in use!"
end
'empty?' uses 'exists?' behind the scenes, I believe.
Suppose all the associations are loaded into memory:
class Foo < ActiveRecord::Base
has_many :things
has_many :items
has_many :widgets
def in_use?
[things, items, widgets].flatten.any?
end
end
Edit
I just realized that this is wrong: each association (even if still loaded into memory) will be loaded which isn't good.
things.any? || items.any? || widgets.any?
is more correct and has been proposed before me.
The answer by #Marcelo De Polli is the most generalized one posted so far.
This answer is an updated version of it for Rails 5.
The parent class for a model is ApplicationRecord in Rails 5 and later, which used to be ActiveRecord::Base up to Rails 4 (n.b., the original question is tagged as Rails 3).
For simplicity of the code, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.map{ |a| self.send(a.name).any? }.any?
end
end
To pursue more run-time efficiency when a model may have many classes of children, use:
class Foo < ApplicationRecord
def children?
self.class.reflect_on_all_associations.each{ |a| return true if self.send(a.name).any? }
false
end
end

Why are individual SELECT queries running when an all-encompassing SELECT already ran? (Rails/ActiveRecord)

I have the following code (note the includes and the .each):
subscribers = []
mailgroup.mailgroup_members.opted_to_receive_email.includes(:roster_contact, :roster_info).each { |m|
subscribers << { :EmailAddress => m.roster_contact.member_email,
:Name => m.roster_contact.member_name,
:CustomFields => [ { :Key => 'gender',
:Value => m.roster_info.gender.present? ? m.roster_info.gender : 'X'
} ]
} if m.roster_contact.member_email.present?
}
subscribers
Correspondingly, I see the following in my logs (i.e. select * from ROSTER_INFO ... IN (...)):
SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` IN ('1450', '1000', '1111')
Yet immediately after that there are select * from ROSTER_INFO for each ID already specified in the IN list of the previous query:
RosterInfo Load (84.8ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1450' LIMIT 1
RosterInfo Load (59.2ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1000' LIMIT 1
RosterInfo Load (56.8ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1111' LIMIT 1
If select * had already been done on ROSTER_INFO on all IDs of interest (IN (...)), why is another select * being done again for each of the same IDs? Doesn't ActiveRecord already know all the ROSTER_INFO columns for each ID?
(Meanwhile, there are no individual queries for ROSTER_CONTACT, yet if I remove :roster_contact from the includes method, then ROSTER_INFO is not queried again, but ROSTER_CONTACT is.)
RosterInfo model (abridged)
class RosterInfo < ActiveRecord::Base
self.primary_key = 'ID'
end
RosterContact model (abridged)
class RosterContact < ActiveRecord::Base
self.primary_key = 'ID'
has_many :mailgroup_members, foreign_key: 'rosterID'
has_many :mailgroups, through: :mailgroup_members
has_one :roster_info, foreign_key: 'ID' # can use this line
#belongs_to :roster_info, foreign_key: 'ID' # or this with no difference
def member_name # I added this method to this
roster_info.member_name # question only *after* having
end # figured out the problem.
end
RosterWeb model (abridged)
class RosterWeb < ActiveRecord::Base
self.primary_key = 'ID'
end
Mailgroup model (abridged)
class Mailgroup < ActiveRecord::Base
self.primary_key = 'ID'
has_many :mailgroup_members, foreign_key: 'mailCatID'
has_one :mailing_list, foreign_key: :legacy_id
end
MailgroupMember model (abridged)
class MailgroupMember < ActiveRecord::Base
self.primary_key = 'ID'
belongs_to :mailgroup, foreign_key: 'mailCatID'
belongs_to :roster_contact, foreign_key: 'rosterID'
belongs_to :roster_info, foreign_key: 'rosterID'
belongs_to :roster_web, foreign_key: 'rosterID'
scope :opted_to_receive_email, joins(:roster_web).where('ROSTER_WEB.receiveEmail=?', 1)
end
The issue turned out to be related to m.roster_contact.member_name -- unfortunately I made member_name a method of roster_contact that itself (indirectly) queried roster_info.member_name. I resolved this by changing the line
:Name => m.roster_contact.member_name,
to directly query roster_info as follows
:Name => m.roster_info.member_name,
I am sorry for the trouble!
I'm going to stick my neck out and say that this is probably an in-flight optimization by your query engine. The 'IN' is typically used to compare large sets of keys, the most efficient way of resolving three keys (assuming ID is the key) would be to retrieve each row by key, as has happened.
class RosterInfo < ActiveRecord::Base
has_one :roster_contact, foreign_key: 'ID'
end
class RosterContact < ActiveRecord::Base
has_one :roster_info, foreign_key: 'ID'
end
I don't know what is the premise for having bi-directional has_one, but I suspect it will turn out badly. Probably change one of them to belongs_to. Do the same for the other bi-directional has_one associations.
Another thing is that you are using 'ID' for the foreign_key column, where the usual practice is roster_contact_id or whichever class you are referencing.
Edit:
On closer examination, RosterInfo, RosterContact, RosterWeb look like separate tables for what should be a single record since they are all having the same set of mutual has_one associations. This is something that should be addressed on the schema level, but right now you should be able to drop the has_one associations from one of the three models to solve your immediate problem.

Trying to create a scope in rails 3 for has habtm relationship

I'm trying to do a basic search on an active record but am having trouble because of the has_and_belongs_to_many relationship. Below are the records and scopes I've created.
Class Section << ActiveRecord::Base
has_and_belongs_to_many :teachers
attr_accessible :department, :class_size
scope :department_scope, lambda { |dept| where("department = ?", dept) }
scope :teacher_fname_scope, lambda { |n| joins(:teachers) & Teacher.has_first_name(n) }
end
Class Teacher << ActiveRecord::Base
has_and_belongs_to_many :sections
attr_accessible :first_name, :last_name
scope :has_first_name, lambda { |fname| where("first_name = ?", fname) }
end
In Rails 3.2 I'm creating a basic search for my Sections model. Suppose I want to do a search for all Sections with a Teacher with a given first_name.
I've tried it using the scopes above, but all I'm getting back from *Section.teacher_fname_scope* is an empty array.
(I'm actually going to have multiple fields, to let the user also search on Section fields like department and class_size so I'll probably create multiple scopes and ultimately chain them together, e.g. the above search by also limited by department and class_size, but my understanding is the scopes are orthogonal so this won't matter to the question above.)
Thanks for any help.
It looks like the winning scope is:
Class Section << ActiveRecord::Base
scope :teacher_fname_scope, lambda { |n| joins(:teachers).("teachers.first_name = ?", n) }
end
This makes sense but I don't see why the original didn't work, given what Ryan Bates talks about in http://railscasts.com/episodes/215-advanced-queries-in-rails-3?view=asciicast
This is an old question, but since I fell here, I'll leave an answer for whoever hits this question:
class Teacher < ActiveRecord::Base
scope :by_name, ->(name) { where(first_name: name) }
end
class Section < ActiveRecord::Base
has_and_belongs_to_many :teachers
scope :by_teacher_name, ->(name) {
joins(:teachers).merge(Teacher.by_name(name))
}
end

Dynamic virtual attribute?

My Model:
class Student < ActiveRecord::Base
has_many :lack_knowledge_points, :through => :knowledge_point_infos,
:conditions => ['knowledge_point_infos.level <= ?',10],:source => :knowledge_point
I want that 10 be dynamic
What is my best practice?
Define a method and find_by_sql? Or can Rails do something else for me?
I am not clear how you want 'level' to be dynamic. Anyway, you could use scope with lambda or define a method in the model.
#If you want it to return an array
def lack_knowledge_points(threshold)
knowledge_point_infos.where('level <= ?', threshold).map{|info|info.knowledge_point}
end