Rails: find_each(:include => :user ) never gets the association - ruby-on-rails-3

I have two models
class User
has_one :entry
end
class Entry
belongs_to: user
end
in my controller I use find_each to iterate over entries to email each of the users.
Entry.find_each(:include => :user, :conditions => {:approved => true}) do |entry|
UserMailer.send_competition_open_email(entry, entry.user)
end
entry.user is always nil. ":include => :user" never finds the user.
yet i can see in my SQL logs it tries to get it. But fails. Any ideas?
Entry Load (0.6ms) SELECT `entries`.* FROM `entries` WHERE `entries`.`approved` = 1 AND (`entries`.`id` >= 0) ORDER BY `entries`.`id` ASC LIMIT 1000
User Load (1.4ms) SELECT `users`.* FROM `users` WHERE (`users`.`id` IN (1,2,3))
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1

class Entry
belongs_to: user
end
Just a quick check that you noticed the colon is butting "belongs_to" and not "user"?

Related

Check if a record is present in a has-many association

I have a rails-api application in which users can follow other users.
To check if an user already follows another user, I need to include a query in the attributes and because of that, I have always a N+1 query problem.
Here is my code:
Index action in user controller:
def index
#users = ::User.all.paginate(page: params[:page])
end
The followers will always be included by a default_scope in the User model.
index.json.jbuilder:
json.partial! 'attributes', collection: #users, as: :user
_attributes.json.jbuilder:
json.extract! user, :id, :firstname, :lastname, :username, :follower_count
is_follower = user.follower.find_by(id: current_user.id).present? if current_user
json.following is_follower
And as a result:
User Load (0.1ms) SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = $1 [["followed_id", 14]]
Rendered v1/user/users/_attributes.json.jbuilder (1.3ms)
User Load (0.1ms) SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = $1 [["followed_id", 9]]
Rendered v1/user/users/_attributes.json.jbuilder (1.4ms)
User Load (0.1ms) SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = $1 [["followed_id", 13]]
Is there some workaround or is it somehow possible to generate a dynamic attribute in the SQL query which includes the boolean value if the user follows the other user?
Thank you very much in advance.
My first thought would be to eager load the followers using the .includes method when you get the list of users like this #users = ::User.all.includes(:followers).paginate(page: params[:page]). But perhaps, I'm not understanding your question correctly? Let me know if that works or if I should focus my answer on a different subject. Thanks!
EDIT: Correct answer from the comments below:
Perhaps you can try user.followers.include?(current_user) to make use of the pre-loaded followers association.

rails belongs_to sql statement using NULL id

When paginating through our Phrase table it takes very long to return the results.
In the sql logs we see many sql requests which don't make sense to us:
Phrase Load (7.4ms) SELECT "phrases".* FROM "phrases" WHERE "phrases"."id" IS NULL LIMIT 1
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
These add up significantly. Is there a way to prevent querying against null ids?
This is the underlying model:
class Phrase < ActiveRecord::Base
belongs_to :user
belongs_to :response, :class_name => "Phrase", :foreign_key => "next_id"
end
In our case the culprit turned out to be hidden in our own code, namely in a sunspot block, which got called on save/update:
searchable do
integer :previous_ids, :multiple => true do
previous.map { |previous| previous.id }
end

Rails: How to set up an IF condition with a JOIN in a has_many :through relationship

I have an application where users can customize a calendar and fill it with a given pool of events. A user can also overwrite a title for his own calendar by an alias. So I have the following has_many :through relation:
class Calendar < ActiveRecord::Base
has_many :event_aliases
has_many :events, :through => :event_aliases
end
class Event < ActiveRecord::Base
attr_accessible :title
has_many :event_aliases
has_many :calendars, :through => :event_aliases
end
class EventAliases < ActiveRecord::Base
attr_accessible :course_id, :calendar_id, :custom_name
belongs_to :event
belongs_to :calendar
end
No I want to deliver the calendar with the aliases. If an event has an alias (custom_name), it should be displayed. Otherwise the default event name (title) should be displayed.
Is there a way to easily set up a query that returns all events for the current calendar whether with a custom_name (if exists) or with the default title?
My current solution is to hardcode an if condition into the query which I would like to avoid.
title_column = "case when custom_name IS NOT NULL then custom_name else title end as title"
# assume we are given a calendar_id
Calendar.find(calendar_id).event_aliases.joins(:event).select(title_column, :event_id).each do |event_alias|
# do further stuff here
end
I also could fetch all event_aliases and run through each of them to get the default title if necessary.
# assume we are given a calendar_id
Calendar.find(calendar_id).event_aliases.each do |event_alias|
title = event_alias.custom_name
if title.nil?
title = Event.find(event_alias.event_id).title
# do further stuff here
end
But this one results in too many queries to me.
So is there any smarter way of accomplishing what I want? Maybe using named scopes or another fancy rails technique?
UPDATE
I ended up with making a "custom" select via the has_many :through relationship. So the only thing changes is the Calendar model:
class Calendar < ActiveRecord::Base
has_many :event_aliases
has_many :events, :through => :event_aliases,
:select => "event_aliases.custom_name as custom_name, events.*"
end
So accessing the custom_name / the title now happens a little like #Doon suggested:
Calendar.find(1).courses.each do |course|
title = course.custom_name || course.title
end
This creates only 2 queries instead of 3:
Calendar Load (0.6ms) SELECT `calendars`.* FROM `calendars` WHERE `calendars`.`id` = 1 LIMIT 1
Event Load (0.7ms) SELECT event_aliases.custom_name as custom_name, events.* FROM `events` INNER JOIN `event_aliases` ON `events`.`id` = `event_aliases`.`event_id` WHERE `event_aliases`.`calendar_id` = 1
what about using includes to grab the events at the same time as you pull the aliases.
Calendar.find(1).event_aliases.includes(:event).each do |e|
puts e.custom_name.blank? ? e.event.title : e.custom_name
end
the SQL Rails generates will look something like this:
Calendar Load (0.2ms) SELECT "calendars".* FROM "calendars" WHERE "calendars"."id" = ? LIMIT 1
EventAlias Load (0.2ms) SELECT "event_aliases".* FROM "event_aliases" WHERE "event_aliases"."calendar_id" = 1
Event Load (0.2ms) SELECT "events".* FROM "events" WHERE "events"."id" IN (1, 2)
also if you want to clean it up a bit you can add a virtual field to the EventAlias
class EventAlias < ActiveRecord::Base
def name
custom_name || self.event.title
end
end
As long as you use the includes, the queries will be be the same.

Limiting the SQL queries generated by Rails when accessing the data from multiple tables on one page

I have many items and many users in a database. A user can set a status to the various items. The three tables are (they have been simplified in this explanation) users, statuses, items. The models are shown below.
class Item < ActiveRecord::Base
has_many :statuses
end
class Status < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
class User < ActiveRecord::Base
has_many :statuses
end
The controller
class ItemController < ApplicationController
def index
#items = Item.all
end
end
The view (this has been simplified, in my code I actually generate a form for the status so the user can create/modify their status but in the example below I'm just making as if the status is being printed out as text):
<th>Item ID</th>
<th>Item Status</th>
...
<% #items.each do |item| %>
<td><%= item.id %></td>
<% status = item.statuses.where(:user_id => current_user.id) %>
<td><%= status.first.status %></td>
<% end %>
Here are the queries generated by Rails:
Item Load (0.3ms) SELECT `items`.* FROM `items` ORDER BY id asc
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 1) AND (`statuses`.`user_id` = 103)
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 2) AND (`statuses`.`user_id` = 103)
Status Load (0.1ms) SELECT `statuses`.* FROM `statuses` WHERE (`statuses`.item_id = 2) AND (`statuses`.`user_id` = 103) LIMIT 1
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 3) AND (`statuses`.`user_id` = 103)
All the items are selected in one SQL query which I think is good. Then it seems the SELECT COUNT (*) query is executed, if a status is found then that status is fetched using another SQL query, this happens for each item.
Guessing there is a much better way of going about this, when I have a few hundred items the number of SQL queries being carried out is huge! If anyone has any tips for how they would go about this I'd be interested to hear.
Thanks.
The solution is to include the statuses when you build the items.
#items = Item.includes(:statuses).all
And then use a select method to return the relevant status.
item.statuses.select {|s| s.user_id => current_user.id}
The rails magic sometimes results in these crazy queries, since item.statuses can be interpreted as either an array of status objects, or an ActiveRecord Relation.
When you call the where method on item.statuses, it defaults to ActiveRecord Relation behaviour, and builds a new SQL query. If you call select, item.statuses behaves as an array.
This is also the case for methods like sum. Very annoying!

Problems with :uniq => true/Distinct option in a has_many_through association w/ named scope (Rails)

See updates at bottom of question.
I had to make some tweaks to my app to add new functionality, and my changes seem to have broken the :uniq option that was previously working perfectly.
Here's the set up:
#User.rb
has_many :products, :through => :seasons, :uniq => true
has_many :varieties, :through => :seasons, :uniq => true
has_many :seasons
#product.rb
has_many :seasons
has_many :users, :through => :seasons, :uniq => true
has_many :varieties
#season.rb
belongs_to :product
belongs_to :variety
belongs_to :user
named_scope :by_product_name, :joins => :product, :order => 'products.name'
#variety.rb
belongs_to :product
has_many :seasons
has_many :users, :through => :seasons, :uniq => true
First I want to show you the previous version of the view that is now breaking, so that we have a baseline to compare. The view below is pulling up products and varieties that belong to the user. In both versions below, I've assigned the same products/varieties to the user so the logs will looking at the exact same use case.
#user/show
<% #user.products.each do |product| %>
<%= link_to product.name, product %>
<% #user.varieties.find_all_by_product_id(product.id).each do |variety| %>
<%=h variety.name.capitalize %></p>
<% end %>
<% end %>
This works. It displays only one of each product, and then displays each product's varieties. In the log below, product ID 1 has 3 associated varieties. And product ID 43 has none.
Here's the log output for the code above:
Product Load (11.3ms) SELECT DISTINCT `products`.* FROM `products` INNER JOIN `seasons` ON `products`.id = `seasons`.product_id WHERE ((`seasons`.user_id = 1)) ORDER BY name, products.name
Product Columns (1.8ms) SHOW FIELDS FROM `products`
Variety Columns (1.9ms) SHOW FIELDS FROM `varieties`
Variety Load (0.7ms) SELECT DISTINCT `varieties`.* FROM `varieties` INNER JOIN `seasons` ON `varieties`.id = `seasons`.variety_id WHERE (`varieties`.`product_id` = 1) AND ((`seasons`.user_id = 1)) ORDER BY name
Variety Load (0.5ms) SELECT DISTINCT `varieties`.* FROM `varieties` INNER JOIN `seasons` ON `varieties`.id = `seasons`.variety_id WHERE (`varieties`.`product_id` = 43) AND ((`seasons`.user_id = 1)) ORDER BY name
Ok, so everything above is the previous version which was working great. In the new version, I added some columns to the join table called seasons, and made a bunch of custom methods that query those columns. As a result, I made the following changes to the view code that you saw above so that I could access those methods on the seasons model:
<% #user.seasons.by_product_name.each do |season| %>
<%= link_to season.product.name, season.product %>
#Note: I couldn't get this loop to work at all, so I settled for the following:
#<% #user.varieties.find_all_by_product_id(product.id).each do |variety| %>
<%=h season.variety.name.capitalize %>
<%end%>
<%end%>
Here's the log output for that:
SQL (0.9ms) SELECT count(DISTINCT "products".id) AS count_products_id FROM "products" INNER JOIN "seasons" ON "products".id = "seasons".product_id WHERE (("seasons".user_id = 1))
Season Load (1.8ms) SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name
Product Load (0.7ms) SELECT * FROM "products" WHERE ("products"."id" = 43) ORDER BY products.name
CACHE (0.0ms) SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name
Product Load (0.4ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
Variety Load (0.4ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 2) ORDER BY name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
Variety Load (0.4ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 8) ORDER BY name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
Variety Load (0.4ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 7) ORDER BY name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 43) ORDER BY products.name
CACHE (0.0ms) SELECT count(DISTINCT "products".id) AS count_products_id FROM "products" INNER JOIN "seasons" ON "products".id = "seasons".product_id WHERE (("seasons".user_id = 1))
CACHE (0.0ms) SELECT "seasons".* FROM "seasons" INNER JOIN "products" ON "products".id = "seasons".product_id WHERE ("seasons".user_id = 1) AND ("seasons".user_id = 1) ORDER BY products.name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
CACHE (0.0ms) SELECT * FROM "products" WHERE ("products"."id" = 1) ORDER BY products.name
CACHE (0.0ms) SELECT * FROM "varieties" WHERE ("varieties"."id" = 8) ORDER BY name
I'm having two problems:
(1) The :uniq option is not working for products. Three distinct versions of the same product are displaying on the page.
(2) The :uniq option is not working for varieties. I don't have validation set up on this yet, and if the user enters the same variety twice, it does appear on the page. In the previous working version, this was not the case.
The result I need is that only one product for any given ID displays, and all varieties associated with that ID display along with such unique product.
One thing that sticks out to me is the sql call in the most recent log output. It's adding 'count' to the distinct call. I'm not sure why it's doing that or whether it might be an indication of an issue. I found this unresolved lighthouse ticket that seems like it could potentially be related, but I'm not sure if it's the same issue: https://rails.lighthouseapp.com/projects/8994/tickets/2189-count-breaks-sqlite-has_many-through-association-collection-with-named-scope
Update
I think the problem is that the named_scope is being called once for each season. There needs to be something in the named_scope that narrows the returned products by season id.
What's happening right now is:
user = get me user
seasons = get me user's seasons (say, there are 3 seasons for the user)
products = get me the products
products += get me the products
products += get me the products
Give me each of the products
So what's happening is not that uniq is breaking, but rather than there's no delimeter on the named scope. (I think).
I tried the following, but it throws this exception: odd number list for Hash
named_scope :by_product_name, lambda { |seasons| { season_ids = seasons.map { |season| season.id }; :joins => :product, :conditions => { :seasons { :id => season_id } } :order => 'products.name' } }
Ideas?
Update #2
Ok, now I'm thinking maybe it's not the named scoped at all.
In #user/show, I just changed the loop to bypass the named scope:
<% #user.seasons.each do |season| %>
<%= link_to season.product.name, season.product %>
#Note: I couldn't get this loop to work at all, so I settled for the following:
#<% #user.varieties.find_all_by_product_id(product.id).each do |variety| %>
<%=h season.variety.name.capitalize %>
<%end%>
<%end%>
The above doesn't use the named scope, but I'm still getting the same result. In other words, I'm still seeing all instances of each product, instead of just one.
The code above that creates the first loop is the same as my original code that I listed at the top of this question. The difference is that this code is looping through seasons to hit the products, whereas my original code looped through products. This difference is where the problem is hiding, but I don't know how to fix it.
Also, I mentioned in my original question that I couldn't get the varieties loop working either. You can see the line commented in the code directly above. When looping through the seasons, instead of products, when Rails hits that varieties loop, it throws a name error:
undefined local variable or method `product'
Seems like that might be another symptom of the same problem?
Any other ideas?
I believe the issue is the formatting of the lambda. I obviously can't run the SQL, but the following lambda DOES create an apporpriate hash:
lambda { |seasons| season_ids = seasons.map { |season| season.id }; { :joins => :product, :conditions => { :seasons => { :id => season_ids } }, :order => 'products.name' } }
The output of that call with two seasons with ids 1 and 2 is:
{:joins=>:product, :conditions=>{:seasons=>{:id=>[1, 2]}}, :order=>"products.name"}