Consider a typical relational structure:
Answer has many comments. comments belongs to a user, and have many likers
answers.to_json(:include => {:comments => {:include => :user, :likers}})
If i call to_json , it will perform the database retrievals one by one for individual user in individual comments.
The much more efficient solution would be to put the required ids for each type in an array, do 3 database calls to retrieve them, put it into a hash, and construct the json according to the hash.
This seems like a pretty common use case. I am thinking of writing my own recursive function for this, but if anyone can point me to something that has already been done, that will be great!
** i am using mongoid / mongodb, i am not sure if it works the same way in active record.
May be you can try mongoid eager loading, then you can query mongo just like AR eager loading
Answer.includes(:comments, :likers)
The patch is still not included in the mongoid master branch, but you can download it as a independednt gem from here
Read this pull request for more info
Related
For performance reason, I use as often as possible the only() keyword when writing up a mongoid query in order to specify the fields I want to load.
The usual suspect, is for instance when I want a user's email of all my admins only for display purposes.
I would write:
User.where(:groups => :admins).only(:email).each do |u|
puts u.email
end
I do this because my user model are quite full of a lot of data that I can gladly ignore when listing a bunch of emails.
However, now let imagine, that my users are referenced via a Project model, so that for each project I can do: project.user. Thanks to mongoid's lazy loading, my object user will only get instantiated (and queried from the DB) when I call upon the reference.
But what if I want to list all the email of the owner of all admin project for instance ?
I would write this:
Project.where(:admin_type => true).each do |p|
puts p.user.email
end
The major problem here is that doing this, I load the entire user object for each projects, and if there are lots of project matching the query that could get pretty heavy. So how do I load only the emails ?
I could do this:
User.where(:_id => p.user_id).only(:email).first.email
But this obviously defeat the purpose of the nice syntax of simply doing:
p.user.email
I wish I could write something like: p.user.only(:email).email, but I can't. Any ideas ?
Alex
Answer from creator of Mongoid. It's not possible yet. It's been added as feature request.
I think you need to denormalize here. First of all, read A Note on Denormalization.
You can implement denormalization by self using mongoid events or use great mongoid_denormalize gem. It pretty straight and after implementing it you could use p.user_email or something in your queries.
I've got three nested models: user has many plates and plate has many fruits. I also have a current_user helper method that runs in the before filter to provide authentication. So when I get to my controller, I already have my user object. How can I load all the user's plates and fruits at once?
In other words, I'd like to do something like:
#plates = current_user.plates(include: :fruits)
How can I achieve this?
I'm using Rails 3.1.3.
You will probably want to use the provided #includes method on your relation. DO NOT USE #all unless you intend to immediately work through the records, it will immediately defeat many forms of caching.
Perhaps something like: #plates = current_user.plates.includes(:fruits)
Unfortunately, there are portions of the Rails API that are not as well documented as they should be. I would recommend checking out the following resources if you have any further questions about the Rails query interface:
Query Interface Guide
ActiveRecord::Relation Walkthrough (screencast)
The query interface is possibly the most difficult part of the Rails stack to keep up with, especially with the changes made with Rails 3.0 and 3.1.
You can do
ActiveRecord::Associations::Preloader.new([current_user], :plates => :fruit).run
To eager load associations after current_user was loased. The second argument can be anything you would normally pass to includes: a symbol, an array of symbols, a hash etc
#plates = current_user.plates.all(:include => :fruits)
should do it.
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.
I'm designing a ruby on rails app for a pharmacy, and one of the features is that there are stores who have pharmacists who work there. In addition, there are pharmacists, who can work at many stores. This sounds like a job for HABTM, right? Well, being the novice I am, I manually designed a workaround (because I never heard of HABTM - I basically taught myself rails and never got to some of the more advanced relationships). Right now, when a pharmacist is saved, there's a couple of lines in the create and update action of the pharmacists controller that turns the stores that they work at into a string, with each store_id separated by a comma. Then, when a store is displayed, it does a MYSQL request by
#pharmacists = Pharmacist.find :all, :conditions => "stores REGEXP '#{#store.id}'"
Would moving this system over to a rails based HABTM system be more efficient? Of course it would require less code in the end, but would it be worth it? In other words, what benefits, other than less code, would I get from moving this association to be managed by rails?
The benefit is that you will be using the right tool for the job! The whole point of using a framework such as Rails is that it helps you solve common problems without having to re-invent the wheel, which is what you've done here. By using associations you'll also be using a relational database properly and can take advantage of benefits like foreign key indexing, which will be faster than string manipulation.
You should use a has_and_belongs_to_many relationship unless you need to store extra attributes on the join model (for example the date a pharmacist started working at a store) in which case use has_many :through.
Using Rails associations will give you all the convenient methods that Rails provides, such as these:
# Find the stores the first pharmacist works at
#stores = Pharmacist.first.stores
# Find the pharmacists who work at a store
#pharmacists = Store.find_by_name('A Store').pharmacists
A Guide to ActiveRecord Associations
I have a number of custom find_by_sql queries in Rails. I would like to use eager loading with them but there doesn't seem to be a good way to do this.
I have seen the eager_custom.rb file floating around and it doesn't seem to work with Rails now. It appear Rails does eager loading differently now, using 2 queries (the regular query plus a query where the 'id IN' the ids from the first query), instead of the single join query used in the past.
My question is if I do a custom SQL query, then do 'id IN' query, is there a way to add back associated objects into the initial query results?
For example I have topics loaded with find_by_sql, then I find topic images where the topic id is in the topics ids, is there a way to add the images manually back to the topics?
Thanks
As you noticed, in Rails 2.1 a new kind of eager/pre-loading was introduced which uses multiple queries with id IN (...). This method is usually faster, especially when there are multiple associations being pre-loaded. You can use this functionality manually with find_by_sql by using the preload_associations class method inherited from ActiveRecord (not recommended). For example:
class Person
def self.find_a_special_group
people = find_by_sql("...")
preload_associations(people, [:jobs, :addresses])
return people
end
end
The preload_associations method is protected, so you must call it from within the class, and it takes (1) an array of objects, (2) an array, hash, or symbol of associations (same format as find's :include option), and (3) an options hash. See the documentation for the ActiveRecord::AssociationPreload::ClassMethods module for more details.
However, having said all of that, this technique is certainly undesirable as the Rails documentation discourages programmers from using preload_associations directly. Are you sure you have to use find_by_sql? Are you sure you know all of the options find takes? (:select, :from, :joins, :group, :having, etc) I'm not saying you don't need find_by_sql, but it might be worth a few minutes to make sure.