I'm using subclasses in mongoid and I would like to eager load relations of both the parent and its subclasses.
class Event
include Mongoid::Document
belongs_to :modifier
end
class Fixture < Event
belongs_to :club
end
When I run Event.includes(:modifier, :club) I get:
Mongoid::Errors::InvalidIncludes:
Problem:
Invalid includes directive: Event.includes(:modifier, :club)
Summary:
Eager loading in Mongoid only supports providing arguments to
Event.includes that are the names of relations on the Event model,
and only supports one level of eager loading. (ie, eager
loading associations not on the Event but one step away via
another relation is not allowed.
Resolution:
Ensure that each parameter passed to Event.includes is a valid name
of a relation on the Event model. These are: "modifier".
The error message is reasonable but I'd like to know if there is a workaround, short of running a separate query on each class?
I'd like to be able to also chain further criteria, i.e. Event.includes(:modifier, :club).desc(:updated_at) rather than sorting the result array in rails.
versions: Mongoid v3.16 & Rails 3.2.15
Edit: I'd better make it clearer what it is I want.
I want all events and all fixtures documents.
I also want all their relations: the modifiers on Events and Fixtures, and the clubs on Fixtures.
And I want those related documents to be retrieved from mongo all at once, i.e. typically done through 'eager loading' using the '.includes()' method .
There will be further subclasses, i.e. class Election < Events, class Seminar < Events. So I'd like to avoid querying each subclass separately.
I'd also like to chain further criteria, such as desc(:updated_at).
I am not exactly sure what you try to do, but I guess this could solve your problem.
Event.where(:modifier_id.ne => nil, :club_id.ne => nil).desc(:updated_at)
Let me know if that works for you.
so far MongoID doesn't support nested document eager loading. you better check out this gem
Related
Rails 6/ Ruby 2.7
So I have two classes ModuleX::SubModuleA::Order and ModuleY::SubModuleB::OrderType
and I want to do something like
ModuleX::SubModuleA::Order.joins("ModuleY::SubModuleB::OrderType")...
This syntax would be pretty simple normally: ModuleX::SubModuleA::Order.joins(:order_type) but I cannot find any documentation regarding how this works for classes in modules.
The syntax of joins allows two uses: first, referencing a relation defined on the left-hand model by its (symbolic) name, or second, raw SQL. Neither method is actually impacted at all by the use of modules, because neither references the class of the model you're joining onto.
In your case, you probably want to set up a relation between orders and order types, like:
class ModuleX::SubModuleA::Order
belongs_to :order_type, class_name: 'ModuleY::SubModuleB::OrderType'
end
Then, you can just do the same syntax as you expect:
ModuleX::SubModuleA::Order.joins(:order_type).all
I understand that generally it is better to avoid having default scopes in Rails models, however, as some associations are always needed & instead of having the .includes method in the controller every time for the model, I prefer having that includes in a default scope in the model itself.
Is there any major drawback of this change (moving .includes to model's default_scope instead of controller)?
So for example:
Instead of having in the controller:
Brand.includes(:car_model)
I want to have includes in the Brand model itself this:
default_scope { includes(:car_model) }
Note:
From what I see is that bullet is now saying avoid eager load to avoid this includes instead of the use eager load that I had before on the same model & included association.
Some of my classes :
class User
embeds_many :notifications
field :first_name
field :last_name
def name{ "#{first_name} #{last_name}" }
class Notification
embedded_in :user
belongs_to :sender, class_name: "User", inverse_of: nil
Now in my views, I implemented a small mailbox system for notifications. However, it's currently hitting N+1 times the database :
<% current_user.notifications.sort{...}.each do |notif|%>
...
<%= notif.sender.name if notif.sender %>
The problem here is the notif.sender.name which causes N hits on the database. Can I somehow preload/eager load this ? Something like current_user.notifications.includes(:sender) (but which would work :D)
I currently only need the sender name.
I think you're half out of luck here. Mongoid has an error message like:
Eager loading in Mongoid only supports providing arguments to M.includes that are the names of relations on the M model, and only supports one level of eager loading. (ie, eager loading associations not on the M but one step away via another relation is not allowed).
Note the last parenthesized sentence in particular:
eager loading associations not on the M but one step away via another relation is not allowed
Embedding is a relation but you want to apply includes to the embedded relation and that's one step too far for Mongoid.
The fine manual does say that:
This will work for embedded relations that reference another collection via belongs_to as well.
but that means that you'd call includes on the embedded relation rather than what the models are embedded in. In your case, that means that you could eager load the senders for each set of embedded Notifications:
current_user.notifications.includes(:sender).sort { ... }
That still leaves you with the N+1 problem that eager loading is supposed to get around but your N will be smaller.
If that's still too heavy then you could denormalize the name into each embedded document (i.e. copy it rather than referencing it through the sender). Of course, you'd need to maintain the copies if people were allowed to change their names.
It's not perfect, but this article presents a possible solution.
You can load all the senders and use set_relation to avoid them to be loaded every time.
def notifications_with_senders
sender_ids = notifications.map(:sender_id)
senders = User.in(id: sender_ids).index_by(&:id)
notifications.each do |notification|
notification.set_relation(:sender, senders[notification.sender_id])
end
end
Would be great to have that as a Relation method (like includes of Rails Active Record)
I'm using Mongoid to build a Rails 4 app.
The problem I'm having right now is how to filter some Mongoid objects through their own relations and have a Mongoid::Criteria at the end instead of an Array.
This is some example code:
class Editor
include Mongoid::Document
has_many :books
def likes
books.collect { |book| book.likes }
end
end
class Book
include Mongoid::Document
belongs_to :editor
end
What I would like to be able to do is something like:
Editor.last.likes.where(:created_at.gt => 10.days.ago)
but of course this doesn't work as Editor.last.likes returns an Array, not a Mongoid::Criteria
I know Mongoid has an aggregation framework but it's not entirely clear to me how to use it, nor if it's the best way to solve my problem.
Suggestions?
TIA,
ngw
The biggest problem you have here is that MongoDB does not do joins like a relational database does. All of the work you are getting for convenience when traversing object properties is being done client side while pulling in the 'related' documents over the wire in a query. But in sending a query, the two collections cannot be joined.
Your workaround solution is to work with the data you can get at in separate queries in order to target the results. One approach is here: rails mongoid criteria find by association
There should be other examples on Stack Overflow. You're not the first to ask.
I am using Ruby on Rails 3.0.7 and, for performance reason, I would like to avoid loading associated objects on retrieving a class obect. That is, if I have an Article class\model with a has_many :users statement I would like to not load associated User objects when I retrieve an Article object (I think this behavior depends on the Ruby on Rails "Convention over Configuration" principle).
How can I do that?
As noted by Yet Another Geek, Rails (ActiveRecord) doesn't load the relationship objects by default. Rather, it goes and gets them when you ask for them. If you don't need the objects of that relationship, it will never bother to load them, saving database time.
If you do need then, it will go retrieve them lazily (by default). If you know you'll need all (or many) of the objects of the relationship (assuming x-to-many), then you can use the :include modifier to your find to get them all up front (which will be a lot faster since it can do that with a single db call). Knowing and taking advantage of the ability to eagerly load relationship objects is an important thing.
#person = Person.find(params[:id], :include => :friends)
All that being said, the behavior you want (not loading the objects if you don't need them) is the default behavior and you should be all set. The rest of the answer was just some context that may be useful to you later.
Implied by this wiki article, loading is lazy by default. You have to include the :users relationship if you want it eagerly loaded.