How to specify eager loading of associations in rails model - ruby-on-rails

class A < ActiveRecord::Base
has_many :Bs
has_many :Cs
...
end
I wish to load all of B's and C's whenever I do a query on A, say A.where(name: :abc), with a single query, instead of multiple calls to database.
I don't wish to specify .includes for every query I run. How do I specify eager loading in the model itself?
I looked many similar question and tried do this but it does not work:
default_scope :include => [:Bs, :Cs]

default_scope { includes(:Bs, :Cs) } should do it.
As far as I know the scope takes a block as argument not a hash of options. I just tried it in the rails console and seems to work.

Related

Rails preloading the association

I am having trouble preloading a belongs_to association.
The model in question has following association
class Order < ActiveRecord::Base
belongs_to :user_name, -> { select(:id, :name) }, class_name: "User", :foreign_key => 'user_id'
end
The above association works fine and resolves as expected.
I want to return the user.id and user.name along with order.
How do I preload the association user_name.
I did try Order.includes(:user_name), which did not work as expected.
I would change association just to:
class Order < ActiveRecord::Base
belongs_to :user
end
and select users.id and users.name elsewhere. It makes your code more flexible.
How do I preload the association
ActiveRecord provides a few ways for that:
Preload. Loads the association data in a separate query. Since
preload always generates two sql you can not use preloaded table in
where condition.
Includes. By default loads the association data in
a separate query just like preload. But with additional references
call it switches from using two separate queries to creating a
single LEFT OUTER JOIN. So with that you can use association tables in where condition.
Eager load. Loads association in a single query using LEFT OUTER JOIN.
Here is some useful articles:
Preload, Eagerload, Includes and Joins
Making sense of ActiveRecord joins, includes, preload, and eager_load
Try adding a scope like below
scope : user_name_id, -> { joins(:user_name).select( 'user_names.id, user_names.name' ).collect{|c| [c.id, c.name]} }

Rails 4 Relation is not being sorted: 'has_many' association w/ default scope, 'includes' and 'order' chained together

I am trying to use a default scope to impose a sort order on the model QuizCategoryWeight. The goal is to get #possible_answer.quiz_category_weights to return the weights in sorted order.
Update: I have narrowed the problem down to the fact that default scopes seem to work for me as long as they just have an 'order' method but not when the 'includes' method is chained with the 'order' method. However, this chaining does work for named scopes.
Could it be my development environment? Or is this a bug in Rails perhaps?
I am using windows, so maybe that's the problem. Currently on ruby 2.0.0p645 (2015-04-13) [i386-mingw32] and Rails 4.2.4...
The following, using a default scope on QuizCategoryWeight, does not seem to work:
class QuizCategoryWeight < ActiveRecord::Base
#trying to use a default scope, but does not work
default_scope { includes(:quiz_category).order("quiz_categories.sort_order") }
belongs_to :possible_answer, inverse_of: :quiz_category_weights,
class_name: 'QuizPossibleAnswer', foreign_key: 'possible_answer_id'
belongs_to :quiz_category
end
class QuizPossibleAnswer < PossibleAnswer
has_many :quiz_category_weights,
#does not work whether the line below is used or not
->{ includes(:quiz_category).order("quiz_categories.sort_order") },
inverse_of: :possible_answer,
dependent: :destroy,
foreign_key: 'possible_answer_id'
end
class QuizCategory < ActiveRecord::Base
default_scope { order :sort_order }
end
With a named scope, it does work. However, this means that I have to add an argument to my form builder to use the collection 'f.object.quiz_category_weights.sorted'.
class QuizCategoryWeight < ActiveRecord::Base
# named scope works...
scope :sorted, ->{ includes(:quiz_category).order("quiz_categories.sort_order") }
belongs_to :possible_answer, inverse_of: :quiz_category_weights,
class_name: 'QuizPossibleAnswer', foreign_key: 'possible_answer_id'
belongs_to :quiz_category
end
class QuizPossibleAnswer < PossibleAnswer
has_many :quiz_category_weights,
inverse_of: :possible_answer,
dependent: :destroy,
foreign_key: 'possible_answer_id'
end
I think there is a bug with using 'includes' with a default scope, either in the Rails framework generally or in my windows version.
However, I've found that using 'joins' does work. I'm not using any of other the attributes from QuizCategory so it's more appropriate to my use case as well: I only want to sort using the 'sort_order' attribute from the joined table.
The fixed code is:
class QuizCategoryWeight < ActiveRecord::Base
default_scope { joins(:quiz_category).order("quiz_categories.sort_order") }
belongs_to :quiz_category
end
The includes method was introduces for relations to give Rails a hint to reduce database queries. It says: When you fetch Objects of type A, also fetch associated objects, because I need them later, and they should not fetched one by one (the N+1 queries problem)
The includes was first implemented with two database queries. First all A, then all B with one of the ids from A. Now includes often uses a sql join to have only one database query. But this is an internal optimisation.
The concept is object oriented, you want objects from A, then you retrieve the B through the A. So I think, if you set the order from the included B back to A, you are doing more than was meant for the original includes.

Rails 4 - Restrict retrieved columns from associated models

I'm new to Rails, and while writing Active Record queries, I notice that all columns of all associated tables are being retrieved. I would like to tell Active Record which fields from which tables ought to be retrieved. How would go about doing that?
My models and their associations are as follows:
class User < ActiveRecord::Base
has_one :profile
has_many :comments
has_many :posts
end
class Profile < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
I'm following the Rails Edge Guides, and when I try to use select("users.id, profiles.first_name, profiles.last_name, comments.comment") to specify the field lists, I get a deprecation warning on the Rails console (and the SQL query that is run is a LEFT OUTER JOIN of all tables involved, but it still includes all columns):
DEPRECATION WARNING: It looks like you are eager loading table(s) (one of: users, posts) that are referenced in a string SQL snippet. For example:
Post.includes(:comments).where("comments.title = 'foo'")
Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string:
Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
If you don't rely on implicit join references you can disable the feature entirely by setting `config.active_record.disable_implicit_join_references = true`. (called from irb_binding at (irb):34)
Check if following work for you
Class User < ActivcRecord::Base
default_scope select("column1, column2, column3")
end
Buried deep inside the Rails Edge Guides for Active Record Query Interface, I found the answer. The trick is to use scopes for the particular association type where you want to restrict the retrieved fields.
Quoted directly from the guide:
4.1.3 Scopes for belongs_to
There may be times when you wish to customize the query used by belongs_to. Such customizations can be achieved via a scope block. For example:
class Order < ActiveRecord::Base
belongs_to :customer, -> { where active: true },
dependent: :destroy
end
You can use any of the standard querying methods inside the scope block.
So, adding a select method to the above scope, with the list of fields you want retrieved will do the trick.

Rails Eager Load and Limit

I think I need something akin to a rails eager loaded query with a limit on it but I am having trouble finding a solution for that.
For the sake of simplicity, let us say that there will never be more than 30 Persons in the system (so Person.all is a small dataset) but each person will have upwards of 2000 comments (so Person.include(:comments) would be a large data set).
Parent association
class Person < ActiveRecord::Base
has_many :comments
end
Child association
class Comment < ActiveRecord::Base
belongs_to :person
end
I need to query for a list of Persons and include their comments, but I only need 5 of them.
I would like to do something like this:
Limited parent association
class Person < ActiveRecord::Base
has_many :comments
has_many :sample_of_comments, \
:class_name => 'Comment', :limit => 5
end
Controller
class PersonController < ApplicationController
def index
#persons = Person.include(:sample_of_comments)
end
end
Unfortunately, this article states: "If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects"
Is there any good way around this? Or am I doomed to chose between eager loading 1000s of unneeded ActiveRecord objects and an N+1 query? Also note that this is a simplified example. In the real world, I will have other associations with Person, in the same index action with the same issue as comments. (photos, articles, etc).
Regardless of what "that article" said, the issue is in SQL you can't narrow down the second sql query (of eager loading) the way you want in this scenario, purely by using a standard LIMIT
You can, however, add a new column and perform a WHERE clause instead
Change your second association to Person has_many :sample_of_comments, conditions: { is_sample: true }
Add a is_sample column to comments table
Add a Comment#before_create hook that assigns is_sample = person.sample_of_comments.count < 5

How do I optimize a polymorphic news feed in rails?

Here is my model:
class User < ActiveRecord::Base
has_many :activities
has_many :requests
class Activity < ActiveRecord::Base
belongs_to :user
belongs_to :object, :polymorphic => true
I want to get all the users activities and display them
Activity.where(:user_id => current_user.id).include(:object)
the problem is that I can't eager load the object model because it's polymorphic
How do I overcome this problem?
Eager loading is supported with polymorphic associations. You will need to do something along the following lines:
Activity.find(:all, :include => :objectable, :conditions => {:user_id => current_user.id})
Although you need to make sure that you have defined the polymorphic relationship correctly on the associated models.
For further help refer to:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Eager+loading+of+associations
The polymorphic part is at the end of "Eager loading of Associations" section.
As #Wahaj says, eager loading only works with :includes and not :join.
Here's the explanation from the docs:
Address.find(:all, :include => :addressable)
This will execute one query to load the addresses and load the addressables with one query per addressable type. For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model’s type is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that query.
I think this is what you're after:
current_user.activities.includes(:object)
As the docs say, there will be an extra query for each association. I'm not sure, but you may need to define an association the other direction for rails to know which AR models to search, eg:
class Post < ActiveRecord::Base
has_many :activities, :as => :object
end
If you're still getting an error, you might be on an earlier rails version which hadn't yet implemented this.
Its not possible to have eager loading to the Polymorphic relationship ... but u can do it for one polymorphic type like if u r having two polymorphic_type then filter the records on that type and then make eager loading it will work then .... not the perfect eager loading but partial eager loading

Resources