Undefined Method, across classes, for a database derived field - ruby-on-rails

I'm aware that variants of this question have been asked before, but I do believe my question is different ;-)
I have 2 Ruby on Rails classes (with assoc. controllers, models, etc). These are Devices and Messages.
In the Devices index.html view, I need to make reference to the newest Message record.
<table>
<tr>
<th>Device Name</th>
<th>Checked</th>
</tr>
<% newestMessage = Message.find(:all, :order => "updated_at DESC", :limit => 1) %>
<% #devices.each do |device| %>
<tr>
<td><%= device.name %></td>
<td><%= newestMessage.updated_at %></td>
</tr>
<% end %>
</table>
(I believe I should move the logic to a helper, but for the time being I'm keeping it as simple as possible while I get it working)
When I access this blahblah.com/devices, I get...
undefined method `updated_at' for #<Array:0x103f36c00>
Due to the line,
<td><%= newestMessage.updated_at %></td>
Now, I've seen questions like this that suggest to add the equivalent of the following to messages_controller.rb,
attr_accessor :updated_at
However, I've tried this and it doesn't help. Also, I don't think this should be necessary anyway as 'updated_at' is derived from the database, built with scaffold, etc.
If I just print 'newestMessage' it seems to have a sensible value.
My question is, how should I access fields within newestMessage, which are of class Message from the Device index.html.erb?
Thanks in advance.

Try newestMessage = Message.last then newestMessage.updated_at

Related

Index view loading very slowly

I have a model Schools and a model PerformanceStats.
PerformanceStat
belongs_to :school
School
has_one :performance_stat
the index page for PerformanceStat shows all 2,000 performance stats, and also the school.name, school.score, and school.city, and I need access to the school.id and school.slug.
Controller:
def index
#performance_stats=PerformanceStat.all
end
My view code:
<tbody>
<% #performance_stats.each do |stat| %>
<% school = School.find(stat.school_id)%>
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
then the view goes on to display the performance stats.
This view load very slowly....10-20 seconds. How can I speed things up? I've tried PerformanceStats.scoped, and plucking school stats and selecting from an array, but these don't seem to help. Is there a way for me to access the school attributes without finding a School for every PerformanceStat? I believe the School.find bit is slowing things down considerably.
I have indexes on :school_id in PerformanceStat, and :score, :slug in the School model.
UPDATE:
The suggestion in the selected answer to add a cache resulted in this line of code in the index action of the SchoolsController:
fresh_when etag: #performance_stats
The load time dropped to 18ms. This solution works great for me because the content of the index action does not change often. This data gets updated once a year. This link has other suggested cache solutions for data that changes frequently.
PerformanceStat.all is a heavy query if you've a lot of data in this table and it'll be finding school for each performance stat.
What I can understand from your code is that you're facing (N + 1) problem over here.
NOTE: you should not fire queries from your views or helpers and let the controller do all the action.
For instance in your code:
<% #performance_stats.each do |stat| %>
<% school = School.find(stat.school_id)%> <- #THIS IS WRONG & LET THE ASSOCIATIONS DO ALL THE ACTION ON ITS OWN
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
you can use includes, PerformanceStat.includes(:school) it will fetch all the schools for each PerformanceStat.
your controller code should be:
#performance_stats = PerformanceStat.includes(:school)
instead of : #performance_stats = PerformanceStat.all
and your view code will now be:
<% #performance_stats.each do |stat| %>
<% school = stat.school %> #make sure all stats have a school assigned to them otherwise you can put a check below whether the school is nil or not
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
Quite a few things here. First of all change your controller method to this one, otherwise you will run into n+1 queries
def index
#performance_stats=PerformanceStat.includes(:school)
end
Since you have eagerly loaded the school, now you can access it directly in your view as
<% stat.school %>
Secondly loading almost 2000 records in one go is not optimal at all, it's gonna take a while to load all records. For this you must add pagination by using following gems
kaminari
will_paginate

How to avoid hitting database in the view

I get that one should not ping the database in the view... but wondering about the right solution. In one of my views, I need to pull info on an #order, it's child items, and also Amount, another model, based on each child item. Something like this:
<% #order.items.each do |item| %>
<td><%= item.name %></td>
<td><%= Refund.where(item_id:item.id).first.amount %></td>
<td><%= Amount.where(item_id: item.id).first.amount %></td>
<% end %>
For the sake of avoiding the db hits in the view, the only solution I've thought of is to create a huge hash of all the relevant data in the controller, which is then accessed from the view. So it would be something like this:
# controller (writing quickly, code may not be totally right, hopefully you get gist
data = Hash.new
data["items"] = []
#order.items.each do |item|
item_hash = {
"name" => item.name,
"amount" => Amount.where(item_id: item.id).first.amount,
"refund" => Refund.where(item_id:item.id).first.amount
}
data["items"] << item_hash
end
# view code
<% data["items"].each do |item| %>
<td><%= item["name"] %></td>
<td><%= item["refund"] %></td>
<td><%= item["amount"] %></td>
<% end %>
And I know SO hates this type of question... but I really need to know... is that the best solution? Or are there are best practices? The reason I ask is because it seems very clean in the view, but very bulky in the controller, and also it gets quite unwieldy when you have a much more complex set of nested tables, which is what I actually have (i.e., the data hash would be quite funky to put together)
First of I would use associations between item and the 2 other classes, so that you can do
item.refund
item.amount
Instead of Refund.where(...). You could further define methods such as
def refund_amount
refund.amount
end
And similarly for the other one (and hopefully come up with a better name than amount_amount.
This keeps both your view and controller clean but it won't be any faster. So far all of the approaches involve running 2 database queries per item which is the real issue as far as I'm concerned - whether those excess queries happen in the view or the controller is of lesser concern.
However you can avoid this with Active Record's include mechanism:
Item.include(:amount,:refund).where("your conditions here")
Will load the named associations in bulk rather than loaded them one at a time as each item is accessed.

Rails stock ticker dashboard widget proof of concept

I’m new to rails and trying to expand beyond just the standard scaffolding. I would like to build a dashboard that isn’t based on just one model but a collection of models (partials?) to view weather, the stock market, etc. Some of this data will be pulled from queries from models. Other elements will be hard coded, like the stock ticker widget which I thought I would begin with.
I began by creating a dashboard.rb file in the lib folder. I used the following code
class Dashboard
def self.tickertable
yahoo_client = YahooFinance::Client.new
data = yahoo_client.quotes(["BVSP", "NATU3.SA", "USDJPY=X"], [:ask, :bid, :last_trade_date])
#ask = data.ask
end
end
Next, I added the necessary code to the application .rb file to load the lib folder
config.autoload_paths += %W(#{config.root}/lib)
I then added a _tickertable partial in a newly created views/shared folder.
<table>
<thead>
</thead>
<tbody>
<% Dashboard.tickertable.each do |ticker| %>
<tr>
<td><%= ticker.name %></td>
<td><%= ticker.symbol %></td>
<td><%= ticker.last_trade_price %></td>
</tr>
<% end %>
</tbody>
</table>
I put this partial to an already existing view which when it loads it gives the following error:
undefined method `ask' for #<Array:0x6663330>
on the "<% Dashboard.tickertable.each do |ticker| %>" line.
What key element am I missing? I feel like I am close but clearly something is missing.

Weird rails pluralization issue

I have a ruby on rails application that recently started giving me issues.
I believe there may be a weird bug/feature in the way rails is pluralizing model names for the database.
For example,
I have a model called DiagExerciceWeekFive. The table in the database is called diag_exercice_week_fives. The pluralization works correctly here.
I think there may be a problem in the way rails is attempting to "de-pluralize" the table into the respective objects.
When I try to load up a simple form that displays all of my diagweekfives, I get this error:
uninitialized constant Diag::DiagExerciceWeekFife
Not once have I used that name in my application.
Here's the relevant bit of code that is throwing the error:
<% ExerciceWeekFive.all.each do |exercice| %>
<tr class="success">
<td><%= check_box_tag :exercices_week_five_ids, exercice.id, #diag.exercices_week_fives.include?(exercice), :name => 'diag[exercices_week_five_ids][]' %></td>
<td><%= exercice.number %></td>
<td><%= exercice.description %></td>
</tr>
The exception is thrown on the first <td> within the <tr>
Has anyone run into this before? I know little about rails, but I am trying to maintain some legacy code.
Thanks.

Why is this so slow to load? Is there any technique to make this faster?

I'm using the gem called "Mailboxer" ( https://github.com/ging/mailboxer )
This enables messaging system within Rails app.
With this gem, I'm showing 10 received messages for each page.
I'm using Kaminari for pagination here.
But, it is kinda too slow with my codes.
It's issuing more than 25 sql at once :(
How can I make this faster? It takes more than 1500ms to show just 1 page.
Here are my codes
What's wrong with this? Is there any technique to make this faster?
controller
#number_of_messages_to_display = 10
#messages = current_user.mailbox.inbox.page(params[:page]).per(#number_of_messages_to_display)
#messages_count = current_user.mailbox.inbox.count
view(messages/index.html.erb)
<%= #messages_count.to_s %> messages in your received message box.
<table>
<% #messages.each do |m| %>
<tr>
<td><%= check_box_tag "id[]",m.id %></td>
<td><%= if m.is_read?(current_user) then "Read" else "Un-read" %></td>
<td><%= profile_link(m.recipients.first) if m.recipients.first != current_user %></td>
<td><%= link_to m.subject, show_messages_path(:id => m) %></td>
<td><%= today_datetime(m.last_message.created_at) %></td>
</tr>
<% end %>
</table>
view(helpers/application_helper.rb)
def profile_link(user)
if user
nickname = user.user_profile.try(:nickname)
username = user.try(:username)
link_to nickname, show_user_path(username)
else
"Un-known"
end
end
def today_datetime(date_time)
date_time.to_date == Date.current.to_date ? "<span class='text-info'>#{date_time.to_s(:us)}</span>".html_safe : date_time.to_s(:us)
end
routes.rb
get 'messages/:id' => 'messages#show', :as => :show_messages
get "users/:id" => 'users#show', :as => :show_user
models/user.rb
def to_param
"#{username}"
end
Classic example of the N + 1 problem.
You retrieve #messages = current_user.mailbox.inbox.page, which will retrieve records from the messages table.
In the view, you loop through them and check each message's recipients list (a has_many relationship, probably based on the receipts table, as can be seen here). So, for each message, you end up sending another query to the database.
You can correct this by retrieving the recipients together with the messages (and the last_message association as well, since you're using it):
#messages = current_user.mailbox.inbox.
includes(:receipt, :last_message).page
Also, you may have a different problem compounding this, as 25 queries should execute pretty quickly on a modern computer. I'd recommend using something like the RailsPanel tool to track down where the time is being spent.

Resources