I have the following class:
class Car < ActiveRecord::Base
has_many :adverts, :dependent => :destroy
has_many :active_adverts, :class_name => 'Advert', :select => "adverts.id, adverts.availability_state, adverts.source_name, adverts.last_observed_at", :conditions => "availability_state = 'active'"
end
When I do: cars = Car.where(:brand => 'BMW').includes(:adverts) it works fine, and returns me an array of the cars that I requested, and the adverts are eager loaded correctly.
How ever if I do: cars = Car.where(:brand => 'BMW').includes(:active_adverts) it seems to execute the expected queries, but it returns me an ActiveRecord::Relation object and if I ask cars.first I get:
NoMethodError: undefined method `each' for nil:NilClass
from /Users/nk/.rvm/gems/ruby-1.9.3-p194#au/gems/activerecord-3.2.13/lib/active_record/associations/preloader/association.rb:88:in `block in associated_records_by_owner'
What am I doing wrong?
PS. I followed the advice her: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Where it states:
If you do want eager load only some members of an association it is
usually more natural to include an association which has conditions
defined on it:
class Post < ActiveRecord::Base
has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
end
Post.includes(:approved_comments) This will load posts and eager load
the approved_comments association, which contains only those comments
that have been approved.
Found it, forgot to select the foreign key car_id in the select.
Related
In Rails 5, given a relationship between two tables that involves joining them on multiple shared attributes, how can I form an association between the models corresponding to these tables?
SQL:
SELECT *
FROM trips
JOIN stop_times ON trips.guid = stop_times.trip_guid AND trips.schedule_id = stop_times.schedule_id
I tried the following configuration, which works in general...
class Trip < ApplicationRecord
has_many :stop_times, ->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) }, :inverse_of => :trip, :primary_key => :guid, :foreign_key => :trip_guid, :dependent => :destroy
end
class StopTime < ApplicationRecord
belongs_to :trip, :inverse_of => :stop_times, :primary_key => :guid, :foreign_key => :trip_guid
end
Trip.first.stop_times.first #> StopTime object, as expected
Trip.first.stop_times.first.trip #> Trip object, as expected
... but when I try to use it in more advanced queries, it triggers ArgumentError: The association scope 'stop_times' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported....
Trip.joins(:stop_times).first #=> the unexpected ArgumentError
StopTime.joins(:trip).first #> StopTime object, as expected
I understand what the error is referencing, but I'm unsure of how to fix it.
EDIT:
I was hoping a single association would be sufficient, but it has been noted two different associations can do the job:
class Trip < ApplicationRecord
has_many :stop_times,
->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) },
:primary_key => :guid,
:foreign_key => :trip_guid # use trip.stop_times instead of trip.joined_stop_times to avoid error about missing attribute due to missing join clause
has_many :joined_stop_times,
->{ where("stop_times.schedule_id = trips.schedule_id") },
:class_name => "StopTime",
:primary_key => :guid,
:foreign_key => :trip_guid # use joins(:joined_stop_times) instead of joins(:stop_times) to avoid error about instance-specific association
end
Trip.first.stop_times
Trip.eager_load(:joined_stop_times).to_a.first.joined_stop_times # executes a single query
If anyone reading this knows how to use a single association, please at-mention me.
I don't think it is the right solution, but it can help. You can add another similar instance independent association that will be used for preloading only. It will work with :joins and :eager_load but not with :preload.
Note that :includes might internally use either :eager_load or :preload. So, :includes will not always work with that association. You should explicitly use :eager_load instead.
class Trip < ApplicationRecord
has_many :preloaded_stop_times,
-> { where("stop_times.schedule_id = trips.schedule_id") },
class_name: "StopTime",
primary_key: :guid,
foreign_key: :trip_guid
end
# Usage
trips = Trip.joins(:preloaded_stop_times).where(...)
# ...
# with :eager_load
trips = Trip.eager_load(:preloaded_stop_times)
trips.each do |trip|
stop_times = trip.preloaded_stop_times
# ...
end
I have my models setup like this:
class Country < ActiveRecord::Base
has_many :manufacturers
end
class Manufacturer < ActiveRecord::Base
belongs_to :country
has_many :cars
end
class Cars < ActiveRecord::Base
belongs_to :manufactuer
has_many :comfort_levels
attr_accessor :attr_accessor_1, :attr_accessor_2
end
class ComfortLevel < ActiveRecord::Base
belongs_to :car
end
This is how I am eager loading manufacturers with cars (including car's attr accessors) for a country:
data = current_country.manufacturers.to_json :include => {:cars => {:methods => [:attr_accessor_1, :attr_accessor_1]}}
What will be the syntax to also eager load the comfort levels for cars in the above call?
I have tried various things, but no luck so far.
Would greatly appreciate any help in this regard. Thanks!
Have you tried this?
data = current_country.manufacturers.to_json :include => {:cars => {:methods => [:attr_accessor_1, :attr_accessor_1], :comfort_levels => {}}}
I was finally able to eager load everything, using:
data = current_country.manufacturers.to_json :include => [{:cars => {:methods => [:attr_accessor_1, :attr_accessor_1]}}, :comfort_levels]
The above call did not nest comfort level records inside car records, however, the following did:
data = current_country.manufacturers.to_json :include => {:cars => {:methods => [:attr_accessor_1, :attr_accessor_1, :comfort_levels]}}
This link helped me clear some concepts around the syntax for eager loading.
Help me please.
I have some model which has_many association with other model.
For example: profile => has_many :statistics
And inside of statistic model I have some scope:
scope last_ten, limit(10).order('online desc')
And question is how can I use eager load for this scope? I don't need every record of statistics for profile. Only scoped.
Now I can use only
User.profiles.includes(:statistics)
Thanks.
As explained here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
It's better to define a custom relation:
class Profile < ActiveRecord::Base
has_many :most_recent_stats, :class_name => 'Statistic', :order => 'online DESC', :limit => 10
...
end
User.profiles.includes(:most_recent_stats)
class Message
has_many :threads, :class_name=>"Message", :conditions => "`#{Message.table_name}`.conversation_id = #{self.send(:conversation_id)}"
end
m = Message.first
NoMethodError: undefined method `conversation_id' for #<Class:0xc5021dc>
I even tried with single quote:
class Message
has_many :threads, :class_name=>"Message", :conditions => '`#{Message.table_name}`.conversation_id = #{self.send(:conversation_id)}'
end
m = Message.first
m.threads
This gave me Mysql::Error: You have an error in your SQL syntax
It seems it's not considering the #{...} thing while generating the condition sql
i could do it with scopes
scope :threads, lambda {|conv_id| where(:conversation_id => conv_id) }
and access it Message.where("some condition").threads()
but am looking for a neat association like
m = Message.find(1000)
m.threads should give all the conversation threads which it belongs to
You cannot use dynamic conditions in has_many. However, in your particular case it seems you need primary_key and foreign_key instead:
class Message
has_many :threads, :class_name=>"Message", :primary_key => 'conversation_id', :foreign_key => 'conversation_id'
end
You may also be interested by one of the gems that adds tree structure to ActiveRecord.
I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])