Eager Loading with "has many through" -- do I need Arel? - ruby-on-rails

I have three tables: users, members, projects. The middle is a join table expressing a has-many-through between the other two tables; and it has some attributes of interest, including join_code and activated.
More expansively:
class User < ActiveRecord::Base
has_many :members
has_many :projects, :through => :members
end
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :project
# has a column called join_code
# has a column called activated
# Note that this class can be thought of as "membership"
end
class Project < ActiveRecord::Base
has_many :members
has_many :users, :through => :members
end
Goal: Given a particular user, I want a query that will get all the projects, and eager load only the member records that link those projects to the user.
So far I have this method in user.rb that does a query:
def live_projects
self.projects.order("projects.name").includes(:members).where(:members => {:join_code => nil, :activated => true})
end
But it's not enough. I'd like to then be able to do this in the view code:
<% current_user.live_projects.each do |project| %>
<li project_id="<%= project.id %>">
<% member = project.member %>
(Do something with that member record here)
<%= project.name %>
<% end %>
</li>
<% end %>
Here, normally, I'd have project.members, but in my context I'm only interested in that one member record that links back to the user.
Here is what I think the raw SQL should look like
select projects.*, members.*
from projects inner join members on projects.id = members.project_id
where members.user_id = X and members.join_code is null and members.activated = 't';
How to do that in Arel (or ActiveRecord)?

I may have something of an answer here, namely that the ActiveRecord code I wrote seems pretty reasonable. Again, here's that query:
def live_projects
self.projects.order("projects.name").includes(:members).where(:members => {:join_code => nil, :activated => true})
end
On a run through the UI with sample data it generates this output from Rails server:
Project Load (0.6ms) SELECT "projects".* FROM "projects" INNER JOIN "members" ON "projects".id = "members".project_id WHERE "members"."join_code" IS NULL AND "members"."activated" = 't' AND (("members".user_id = 3)) ORDER BY projects.name
Member Load (2.0ms) SELECT "members".* FROM "members" WHERE ("members".project_id IN (50,3,6,37,5,1))
Then later in the view code I can do this:
<% current_user.live_projects.each do |project| %>
<li project_id="<%= project.id %>" class="<%= 'active' if project == #project %>">
<% member = project.members.detect { |member| member.user_id == current_user.id } %>
(Do something with that member record here)
</li>
<% end %>
That expression to get the member record is pretty ugly in the view, but select is an Array method, not a query, and no extra DB hits other than the two shown above appear in the output from Rails server. Thus I guess my n+1 problem is solved.

Add an association called live_members on the Project class.
class Project < ActiveRecord::Base
has_many :live_members, :class_name => "Member",
:conditions => {:join_code => nil, :activated => true}
has_many :members
has_many :users, :through => :members
end
Add an association called live_projects on the User class.
class User < ActiveRecord::Base
has_many :members
has_many :projects, :through => :members
has_many :live_projects, :through => :members, :source => :project,
:include => :live_member, :order => "projects.name"
end
Now you can:
user.live_projects

It seems that you expect there to be at most one active member linking each user to a project. If this is the case the following should work:
In member.rb:
scope :live, where(:join_code => nil, :activated => true)
In user.rb:
def live_projects_with_members
members.live.includes(:project).group_by(&:project)
end
In your view:
<% current_user.live_projects_with_members.each do |project, members| %>
<% member = members.first %>
<li project_id="<%= project.id %>" class="<%= 'active' if project == #project %>">
(Do something with that member record here)
</li>
<% end %>
If you then want to add an extra join for your usage stats you can do this:
def live_projects_with_members
members.live.includes(:project, :stats).group_by(&:project)
end

Related

Rails + ActiveRecord - fighting with "N+1" issue

I have these models:
car:
class Car < ActiveRecord::Base
has_many :car_services, dependent: :destroy
has_many :services, through: :car_services
end
car_service
class CarService < ActiveRecord::Base
belongs_to :car
belongs_to :service
validates_uniqueness_of :service_id, scope: :car_id
end
truck
class Truck < ActiveRecord::Base
has_many :truck_services, dependent: :destroy
has_many :services, through: :truck_services
end
truck_service
class TruckService < ActiveRecord::Base
belongs_to :truck
belongs_to :service
end
service
class Service < ActiveRecord::Base
has_many :car_services
has_many :cars, through: :car_services
has_many :truck_services
has_many :trucks, through: :truck_services
end
In controllers' actions (there are separated actions for cars and for trucks) I query the models like this:
#cars = Car.find_by_sql("SELECT ...")
...
#trucks = Truck.find_by_sql("SELECT ...")
and then in the respective views:
<% #cars.each do |result| %>
<li data-services="<% result.services.each do |service| %><%= service.name %>,<% end %>">...</li>
...
<% end %>
This procedure works very well for cars, it's very fast.
Different action for trucks, but the same procedure:
<% #trucks.each do |result| %>
<li data-services="<% result.services.each do |service| %><%= service.name %>,<% end %>">...</li>
...
<% end %>
Now, this procedure is very slow. When I tried to debug why (because as I mentioned, the procedure is same for both models, but for cars it's working very fast even though that in the car model is 600k records and in the truck "only" 300k), I spotted in the console log following information:
...
Service Load (159.1ms) SELECT `services`.* FROM `services` INNER JOIN `truck_services` ON `services`.`id` = `truck_services`.`service_id` WHERE `truck_services`.`truck_id` = 35769
Service Load (166.8ms) SELECT `services`.* FROM `services` INNER JOIN `truck_services` ON `services`.`id` = `truck_services`.`service_id` WHERE `truck_services`.`truck_id` = 45681
Service Load (151.4ms) SELECT `services`.* FROM `services` INNER JOIN `truck_services` ON `services`.`id` = `truck_services`.`service_id` WHERE `truck_services`.`truck_id` = 50974
...
How is this possible? Why I don't see these lines also for cars and I see them for truck? Do I miss something? I was thinking about missing indexes in the MySQL tables, but there aren't used any.
I am fighting with this issue the whole Saturday, but I have no solution for this...
I would be very grateful for an advice how to fix this issue.
Thank you very much guys.
I will start with an advice: post your sql queries for others to understand exactly what you are doing and to be able to help you
To eliminate N+1 you should use includes or preload
In your controllers do this:
#cars = Car.includes(:services).all
respectively,
#trucks = Truck.includes(:services).all
You can see that I preferred a much cleaner syntax to find_by_sql.
When you run:
#cars = Car.includes(:services).all
this will trigger 3 queries:
SELECT "cars".* FROM "cars";
SELECT "car_services".* FROM "car_services" WHERE "car_services".car_id IN (?, ?, ? ...);
SELECT "services".* FROM "services" WHERE "services".car_service_id IN (?, ?, ? ...);
If you remove N+1 your page will load faster but you should check that all your keys have indexes defined, too.
The above its just an example. You should really post your complete queries for us to understand exactly what you are doing. And those models Car and Truck, they look very similar, maybe it will be a better solution to use Single Table Inheritance.
You could rewrite this:
<% #trucks.each do |result| %>
<li data-services="<% result.services.each do |service| %><%= service.name %>,<% end %>">...</li>
<% end %>
as this
<% #trucks.each do |truck| %>
<%= content_tag :li, '...', data: {services: truck.services.map(&:name).join(', ')} %>
<% end %>
Hope this will help you somehow.

PG error for SELECT DISTINCT, ORDER BY expressions must appear in select list when trying to view on Heroku

I am getting this follow error after I added more relationships to my project. On localhost, the page displays perfectly fine. However, I get an error when trying to view it on heroku.
ActiveRecord::StatementInvalid (PG::Error: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
Here is my traveldeal controller:
def show
#traveldeal = #commentable = Traveldeal.find(params[:id])
#comments = Comment.all
#title = #traveldeal.title
#eventdeals = Eventdeal.tagged_with(#traveldeal.location_tag_list, :any => true, :order => 'RANDOM()', :limit => 3)
end
traveldeal/show.html
<% unless #eventdeals.blank? %>
<h1>Events in the area:</h1>
<% #eventdeals.each do |eventdeal| %>
<%= link_to eventdeal do %>
<!-- content -->
<% end %>
<% end %>
<% end %>
Heroku logs
SELECT DISTINCT eventdeals.* FROM "eventdeals" JOIN taggings event_taggings_e9f0e2e ON event_taggings_e9f0e2e.taggable_id = eventdeals.id AND event_taggings_e9f0e2e.taggable_type = 'Eventdeal' WHERE (event_taggings_e9f0e2e.tag_id = 1 OR event_taggings_e9f0e2e.tag_id = 3 OR event_taggings_e9f0e2e.tag_id = 4 OR event_taggings_e9f0e2e.tag_id = 5) ORDER BY RANDOM()):
This code was working so that I only displayed 3 random eventdeals that matched location (through act-as-taggable-on) with a traveldeal.
However, after I added a relationship (related to eventdeals but not traveldeals), I started getting the pg error.
Here are the relationships that was added:
trip.rb
class Trip < ActiveRecord::Base
has_many :eventdealtrips, :dependent => :destroy
has_many :eventdeals, :through => :eventdealtrips
end
eventdeal.rb
class Eventdeal < ActiveRecord::Base
has_many :eventdealtrips
has_many :trips, :through => :eventdealtrips, :dependent => :destroy
end
eventdealtrip.rb
class Eventdealtrip < ActiveRecord::Base
belongs_to :eventdeal
belongs_to :trip
end
Any advice on how I can still get a random array of 3 eventdeals?
Thanks.
I had this issue and solved it by running order at the end of my daisy chain.

How to loop through all available arrays in Ruby code

I am trying to create a dashboard where users (User model) who has clicked "attending" (which is a flaggable) to an event (Event model) which is connected to a Collection (Collection model) will be able to see what are the Events they are going for.
My question however, is simply how to loop through all possible arrays in order for me to get all the associated IDs for the Events that the User has clicked "attending".
So for example, in my home page I want to display all possible events that the user is attending:
user_statistics.html.erb
<div class="span3 events">
<h3>Events</h3>
<% if #events.empty? %>
<p>You are currently not attending any events.</p>
<% else %>
<p>You are attending: <b><%= pluralize(#events.count, "event") %></b></p>
<p>Event 1: <%= #event1_name %> on Date: <%= #event1.date %> at Time:<%= #event1.time %></p>
<% end %>
</div>
pages_controller.rb
def home
#title = "Title"
#user = current_user
if current_user
#post = Post.new
#feed_items = current_user.feed
#user_following = #user.following
#user_followers = #user.followers
#events = #user.flaggings.with_flag(:attending)
#event1 = Event.find(#events[0].flaggable_id)
#event1_name = Collection.find(#event1.collection_id).name
end
end
I have set the #event1 array to 0 to access the first flag for 'attending', and then get the flaggable_id so I have the id to call up the Collection.
My issue is that if I have several events, how do I go about looping through to all the arrays to ensure I can pull out all the Collections?
For the first User who has clicked "attending" for 2 events, this is the data:
in IRB, for User.first who is attending 2 events
User Load (0.3ms) SELECT "users".* FROM "users" LIMIT 1
MakeFlaggable::Flagging Load (0.4ms) SELECT "flaggings".* FROM "flaggings" WHERE
"flaggings"."flagger_id" = 1 AND "flaggings"."flagger_type" = 'User' AND
"flaggings"."flag" = 'attending'
[#<MakeFlaggable::Flagging id: 16, flaggable_type: "Event", flaggable_id: 3,
flagger_type: "User", flagger_id: 1, flag: "attending", created_at: "2012-02-20 09:26:36",
updated_at: "2012-02-20 09:26:36">, #<MakeFlaggable::Flagging id: 18, flaggable_type:
"Event", flaggable_id: 4, flagger_type: "User", flagger_id: 1, flag: "attending",
created_at: "2012-02-20 10:38:00", updated_at: "2012-02-20 10:38:00">]
You can see that the user has flagged 'attending' for 2 events, which are stored in arrays. Hence if I have 2 events, I would ideally want to loop through such that I have 2 arrays.
I find it quite confusing to implement this... any help would be much appreciated!
user.rb partial code
Class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :user_bio,
:shop, :cover_photo, :avatar, :remote_image_url
has_secure_password
mount_uploader :cover_photo, ImageUploader
mount_uploader :avatar, ImageUploader
make_flagger
scope :shop, where(shop: true)
has_many :posts, dependent: :destroy
has_many :relationships, dependent: :destroy,
foreign_key: "follower_id"
has_many :reverse_relationships, dependent: :destroy,
foreign_key: "followed_id",
class_name: "Relationship"
has_many :following, through: :relationships, source: :followed
has_many :followers, through: :reverse_relationships, source: :follower
has_many :collections, dependent: :destroy
...
end
collections.rb partial code
class Collection < ActiveRecord::Base
attr_accessible :name, :description, :image, :remote_image_url
belongs_to :user
has_many :products, dependent: :destroy
has_many :events, dependent: :destroy
mount_uploader :image, ImageUploader
make_flaggable :like
...
end
events.rb partial code
class Event < ActiveRecord::Base
attr_accessible :date, :time, :description
belongs_to :collections
make_flaggable :attending
...
end
It's not 100% clear from your post, but it sounds like you need a named scope for Events where the attending flag is set and a has_many :through association to let the User have Events. With those two bits together, you could do something like:
User.first.events.attending
and AREL will take care of wiring that all up into a nice fast query for you.
Is flaggable a gem or have you written it as a polymorphic class?
This line is kind of nasty (sorry):
#events = #user.flaggings.with_flag(:attending)
The #events variable isn't holding events - it's holding a collection of flaggings. As a naming convention, this is obviously bad.
Also, this line is probably redundant if you've set up (or used a gem) for the flaggable polymorphic relationship:
#event1 = Event.find(#events[0].flaggable_id)
#This could be rewritten as:
#event1 = #events.first.flaggable
Or even nicer, combine the two previous lines into:
##events = #user.flaggings.with_flag(:attending)
##event1 = Event.find(#events[0].flaggable_id)
#becomes:
flaggings = #user.flaggings.with_flag(:attending)
#events = flaggings.map(&:flaggable)
#jxpx777 makes a very good point about named scope and hmt associations. This is probably the way forward. You could do something like this:
Class User < AR...
has_many :attendances, :class => 'Flagging', :conditions => {:flag => 'attending'} #You might need to add :type => "Event" if you use flaggings elsewhere...
has_many :events_attending, :class => 'Event', :through => :attendances, :source => :flaggable_id #You'll probably have to change a lot of these variable names - I can't see the rest of your source code...
That will then give you ability to just do:
#events = #user.events_attending.include(:collection)
Caveat - none of this code is tested - it's all off the top of my head, but it should point you in the right direction at least
Okay, I managed to solve my own problem albeit in a very dirty manner...
pages_controller.rb
def home
#title = "Simplifying and socializing online shopping - Ruuva"
#user = current_user
if current_user
#post = Post.new
#feed_items = current_user.feed
#user_following = #user.following
#user_followers = #user.followers
#events_attending = #user.flaggings.with_flag(:attending)
end
end
_user_statistics.html.erb partial
<h3>Events</h3>
<% if #events_attending.empty? %>
<p>You are currently not attending any events.</p>
<% else %>
<p>You are attending these events:</p>
<ol>
<% #events_attending.each do |e| %>
<% unless Event.find_by_id(e.flaggable_id).nil? %>
<li>
Collection: <%= link_to Collection.find(Event.find(e.flaggable_id).collection_id).name, event_path(e.flaggable_id) %>
<br>happening on: <%= Event.find(e.flaggable_id).date %> at <%= Event.find(e.flaggable_id).time %>
</li>
<% end %>
<% end %>
</ol>
<% end %>
I'm pretty sure the code is very bad, and I DO used several 'shortcuts' issues that will bite me back in the future... but an mvp is an mvp. Thanks guys for the help, though! :)

Find items with belongs_to associations in Rails?

I have a model called Kase each "Case" is assigned to a contact person via the following code:
class Kase < ActiveRecord::Base
validates_presence_of :jobno
has_many :notes, :order => "created_at DESC"
belongs_to :company # foreign key: company_id
belongs_to :person # foreign key in join table
belongs_to :surveyor,
:class_name => "Company",
:foreign_key => "appointedsurveyor_id"
belongs_to :surveyorperson,
:class_name => "Person",
:foreign_key => "surveyorperson_id"
I was wondering if it is possible to list on the contacts page all of the kases that that person is associated with.
I assume I need to use the find command within the Person model? Maybe something like the following?
def index
#kases = Person.Kase.find(:person_id)
or am I completely misunderstanding everything again?
Thanks,
Danny
EDIT:
If I use:
#kases= #person.kases
I can successfully do the following:
<% if #person.kases.empty? %>
No Cases Found
<% end %>
<% if #person.kases %>
This person has a case assigned to them
<% end %>
but how do I output the "jobref" field from the kase table for each record found?
Maybe following will work:
#person.kase.conditions({:person_id => some_id})
where some_id is an integer value.
Edit
you have association so you can use directly as follows:
#kases= #person.kases
in your show.rhtml you can use instance variable directly in your .rhtml
also #kases is an array so you have to iterate over it.
<% if #kases.blank? %>
No Kase found.
<% else %>
<% for kase in #kases %>
<%=h kase.jobref %>
<% end %>
<% end %>
If your person model has the association has_many :kases then, you can get all the cases that belongs to a person using this
#kases = Person.find(person_id).kases
Assuming person_id has the id of the person that you want to see the cases for.
You would probably want something like
has_many :kases
in your Person model, which lets you do #kases = Person.find(person_id).kases
as well as everything else that has_many enables.
An alternate method would be to go through Kase directly:
#kases = Kase.find_all_by_person_id(person_id)

Using has many :through

Using has_many => through association.
Here is what i have.
:planning model
has_many :acttypes
has_many :actcategories
has_many :acts, :through => :actcategories
:acts model
belongs_to :acttype
has_many :actcategories
has_many :plannings, :through => :actcategories
:actcategories model
named_scope :theacts, lambda { |my_id|
{:conditions => ['planning_id = ?', my_id] }}
belongs_to :act
belongs_to :planning
:acttype model
has_many :acts
My Problem Starts here. I need to show all Acts by each Act Type from Plannings that is part of the actcategories association
Right now i am getting all the acts and missing the actcategories association.
Planning Controller
def show
#planning = Planning.find(params[:id])
#acttypes = Acttype.find(:all, :include => :acts)
#acts = Actcategory.theacts(#planning)
end
Planning Show View
<% #acttypes.each do |acttype|%>
<%= acttype.name %>
<% #acts.each do |acts| %>
<li><%= link_to acts.act.name, myacts_path(acts.act, :planning => #planning.id) %></li>
<% end %>
<% end -%>
Thanks for any help.
I think the key thing you're missing is that finders and named scopes only return the Class that they're called on.
#acts = Actcategory.theacts(#planning)
#acts is all the Actcategories where actcategories.planning_id = #planning.id. They don't necessarily have the required act type.
Really, what I think you're looking for is this named scope:
class Act < ActiveRecord::Base
named_scope :with_planning, lambda do |planning_id|
{ :joins => :actcategories,
:conditions => {:actcategories => {:planning_id => planning_id}}
}
...
end
Which limits acts to those associated with the given planning. This can be called on an association to limit the linked acts to those associated with a specific planning.
Example: #acts contains acts of acttype, x, that are associated with planning, y.
#acts = Acttype.find(x).acts.with_planning(y)
With this named scope this code should accomplish what you were aiming for.
controller:
def show
#planning = Planning.find(params[:id])
#acttypes = Acttype.find(:all, :include => :acts)
end
view:
<% #acttypes.each do |acttype| %>
<h2> <%= acttype.name %><h2>
<% acttype.acts.with_planning(#planning) do |act| %>
This act belongs to acttype <%= acttype.name%> and
is associated to <%=#planning.name%> through
actcatgetories: <%=act.name%>
<%end%>
<%end%>

Resources