Ruby on Rails - Possible to eager load within a .map() method? - ruby-on-rails

Is it possible to use eager loading as part of a .map() method?
For example, I have the following:
tag_images = tags.map(&:images)
And elsewhere, each of those tag_images has its parent_image accessed with image.parent_image.
The bullet gem is advising I use .includes(:parent_image), but I'm not sure how to apply eager loading in this situation.
Here is how the applicable parts of the models are set up...
Image.rb
has_many :image_tags
has_many :tags, through: :image_tags
has_many :crops, class_name: "Image", foreign_key: "parent_image_id"
belongs_to :parent_image, class_name: "Image"
Tag.rb
has_many :image_tags
has_many :images, through: :image_tags
What's going on is that it is going through a series of one or more tags and "collecting" all of the images associated with them (thus the use of tags.map(&:images)). And later on, the parent_image for each of those images is referenced with image.parent_image.
So ideally, while it's going through to "collect" the tag_images, is there a way it could 'get' the associated parent_images while it's already in there during the initial .map method (or perhaps some other query that is more suitable)?
Any ideas?

Assuming you have a variable tags that contains an enumerble of Tags (like an ActiveRecord_Relation), I think you might want something more like:
tag_images = Image.joins(:tags).where(tags: {id: tags}).distinct.includes(:parent_image)
You may or may not need that .distinct - you'll just have to mess with it to see.
BTW, you can check the Specifying Conditions on the Joined Tables docs to learn more about that .joins(:tags).where(tags: {id: tags}) bit.

If you write it as what Bullet suggested tag_images = tags.includes(:parent_image), that will eager load the included associations and add them in memory. If you call parent_image on any tag instances, it will not fire off any additional queries.

Related

How to access has_many through relationship possible in rails?

How can I access my related records?
class Post < ActiveRecord::Base
has_many :post_categories
has_many :categories, through: :post_categories
class Categories < ActiveRecord::Base
has_many :post_categories
has_many :post, through: :post_categories
class PostCategories < ActiveRecord::Base
belongs_to :post
belongs_to :category
PostCategories table has id, posts_id, and categories_id columns.
id | posts_id | categories_id
1. | 2 | 3
2. | 2 | 4
What I want is: to get posts related to a category. like: all Posts where in x category.
Yep, this is an easy one.
one_or_more_categories = # Category.find... or Category.where...
posts = Post.joins(:categories).where(category: one_or_more_categories)
Rails is clever enough to take either a model or a query that would find some data and turn that into an efficient appropriate query, that might be a subquery. Trying things out in the Rails console (bundle exec rails c) is a good way to see the generated SQL and better understand what's going on.
(EDIT: As another answer points out, if you've already retrieved a specific Category instance then you can just reference category.posts and work with that relationship directly, including chaining in .order, .limit and so-on).
Another way to write it 'lower level' would be:
Post.joins(:categories).where(category: {id: one_or_more_category_ids})
...which is in essence what Rails will be doing under the hood when given an ActiveRecord model instance or an ActiveRecord::Relation. If you already knew the e.g. category "name", or some other indexed text column that you could search on, then you'd adjust the above accordingly:
Post.joins(:categories).where(category: {name: name_of_category})
The pattern of joins and where taking a Hash where the join table name is used as a key with values nested under there can be taken as deep as you like (e.g. if categories had-many subcategories) and you can find more about that in Rails Guides or appropriate web searches. The only gotcha is the tortuous singular/plural stuff, which Rails uses to try and make things more "English-y" but sometimes - as in this case - just creates an additional cognitive burden of needing to remember which parts should be singular and which plural.
Not sure if this answers it but in ActiveRecord your Post will have direct access to your Category model and vice versa. So you could identify the category you want the posts from in a variable or an instance variable, and query #specific_category.posts. If you are doing this in your controller, you could even do it in before_action filter. If you are using it in serializers its not much different.
You could also create a scope in your Post model and use either active record or raw SQL to query specific parameters.
You also have an error in your Category model. Has many is always plural so it would be has_many :posts, through: :post_categories
Get the category object and you can directly fetch the related posts. Please see the following
category = Category.find(id)
posts = category.posts
Since you have already configured the has_many_through relation, rails will fetch post records related the category.

Get children of different models in one single query

class Category
has_many :images
has_many :articles
end
class Image
belongs_to :category
end
class Article
belongs_to :category
end
I'm trying to understand what solutions there are in Rails for children of different models to be queried by the same parent?
E.g. I'd like to get all images and articles that belong to the same category and sort them all by created_at.
You can try 'includes' in rails
Article.includes(:Category)
As I said it seems to me you can use eager loading multiple associations. In your case it could be something like this:
Category.where(id: 2).includes(:images, :articles).sort_by(&:created_at)
Basically you pass your desired Category ID and get :images, :articles which belongs_to Category with particular ID. sort_byprobably should do the sorting thing.
This blog post on eager loading could help you as well.
You can't simply force Active Record to bring all their dependences in a single query (afaik), regardless if is lazy/eager loading. I think your best bet is:
class Category
has_many :images, -> { order(:created_at) }
has_many :articles, -> { order(:created_at) }
end
categories = Category.includes(:images, :articles)
As long as you iterate categories and get their images and articles, this will make three queries, one for each table categories, images and articles, which is a good tradeoff for the ease of use of an ORM.
Now, if you insist to bring all that info in just one query, for sure it must be a way using Arel, but think twice if it worths. The last choice I see is the good old SQL with:
query = <<-SQL
SELECT *, images.*, articles.*
FROM categories
-- and so on with joins, orders, etc...
SQL
result = ActiveRecord::Base.connection.execute(query)
I really discourage this option as it will bring A LOT of duplicated info as you will joining three tables and it really would be a pain to sort them for your use.

Reduce N+1 Queries

N+1 queries detected what does that mean what should i do to make it work.
I am using Bullet gem for showing N+1 queries
user: kalyan
N+1 Query detected
Emp => [:functions]
Add to your finder: :include => [:functions]
N+1 Query method call stack
/home/Documents/app/views/tics/show.html.haml:20:in `_app_views_tics_show_html_haml___2301551533476719406_237290860'
This is the message from bullet gem.
_app_views_tics_show.html.haml
- if (#tic.assigned_to == current_user.emp) or (current_user.emp_functions.map{|x| x.id}.include?(1) if current_user.emp_functions.present? )
= best_in_place #tic, :subject
- else
= #tic.subject
help me to reduce n+1 query problem
emp.rb
has_many :emp_functions, inverse_of: :emp,dependent: :restrict_with_exception
belongs_to :user, inverse_of: :emp
emp_functions.rb
belongs_to :emp , inverse_of: :emp_functions
belongs_to :function
function.rb
has_many :emp_functions, dependent: :restrict_with_exception
has_many :emp, through: :emp_functions
user.rb
has_one :emp, inverse_of: :user, dependent: :restrict_with_exception
Imho, the most efficient way to get rid of this N+1 query is to alter the current_user method, adding includes(:emp_functions) to the User.find call. The way you do it depends on the way you handle your authentication. If you're using some kind of gem (Devise or Sorcery for example) you'll need to reopen those classes and alter the method, not forgetting to use super. If youэму written your own authentication, you'd be able to do that more easily.
Second thing I noticed is that you don't actually need to use map on your user.emp_functions in the view, provided that emp_functions is a has_many association on the User model. You can just flatten it to current_user.emp_function_ids.include?(1). This will help you get rid of the N+1 query problem, but only in this particular case. If you do tackle the emp_functions of the current user often in many places, I'd recomend using the first path I described.
Third third thing, not directly relevant to the question is that I really don't think that code belongs in the view. You should move it to a helper or a decorator, keeping the view clean and readable.
If emp_functions is a has_many association on your user model (and it seems like it is), You should be doing:
current_user.emp_function_ids.include?(1)
This is a very specific solution to your issue and I advise against using it in a view
change current_user.emp_functions.map{|x| x.id}.include?(1) to
current_user.emp_functions.where(id: 1).exists?
This will result in 1 single query to the db everytime you hit the page containing it.
You can also remove that if statement to remove another db query.

Recursive :include in Rails

I have a polymorphic model that can relate to itself:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
has_many :comments, as: :commentable
end
These relationships work perfectly, except when I'm trying to call the full tree of child/parent comments through an include statement:
Post.find(1).include(:comments)
This only includes the comments directly tied to the post. I could perhaps retrieve a second level with:
Post.find(1).include(comments: :comments)
But what if I wanted to get all comments descending from the post, no matter how deeply nested? Is this possible?
It seems that you want to retrieve an adjacency list. Rails has no immediate support for it, but if you are using postgresql, you can use the "WITH RECURSIVE" operator.
That plugin takes care of it : https://github.com/chrisroberts/acts_as_sane_tree
Otherwise, you can pretty easily create your own postgresql function (declare it in a migration), and then use it in your queries. Have a look at : http://wiki.postgresql.org/wiki/Getting_list_of_all_children_from_adjacency_tree
WITH RECURSIVE is not currently implemented in mysql or sqlite3.

Rails Advanced Sorting

I have three models, basically:
class Vendor
has_many :items
end
class Item
has_many :sale_items
belongs_to :vendor
end
class SaleItem
belongs_to :item
end
Essentially, each sale_item points to a specific item (but has an associated quantity and sale price which might be different from the item's base price, hence the separate model), and each item is made by a specific vendor.
I'd like to sort all sale_items by vendor name, but this means going through the associated item, because that's where the association is.
My first attempt was to change SaleItem to the following:
class SaleItem
belongs_to :item
has_one :vendor, :through => :item
end
Which allows me to look for SaleItem.first.vendor, but doesn't allow me to do something like:
SaleItem.joins(:vendor).all(:order => "vendors.name")
Is there an easy way to figure out these complex associations and sorting? It would be especially great if there were a plugin that could take care of these sort of things. I have a lot of different types of tables to add sorting to in this application, and I feel like this will be a big chunk of the figuring-out work.
This could definitely be done with a more complex SQL query (possibly using find_by_sql), but you could also do it pretty easily in Ruby. Try something like the following:
SaleItem.find(:all, :include => { :items => :vendors }).sort do |first,second|
first.vendor.name <=> second.vendor.name
end
I haven't tested it, so it might not work exactly like this, but it should give you a good idea of one possible solution.
Edit: Found an old blog post that seems to have solved this issue. Hopefully this still works in the lastest version of ActiveRecord.
source: http://matthewman.net/2007/01/04/eager-loading-objects-in-a-rails-has_many-through-association/
Second Edit: Straight from the Rails documentation
To include a deep hierarchy of associations, use a hash:
for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
That’ll grab not only all the comments but all their authors and gravatar pictures. You can mix and match symbols, arrays and hashes in any combination to describe the associations you want to load.
There's your explanation.
Do you really need your sale_items sorted by the database, or could you wait until it is presented and do the sorting client side via javascript (there are some great sorting libraries out there) - that would save server CPU and (backend) code complexity.

Resources