I'm using Rails 5. I have this in my controller model for loading a certain model subject to criteria ...
@results = MyObjectTime.joins(:my_object,
"LEFT JOIN user_my_object_time_matches on my_object_times.id = user_my_object_time_matches.my_object_time_id #{additional_left_join_clause}")
.where( search_criteria.join(' and '), *search_values )
.limit(1000)
.order("my_objects.day DESC, my_objects.name")
.paginate(:page => params[:page])
My question is, how do I rewrite the above so that the @results array contains both the "MyObjectTime" and any potential "UserMyObjectTimeMatch" it is linked to?
What is ActiveRecord? ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.
ruby rails. Rails has a great, expressive term called pluck that allows you to grab a subset of data from a record. You can use this on ActiveRecord models to return one (or a few) columns. But you can also use the same method on regular old Enumerables to pull out all values that respond to a given key.
The Arel::Table object acts like a hash which contains each column on the table. The columns given by Arel are a type of Node , which means it has several methods available on it to construct queries. You can find a list of most of the methods available on Node s in the file predications.
There are actually three ways to do eager loading in Rails. use preload() to fetch the data with a separate db query (just one though, avoiding N+1 queries) use eager_load() to fetch data with one large query and an outer join. use includes() to dynamically find the best solution and preload or eager load the data.
You can avoid most n+1 queries in rails by simply eager loading associations. Eager loading allows you to load all of your associations (parent and children) once instead of n+1 times (which often happens with lazy loading, rails' default). As seen above, . includes allows nested association eager loading!
Active Record insulates you from the need to use SQL in most cases. Active Record will perform queries on the database for you and is compatible with most database systems, including MySQL, MariaDB, PostgreSQL, and SQLite.
I would recommend to search for an alternative, but with the information that you provided and looking to avoid the potential 10001 queries mentioned in your comment, if you have the has_many 'user_my_object_time_matches' setup you could do:
@results = MyObjectTime.joins(:my_object,
"LEFT JOIN user_my_object_time_matches on my_object_times.id = user_my_object_time_matches.my_object_time_id #{additional_left_join_clause}")
.where( search_criteria.join(' and '), *search_values )
.limit(1000)
.order("my_objects.day DESC, my_objects.name")
.paginate(:page => params[:page])
.includes(:user_my_object_time_matches)
.map{|t| [t, t.user_my_object_time_matches]}
You cannot. At least not using ActiveRecord or Rails' default interface. ActiveRecord query methods are designed in such a way that they will only return the objects of calling Model.
For example, If you query like
MyObjectTime.joins(:time).where(parent_id: 5)
it'll return the objects for MyObjectTime
only. However, because of the join
, the records from association time
are might also be fetched, only not returned. So, you can take advantage of it. Especially when you use includes
in place of joins
, the associated models are fetched and you can use them via reference of the associating record/object.
Explanation to build a result pair
This can be done easily by creating a hash with required results.
For example, consider a model Mark
that has answer_sheet
association.
You can fetch the marks with :answer_sheet
using includes
this way. I'm fetching 20 in the example.
marks = Mark.limit(20).includes(:answer_sheet);
This fetches answer_sheet which can be retrieved via mark, So, build a hash this way
h = {}
marks.each do |mark|
h[mark.id] = {}
h[mark.id][:mark] = mark
h[mark.id][:answer_sheet] = mark.answer_sheet
end
Now, your hash has the mark
and answer_sheet
object ready via mark.id key.
This will only execute at most two queries at first fetch and the iteration doesn't won't trigger any further queries. In my system the only two required queries are (with using includes
)
SELECT "marks".* FROM "marks" LIMIT 20
AnswerSheet Load (0.9ms) SELECT "answer_sheets".* FROM "answer_sheets" WHERE "answer_sheets"."mark_id" IN (877, 3035, 3036, 878, 879, 880, 881, 561, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893)
You can even use the mark object itself as the key. Then the building process become more simple
h = {}
marks.each do |mark|
h[mark] = mark.answer_sheet
end
Now, whenever you wanted to access the answer_sheet
associated with mark
, you'll just need to use h[mark]
to fetch it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With