First of all, I am still new to ROR and I am trying to come up with more efficient way to do a database query for my datatables.
My Model association
class Store < ActiveRecord::Base
has_many :surveys
has_many :customers
end
...
class Survey < ActiveRecord::Base
belongs_to :store
end
...
class Customer < ActiveRecord::Base
belongs_to :store
end
In my DataTables
<tbody>
<% #store.surveys.each do |survey| %>
<tr class="th-container table-fixer">
<td><%= find_customer_id(survey.email).nil? ? survey.first_name : link_to(survey.first_name, store_customer_path(#store, find_customer_id(survey.email))) %></td>
<td><%= get_ltv(survey) %></td>
</tr>
<% end %>
</tbody>
find_customer_id and get_ltv method as follows
def find_customer_id(customer_email)
BwCustomer.find_by(email: customer_email)
end
The problem with the code is that, currently I have over 1000 active record objects that I loop through, when find_customer_id method is hit it will find customer with the given email address and the query takes over 15 sec to process.
In my situation, what would be the best way to approach this?
solution that I have though about:
1. join the tables to so that I don't have to call another table
2. lazy load, only load the objects when needed
Some suggestion will be greatly appreciated
thank you
Your query by email ID should not take so much time.
Add index for the email column in Customers table (Refer this for adding indexes through Active record migrations - http://apidock.com/rails/v4.2.1/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index)
Your code shows calling the find_customer_id twice. Do that once so only 1 database query is fired
You need not write a wrapper method - Customer.find_by_email(customer_email) also works
To optimize further, you can collect all the customer IDs you need to check for existence in database in one loop, and fire a single database query:
Customer.where(email: [list of customer emails])
The main problem is that you're missing an association between customer and survey. You could make one by joining on the email
class Survey < ActiveRecord::Base
belongs_to :customer, primary_key: :email, foreign_key: :email
end
but this is a somewhat sketchy approach. Does you application know the ID of the customer when they fill a survey? Or can these surveys be filled by anyone and you make the link if someone claims to have the same email as a customer?
In any case you need both the email columns to be indexed and if you make an association between the two you will be able to write the following in your controller code.
#store = Store.includes(surveys: :customer).find(params[store_id])
This will make a database query which eager loads all the surveys and customers that you're about to display so that inside the loop you can use survey.customer without calling a new query for each row.
Related
I have a feeling this is a pretty basic question, but for some reason I'm stumped by it (Rails newbie) and can't seem to find the answer (which may be I'm not searching properly).
So I have a basic has_many :through relationship like this:
class User < ApplicationRecord
has_many :contacts, through :user_contacts
class Contact < ApplicationRecord
has_many :users, through :user_contacts
In users/show.html.erb I'm iterating through a single user's contacts, like:
<% #user.contacts.each do |c| %>
<%= c.name %>
<% end %>
Now inside of that each loop, I want to access the user_contact join model that's associated with the given user and contact in order to display the created_at timestamp that indicates when the user <--> contact relationship was made.
I know I could just do a UserContact.find call to look up the model in the database by the user_id and contact_id but somehow this feels superfluous. If I understand correctly how this works (it's entirely possible I don't) the user_contact model should have already been loaded when I loaded the given user and its contacts from the database already. I just don't know how to properly access the correct model. Can someone help with the correct syntax?
Actually the join model will not have been loaded yet: ActiveRecord takes the through specification to build its SQL JOIN statements for querying the correct Contact records but effectively will only instantiate those.
Assuming you have a UserContact model, you could do sth like this:
#user.user_contacts.includes(:contact).find_each do |uc|
# now you can access both join model and contact without additional queries to the DB
end
If you want to keep things readable without cluttering your code with uc.contact.something, you can set up delegations inside the UserContact model that delegate some properties to contact or user respectively. For example this
class UserContact < ActiveRecord::Base
belongs_to :user
belongs_to :contact
delegate :name, to: :contact, prefix: true
end
would allow you to write
uc.contact_name
First of all, the has_many :things, through: :other_things clause is going to look for the other_things relationship to find :things.
Think of it as a method call of sorts with magic built in to make it performant in SQL queries. So by using a through clause you're more or less doing something like:
def contacts
user_contacts.map { |user_contact| user_contact.contacts }.flatten
end
The context of the user_contacts is completely lost.
Since it looks like user_contacts is a one-to-one join. It would be easier to do something like this:
<% #user.user_contacts.each do |user_contact| %>
<%= user_contact.contact.name %>
<% end %>
Also since you're new to Rails it's worth mentioning that to load those records without an N+1 query you can do something like this in your controller:
#user = User.includes(user_contacts: [:contacts]).find(params[:id])
Use .joins and .select in this way:
#contacts = current_user.contacts.joins(user_contacts: :users).select('contacts.*, user_contacts.user_contact_attribute_name as user_contact_attribute_name')
Now, inside #contacts.each do |contact| loop, you can call contact.user_contact_attribute_name.
It looks weird because contact doesn't have that user_contact_attribute_name, only UserContact does, but the .select portion of the query will make that magically available to you on each contact instance.
The contacts.* portion is what tells the query to make all contact's attributes available as well.
I think this is a lot simpler than the title probably lets on. Here are my three models with the associations:
Update: associations were incorrect previously. Here are the corrected associations:
#app/models/call_service.category.rb
class CallServiceCategory < ActiveRecord::Base
has_many :call_services
end
#app/models/call_service.rb
class CallService < ActiveRecord::Base
belongs_to :call_service_category
has_many :calls
end
#app/models/call.rb
class Call < ActiveRecord::Base
belongs_to :call_service
end
So I have a group of call_ids for the calls I want:
#call_ids = [1,2,3,4]
Initial step which works:
What I want to do is grab only the calls with the ids specified in #call_ids. Then, I want to eager load only the associated call_services for those grabbed calls. The following does this perfectly:
#call_ids = [1,2,3,4]
#calls_by_service = CallService.includes(:calls).where("calls.id IN (?)", #call_ids).references(:calls)
This is great. Now I can iterate through only those selected calls' call_services, and I can even list all of those selected calls per service like so:
<% #calls_by_service.each do |call_service| %>
<p> <%= call_service.description %> </p>
<% call_service.calls.each do |call| %>
<%= call.name %><br>
<% end %>
<% end %>
What is great about this too is that #calls_by_service does not contain ALL of the call_services, but instead only those call_service records associated with the calls specified in #call_ids. Exactly what I want at this level.
One Level Deeper which is where I am having trouble:
This is great but I need to go one level deeper: I want to display only the associated call_service_categories for the associated call_services of those selected calls specified by #call_ids.
In other words: I want to grab only the calls with the ids specified in #call_ids. Then: I want to eager load only the associated call_services for those grabbed calls. Then: I want to eager load only the associated call_service_categories for those grabbed calls.
A visual of the structure is like this:
So I want to be able to iterate through those associated call_service_categories (ex: 'Emergency Relief', 'Employment'), and then iterate through the associated call_services of those calls specified in the #call_ids, and then display those calls per service.
I figured out one level (by call_service), now I just need to figure out one level deeper (by call_service_category).
In the rails guides, I attempted looking at the section on specifying conditions on eager loaded associations. I was not having success, but I think the answer is in that section.
Any help is much appreciated. Thanks!
One of the belongs_to associations (in CallService or Call) should be actually a has_one (one-to-one relationship – belongs_to on the one side and has_one on the other). Apart from that, you can try the following code to produce a chained query with left joins and retrieve fields from all 3 tables:
CallServiceCategory.includes(call_services: :calls)
.where(calls: {id: #call_ids})
.references(:call_services, :calls)
I've noticed that you have a through association on your CallServiceCategory model, but as there would be no :call_services in includes, you can't reference fields from CallService model in references (you can, but they just won't appear in the actual sql query).
<% #review.each do |review|%>
<% if review.host_id == #host.id>
<%= #user = User.find(review.user_id) %>
<% end %>
<% end %>
So I'm a bit confused. I have a few things going on here. I'm doing a loop through all reviews of hosts and then checking if the stored host.id value is equal to the active #host object's id that is passed from the controller. Problem is.. Now I need get the user object from the user ID stored in the review but, I'm unsure exactly how to do it. I can't do it from the controller as all this is done in the loop. As you can see I tried to do it with the code above but, I highly doubt I did it right. Please help me out on this. Thanks.
You should pre-load users with loading reviews, in controller. First, you should have belongs_to association, like this:
class Review < ActiveRecord::Base
belongs_to :user
# ...
end
then, in controller, you could use includes, this way:
#reviews = Review.includes(:user)
Now, for every review record in #reviews relation, to get associated user you can call user method, like this:
review.user
What's more, (and that's advantage of using includes) it doesn't fire new SQL query for every single review, so you avoid quite common N + 1 problem.
You can make a relationship in
class Review < ActiveRecord::Base
belongs_to :user
end
and then in view
review.user #gives you user
Put association in Review Model
class Review < ActiveRecord::Base
.
.
.
belongs_to :user
.
.
.
end
After putting association you can directly call association to find user object using Review object.
review.user
But this will raise N+1 query problem, so better user include user while finding review, It will execute only two queries one for finding reviews and another for finding users.
#reviews = Review.includes(:user)
Trying to teach myself Rails and have come across something that was easy in PHP but have got confused and stuck on this one area.
I have a form with a number of drop downs. I have managed to get the drop downs to display a value and store the id of that value into another table. Now I would like to view the new record but instead of displaying the ID that I saved I would like to collect the value from the original table and display that.
I hope that makes sense.
Below is my current code.
Finding Model
class Finding < ActiveRecord::Base
belongs_to :skill
Skill Model
class Skill < ActiveRecord::Base
attr_accessible :skill_desc, :skill_level
has_many :findings
show.html.erb
<td>Finding Skill</td>
<td><%= #finding.skill_id %> </td>
I guess my question is what do I need to change the #finding.skill_id too, to show the skill_desc field from the skills model?
<%= #finding.skill.skill_desc %>
#finding.skill returns the Skill object associated with that finding, upon which you can call skill_desc to retrieve said value.
#finding.skill.skill_desc
If you wanted to find the findings of a skill, you would need to use
#skill.findings to return an array of the findings objects associated with that skill object.
In sql, it might look something like this
SELECT * FROM findings WHERE ID EQ 23 JOIN skills ON findings.skill_id = Skills.id
I am using Ruby on Rails 3.2.2 and I have the following has_many :through association in order to "order articles in categories":
class Article < ActiveRecord::Base
has_many :category_associations # Association objects
has_many :associated_categories, :through => :category_associations # Associated objects
end
class CategoryAssociation < ActiveRecord::Base
acts_as_list :scope => 'category_id = #{category_id} AND creator_user_id = #{creator_user_id}'
belongs_to :associated_article
belongs_to :creator_user, :foreign_key => 'creator_user_id'
end
On retrieving associated_categories I would like to load category_associations objects created by a user (note: the creator user is identified by the creator_user_id column present in the category_associations database table) because I need to display position values (note: the position attribute, an Integer, is required by the act_as_list gem and it is a column present in the category_associations database table) "near" each article title.
Practically speaking, in my view I would like to make something like the following in a proper and performant way (note: It is assumed that each article in #articles is "category-associated" by a user - the user refers to the mentioned creator user of category_associations):
<% #articles.each do |article| %>
<%= link_to(article.title, article_path(article)) %> (<%= # Display the article position in the given category %>)
<% end %>
Probably, I should "create" and "handle" a custom data structure (or, maybe, I should make some else...), but I do not how to proceed to accomplish what I am looking for.
At this time I am thinking that the eager loading is a good approach for my case because I could avoid the N + 1 queries problem since I have to state further conditions on association objects in order to:
retrieve specific attribute values (in my case those refer to position values) of association objects created by a given user;
"relate" (in some way, so that position values are suitable for displaing) each of those specific attribute values to the corresponding associated object.
I think, you are looking for this
#articles = Article.includes(:associated_categories)
This will eager load all your articles including both of its associations (associated_categories, associated_categories). Thus, it will avoid N+1 problem and wont fire queries when you iterate over #articles and its associations in your view.