Eager Loading Nested Association - ruby-on-rails

I have the following models:
class Session < ActiveRecord::Base
belongs_to :game
has_many :session_players
has_many :players, through: :session_players
end
class SessionPlayer < ActiveRecord::Base
belongs_to :player
belongs_to :session
end
class Player < ActiveRecord::Base
has_many :session_players
has_many :sessions, through: :session_player
end
In the erb file I am trying to loop over all sessions, displaying some info about the session and displaying nested data about each player for that session.
The relevant code in the erb file is:
<% #sessions.each do |session| %>
<tr>
<td><%= session.name %></td>
<td>
<table>
<% session.session_players.each do |session_player| %>
<!-- problem line below -->
<tr><td><%= session_player.player.name %> (<%= session_player.placing %>)</td></tr>
<% end %>
</table>
</td>
</tr>
<% end %>
The problem is that I cannot pull data off of both the player and the session_player object due to a null exception when getting the name value off of the session_player.player.name call. The call to the session_player.placing succeeds.
I believe this to be a lazy loading issue as I can retrieve the value of session_player.player.name while debugging the code, just not when running it. I tried eager loading the data by trying various includes combinations in the controller but it did not make a difference:
#sessions = Session.includes(:session_players => :player).all

How about this:
#sessions = Session.joins(:session_players)
Or to avoid N+1
#sessions = Session.includes(:session_players)
You can use try to define when there is no relation
<% session.session_players.each do |session_player| %>
<!-- problem line below -->
<tr><td><%= session_player.try(:player).try(:name) %> (<%= session_player.placing %>)</td></tr>
<% end %>

The code was correct, but the data I was populating it with was not. One of the values was null later in the list of data. This was not being caught until the data was being accessed during template rendering. Eager loading had no effect on when the data when the exception occurred.

Related

Return Attribute of Class Instance

Basically I have a table output on a page, base view. The headers (milestones) are dynamically integrated by passing the Milestones class and iterating through each with an .each do. The table rows are then initially generated by doing the same thing, pulling in client, trade, and units, into the first three columns. The rest of the columns should be dates (class entry attribute due) that are calculated by a lookup of sorts using the client and trade attributes accessible by the loop, and the milestone ID accessible by an array created when dynamically generating them for the headers. This all works correctly as it should, and the table generates fine.. however the dates are output as #<Entry:0x000000056faa90> and so on.
Now I have come across this problem before, and basically the fix was to add a class definition and tell it to return what I assume to be the attribute of that instance. For example:
class Client < ActiveRecord::Base
validates :id, presence:true
validates :client, presence:true
def name
return self.client
end
end
And this works great, but only for how I access the client. Unfortunately I am accessing entry.due differently. Here are my relevant bits of code.
base_controller.rb
class BaseController < ApplicationController
def index
#trades = Trade.all
#milestones = Milestone.all
##entries = Entry.all #Didn't seem relevant to how I am trying to access the information.
end
end
entry.rb
class Entry < ActiveRecord::Base
belongs_to :client
belongs_to :trade
belongs_to :milestone
validates :client, presence:true
validates :trade, presence:true
validates :milestone, presence:true
validates :due, presence:true
# Some of my trial and error; all to no avail.
def due
return self.due
end
def self.due
return self.due
end
def pickDue(c,t,m)
ret = self.select("date_format(due, '%m/%d/%Y')").where("client_id=? AND trade_id=? AND milestone_id=?", c, t, m).first
return ret
end
end
base > index.html.erb
<table>
<thead>
<tr class="sort">
<th>Client</th>
<th>Trade</th>
<th>Units</th>
<%
ms = []
#milestones.each do |milestone|
ms.push(milestone)
%>
<th><%= milestone.name %></th>
<% end %>
</tr>
</thead>
<tbody>
<% #trades.order("entered").last(10).each do |trade| %>
<tr>
<td><%= trade.client.name %></td>
<td><%= trade.name %></td>
<td><%= trade.units %></td>
<% ms.each do |msr| %>
<td>
<%= Entry.select("date_format(due, '%m/%d/%Y')").where("client_id=? AND trade_id=? AND milestone_id=?", trade.client, trade, msr).first %>
<%#= Entry.pickDue(trade.client, trade, msr) %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
With this approach, I can load the page error-free, but instead of showing dates, I am shown objects like #<Entry:0x000000056faa90>.
If I add .due to the end of the selector:
<%= Entry.select("date_format(due, '%m/%d/%Y')").where("client_id=? AND trade_id=? AND milestone_id=?", trade.client, trade, msr).first.due %>
# undefined method `due' for nil:NilClass
If I add .due after anything else:
<%= Entry.due.select("date_format(due, '%m/%d/%Y')").where("client_id=? AND trade_id=? AND milestone_id=?", trade.client, trade, msr).first %>
<%= Entry.select("date_format(due, '%m/%d/%Y')").due.where("client_id=? AND trade_id=? AND milestone_id=?", trade.client, trade, msr).first %>
<%= Entry.select("date_format(due, '%m/%d/%Y')").where("client_id=? AND trade_id=? AND milestone_id=?", trade.client, trade, msr).due.first %>
# stack level too deep
I have tried dozens of different methods acquired through several days of looking into this with no luck. Besides the current situation where it outputs objects instead of the object attribute, the next closest I've come I think is when I got an error saying it could not find the due method for an ActiveRecord object, but I don't remember how got to that error.
I would really appreciate any input.
If I understand correctly, I would get rid of that trial and error code in your model, and then in your view you can just call this:
Entry.where(client: trade.client, milestone: msr, trade: trade).pluck(:due)
This goes inside your milestone loop in your view, like this:
<% ms.each do |msr| %>
<td>
<%= Entry.where(client: trade.client, milestone: msr, trade: trade).pluck(:due).first %>
</td>
<% end %>
Pluck returns an array of values from the columns you specify (see the relevant section of the RailsGuide, so note that the .first method is the Array#first method, not the ActiveQuery#first method. You could even make a query that passes in all of the milestones at once instead of running a query for each milestone, thereby preventing an N+1 query situation.
That would be:
<% Entry.where(client: trade.client, trade: trade, milestone: ms).pluck(:due).each do |entry_due_date| %>
<td>
<%= entry_due_date %>
</td>
<% end %>
Lastly, while I gave you the code for the SQL query in the view, it is generally considered a bad practice in Rails to write SQL queries in your view, and you should probably make this into a method in your Entry model.
I think you might be looking for attributes method.
entry = Entry.new
entry.attributes #=> returns a hash of key-value attribute pairs

Rails display table data

I have developed rails app with three classes.
class Location < ActiveRecord::Base
has_many :observations
end
class Observation < ActiveRecord::Base
belongs_to :location
has_one :rainfall
has_one :temperature
has_one :winddirection
has_one :windspeed
has_one :dewpoint
end
class Rainfall < ActiveRecord::Base
belongs_to :observation
end
Railfall is related to Observation through an observation_id and Observation is related to Location through a location_id.
If i go to console and type something like:
Location.all.first.observations.first.rainfall.value
it returns me the data in value column of rainfall
However when i want to combine all the info in rails in a table and in my view/location/index.html.erb i put:
<tbody>
<% #locations.each do |location| %>
<tr>
<td><%= location.name %></td>
<% unless #observations = nil %>
<% location.observations.each do |obs| %>
<td><%= obs.name %></td>
<% unless #rainfalls = nil %>
<td><%= obs.rainfalls.value %></td>
<% end %>
<% end %>
I get an error:
undefined method `rainfalls' for Observation:0x00000004219068>
I have spent the last 5 hours trying just about everything i can and am getting a tad frustrated....any ideas?
Note i have a locations controller but observation and rainfall were generated as models, so do not. Am thinking i need to add something to my location controller, just can't work out what, and I am unsure why it returns the correct data in console, just no in app.
Observation.has_one :rainfall, so it should be:
<%= obs.rainfall.value %>
You have one to one relationship, not has_many. Replace
<%= obs.rainfalls.value %>
by
<%= obs.rainfall.value %>

Issue ordering array in the view

I'm trying to order a column inside array but is not ordering is showing values without ordering by lastname ASC.
Here is my table:
|products|
|id| |money| |client_id|
1 1000 1
2 2000 4
3 2000 7
4 2000 5
5 2000 6
6 2000 3
7 2000 2
|customers|
|id| |name| |lastname1| |lastname2|
1 Lionel Messi Bueno
2 Cristiano Ronaldo Tiger
3 Cecs Fabregas Midtlon
4 Andres Iniesta Blanco
5 Neymar Dos Santos Junior
Here is my controller
class ProductController < ApplicationController
def index
#products= Product.all
end
end
Here is the model
class Client < ActiveRecord::Base
has_many :products
def str_name
return lastname1.to_s+" "+lastname2.to_s+" "+name.to_s
end
end
class Product < ActiveRecord::Base
belongs_to :client
end
Here is my view
<table>
<tr>
<td>Money</td>
<td>Client</td>
</tr>
<% #products.each do |p| %>
<tr>
<td><%= p.money %></td>
<td><%= p.str_name %></td>
<% end %>
</tr>
</table>
I tried but is not ordering by lastname1 asc:
<% #products.each do |p| %>
<tr>
<td><%= p.money %></td>
<td><% #a= p.client(:order=>"lastname1 ASC")%> <%= #a.str_name %></td>
<% end %>
The log is:
SELECT * FROM `clients` WHERE (`clients`.`id` = 1)
SELECT * FROM `clients` WHERE (`clients`.`id` = 2)
SELECT * FROM `clients` WHERE (`clients`.`id` = 3)
SELECT * FROM `clients` WHERE (`clients`.`id` = 4)
And should be like this:
SELECT * FROM `clients` order by last_name1 ASC
Please somebody can help me?
<% #a= p.client(:order=>"lastname1 ASC")%> <%= #a.str_name %></td>
The line above does technically nothing. When you are already in the loop and you ask for the client of the current product, how many is it going to return? Only one, right ? Right, if you have a belongs_to on the other side (which I hope you do, this answer relies on it). So you're actually "sorting" a set of one element every iteration. And that's why your Select is ignoring the "order by". What you should be doing though, is to get a list of all the products ordered by their respective clients' "lastname1". This should not be done in a view like you're trying to do, but in your controller. In your controller:
#In Rails 4
#products = Product.joins(:client).order('clients.lastname1 ASC')
#Other version (be careful where to use plurals and where not to)
#products = Product.find(:all, :joins => :client, :order => 'clients.lastname1 ASC')
And in your view:
<% #products.each do |p| %>
<tr>
<td><%= p.money %></td>
<td><%= p.client.str_name %></td>
</tr>
<% end %>
<% #products.each do |p| %>
<tr>
<td><%= p.money %></td>
<td><% #a= p.client(:order=>"lastname1 ASC")%> <%= #a.str_name %></td>
<% end %>
Not sure if this answers your question, but in the second td, the ERB tag isn't printing out anything to the page. Check your syntax.
If you're actually trying to set #a equal to something like that, it's best done in the controller.
That was just something I noticed. I'll continue to look at your code to see if I can find why you're not getting the result you desire.
In your code, it looks like each product only has one client, however I can't be sure since you did not post code for your Product model. If that is the case, then p.client will only ever return one client, and there is no reason to sort the results; ActiveRecord may be smart enough to skip that step. This could also explain the queries in your log.
If you want a many-to-many relationship between clients and products, you should create a join table.
rails generate model ClientProduct client_id:integer product_id:integer
Your resulting relationships would be
Client:
class Client < ActiveRecord::Base
has_many :client_products
has_many :products, :through => :client_products
...
end
Product:
class Product < ActiveRecord::Base
has_many :client_products
has_many :clients, :through => :client_products
...
end
ClientProduct:
class ClientProduct < ActiveRecord::Base
belongs_to :client
belongs_to :product
...
end

Retrieve info from last record of association

I have a model Lead which has a has_many Relationship with Activity.
I want to display a list of all leads on the leads index page, including a count of all activities and the timestamp of the latest activity of each lead.
The model looks like this:
class Lead < ActiveRecord::Base
has_many :activities, -> { order("activitytimestamp DESC") }, dependent: :destroy
class Activity < ActiveRecord::Base
belongs_to :lead
The view looks like this:
<table>
<thead>
<tr>
<th>Activity | last</th>
...
</tr>
</thead>
<tbody>
<% #leads.each do |lead| %>
<tr>
<td><%= lead.activities.size %> | <%= lead.activities.last %></td>
...
</tr>
<% end %>
</tbody>
</table>
Obviously lead.activities.last doesn't work.
What do I need to do to display the timestamp of the latest lead.activities Record?
You're so close!
General configuration of a general attribute of the most recent record related to a specific record is:
specific_record.related_records.last.general_attribute
or, in your case, I believe:
lead.activities.last.timestamp_field
The issue with
leads.activities.last
is that it returns the object, not the information in the field of that object, which is what you're looking for.

has_many form with nested attributes for existing objects

I am still fairly new to Rails and fairly sure the way I'm attempting to go about this is inefficient or just plain silly, but here's what I'm trying to accomplish. I have 2 models, Cases (patient case files) and Inventories (medical transplant materials used in Cases).
class Case < ActiveRecord::Base
has_many :inventories
accepts_nested_attributes_for :inventories, :reject_if => :all_blank
end
class Inventory < ActiveRecord::Base
belongs_to :case
end
Inventories are created through a separate process and the goal is to associate them with a Case through the Case form. What I am trying to do is put a table on my Case form that lists the available Inventories along with checkboxes to select the desired Inventories to associate with the Case being created. This is further complicated by the fact that I need to be able to include nested fields for a couple of attributes on each Inventory (:case_price and :case_ship_price). I had previously done this in a very roundabout way using a has_many through association and storing those attributes on the pivot table, but it involved some hacky code to capture the field inputs from params and then save them through this block:
class CasesController < ApplicationController
def create
#case = Case.new(params[:case])
if #case.save
#case.case_lineitems.each do |li|
li.update_attributes(:price => params[:lineitem_price][li.inventory_id.to_s],
:shipping_cost => params[:lineitem_shipping][li.inventory_id.to_s])
end
redirect_to #case
else
render 'new'
end
end
end
This felt extremely clumsy and I was worried about problems it might cause, so I wanted to give a simple has_many, belongs_to relationship a try. However, I'm not sure if the typical <%= check_box_tag :inventory_ids, inventory.id, #case.inventories.include?(inventory), name: 'case[inventory_ids][]' %> works for that type of relationship. Here is what this section of my form looks like presently:
<table>
<thead>
<tr>
<th></th>
<th>Product</th>
<th>Serial #</th>
<th>Price</th>
<th>Shipping</th>
</tr>
</thead>
<tbody>
<% #inventories.each do |inventory| %>
<tr>
<td>
<%= check_box_tag :inventory_ids, inventory.id, #case.inventories.include?(inventory), name: 'case[inventory_ids][]' %>
</td>
<td><%= inventory.product.name %></td>
<td><%= inventory.serial_num %></td>
<%= f.fields_for :inventories, inventory do |inv| %>
<td>
<%= inv.text_field :case_price %>
</td>
<td>
<%= inv.text_field :case_ship_price %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
This results in the first checkbox defaulting to checked, and if I leave all unchecked, all of the inventories become associated upon submission. Checking only a subset results in an exception along the lines of Couldn't find Inventory with ID=206 for Case with ID=. Finally, checking all of the Inventories seems to result in the associations and nested attributes saving correctly.
How do I clean this up so that it works as desired? And if I need to go back to a has_many through relationship, is there a better way to save attributes on the pivot table on the same form that creates the row on pivot table? I'd really appreciate any help with this, as no amount of searching has gotten me out of this challenge.

Resources