I'm kind of new to RoR so this may be a stupid question, but is there
any way to pull data associated with one controller into a form in the view of
another?
I have 2 controllers, home and subjects and I have one model subject (probably a poor choice in hindsight). I have a table in my database called subjects and a column in that table called text I want to pull data from that column into both my home and subjects views and i can do it fine in the subjects view but i can't get it to work in the home view.
I'm doing it with
def index
#subjects = Subject.order("subjects.position ASC")
end
in the subjects controller, and
<tr>
<th>Text</th>
<td><%= #subject.Text %></td>
</tr>
in the view.
Let me know if you need any more information, Thanks.
put this in your view
<% for subject in #subjects %>
<tr>
<th>Text</th>
<td><%= subject.text %></td>
</tr>
<% end %>
Note that I am using subject as opposed to #subject inside the loop, and that .text is not capitalised as in your code
Controllers are only associated to tables by convention.
Models are associated to tables, and you can use whatever models you want in your controllers.
Related
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
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.
I have three models. One is an Employee, one is an Item, and one is a Transaction that belongs to both Employee and Items. It's a simple app that allows Employees to check in and check out items - 'Transaction' has a boolean column for checked-in/checked-out.
What I'm trying to do is show within the employee/show view the current list of Items that an Employee has checked out. This is some rough code that I sketched out, but I'm not sure that it's going to work, and I was told not to use a lot of nested conditionals in my views anyway.
<% if #employee.transactions.exists? %>
<h3>Currently Checked-OUT Items</h3>
<table>
<tr>
<th>Item Asset Tag</th>
<th>Item Description</th>
</tr>
<% #employee.transactions.each do |transaction| %>
<% if item.transaction.last? && transaction.status == false %>
<tr>
<td><% transaction.assettag %></td>
<td><% transaction.description %></td>
</tr>
<% else %>
NO CHECKED OUT ITEMS
<% end %>
</table>
<% end %>
<% end %>
Basically, I'm trying to:
checks all employee transactions
compares the item involved in the transaction and sees if it's the .last transaction record for item
if it is, and if it's false, then it's a current checkout.
Is this a better job for a scope within the Transaction model, or a helper method? I've never used either, I'm really new at rails.
You should do a couple of things in here.
First - create a scope that will fetch last item transaction for you. There's no point in going through al item transactions if you're interested in the last one only, right?
Second, use partials. In this example it's hard to show how I would refactor code to use them (some things doesn't make sense here, ex. where does item variable come from?)
Scope example (take last transaction)
#item.transactions.order('created_at DESC').first
You can as well add scopes for checkin / checkout
class Transaction
scope :checkin, -> { where(status: true) }
scope :checkout, -> { where(status: false) }
end
First, you are on the right track. When views get ugly and hard to read because of extensive embedded ruby conditionals and such, think about moving the logic into a helper.
If you have a typical rails app, you'll already have app/helpers/application_helper.rb
So you could just create a helper in that file
def make_employee_list(employee)
if employee.transactions.exists?
content_tag(:div) do
content_tag(:h3, "Currently Checked-OUT Items")
content_tag(:table) do
employee.transactions.each do |transaction|
# you get the idea
end
end
end
end
end
Then in your view you could do this:
<%= make_employee_list(#employee) %>
Okay so in the index portion of my controller I set
#patients = Patient.all
then in patients_helper.rb
def race_abrev
return self.caucasian
end
where caucasian is an integer datatype column in the patients table
then in the view index.html.erb
<% #patients.each do |p| %>
<td><%= p.gender %></td>
<td><%= p.ethnicity %></td>
<td><%= p.race_abrev %></td>
<% end %>
I get a
undefined method `race_abrev' for #<Patient:0xb4d95cd8>
I've checked the table and I'm expecting patient.caucasian to return the integer 1, what am I missing..any insight to a fundamental misunderstanding I seem to have?
race_abrev is a helper, not a method on Patient:
<%= race_abrev(p) %>
And the helper itself would return p.caucasian, although it'd seem like you'd actually want to do something with the value of caucasian, like a compare or something.
All this said, I'm not sure why you're not defining it (or what "it" actually is) on the model, since so far it doesn't seem to have anything to do with the view, which is what view helpers are for.
If you're storing something in the DB you want to transform it may or may not belong in a view helper; if it's to turn it into something human-readable I'd be more likely to put it in the model.
I'm coming from a .NET background, where it is a practice to not bind domain/entity models directly to the view in not-so-basic CRUD-ish applications where the view does not directly project entity fields as-is.
I'm wondering what's the practice in RoR, where the default persistence mechanism is ActiveRecord. I would assert that presentation-related info should not be leaked to the entities, not sure though if this is how real RoR heads would do it.
If DTOs/model per view is the approach, how will you do it in Rails?
Your thoughts?
EDIT:
Some examples:
- A view shows a list of invoices, with the number of unique items in one column.
- A list of credit card accounts, where possibly fraudulent transactions were executed. For that, the UI needs to show this row in red.
For both scenarios, The lists don't show all of the fields of the entities, just a few to show in the list (like invoice #, transaction date, name of the account, the amount of the transaction)
For the invoice example, The invoice entity doesn't have a field "No. of line items" mapped on it. The database has not been denormalized for perf reasons and it will be computed during query time using aggregate functions.
For the credit card accounts example, surely the card transaction entity doesn't have a "Show-in-red" or "IsFraudulent" invariant. Yes it may be a business rule, but for this example, that is a presentation concern, so I would like to keep it out of my domain model.
In general I would answer that your AcitveRecord object can contain any fields and you show in views only what you want. There is scaffolding task in rails scripts but it is only to create some setup model, controller and view. When I work with Rails I don't use ./script/generate scaffold at all. Rather I'm generating only model and controller separatly. The view part I add manualy.
ActiveRecord only maps data from database to some nice objects. What you do with it in view is up to you.
According to separation between presentation and business rules I think that fallowing examples would make it clear to you how to handle it in Rails.
For your invoice example I would create a view this way:
<h1>Invoices</h1>
<table>
<tr>
<th>Invoice #</th>
<th>Date</th>
<th>Name</th>
<th>No. of line items</th>
etc
</tr>
<% #invoices.each do |invoice| %>
<tr>
<td><%= invoice.number %></td>
<td><%= invoice.date.to_s %></td>
<td><%= invoice.name %></td>
<td><%= invoice.line_items.count %></td>
etc.
</tr>
<% end %>
</table>
Or even put a row with invoice data into separate partial and render it in above view. I assume that in your model you have:
# Invoice model
has_many :line_items
Now lets take a look on credit card example. I would do it like this:
# In CreditCard model add method
def fraudulent?
#put here some logic that returns true or false
end
Then in your view when you render this credit card:
<div <%= #credit_card.fraudulent? ? 'class="show_in_red"' : '' %>
here you can show whatever you want
</div>
Or even create helper for it:
# credit card helper
def add_show_in_red(credit_card)
credit_card.fraudulent? ? 'class="show_in_red"' : ''
end
# in Rails 3 or earlier version with plugin that puts `h` method by default
# your helper should have additional safe_html! call
def add_show_in_red(credit_card)
(credit_card.fraudulent? ? 'class="show_in_red"' : '').safe_html!
end
and in view:
<div <%= add_show_in_red(#credit_card) %>>
here you can show whatever you want
</div>