I'm very green when it comes to RoR and am having trouble trying to figure out whether my issue is caused by a problem in my model associations or whether I'm just not using the right syntax to access the data.
A user can have many budgets. Each budget is comprised of multiple detail lines. I thought there wasn't a need for the user id in budget_details since it's captured in budget and so can be inferred through the relationship between the three (maybe?!)
In the budget_details index I want to be able to include the users name; I've got it to work in the 'show' view but not the index.
I did use a scaffold to set these up, so I know there's a lot of crud there, I was just trying to do an example before moving to a new project to do it for real.
The actual error is;
NoMethodError in Budget_details#index
Showing C:/Sites/example1/app/views/budget_details/index.html.erb where line #17 raised:
undefined method `name' for nil:NilClass
I can't see why this fails but the show method works? Is is something to do with the scope? ie show is at the single instance level whereas index is at the 'all' level and so it can't find the data in Users?
Any help much appreciated
Models:
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :name
has_many :budgets
has_many :budget_details, :through => :budgets
Budget.rb
class Budget < ActiveRecord::Base
attr_accessible :budget_name, :user_id
belongs_to :user
has_many :budget_details
Budget_details.rb
class BudgetDetail < ActiveRecord::Base
attr_accessible :amount, :amount_type, :budget_id, :itemname
belongs_to :budget
Controller - budget_details_controller.rb
class BudgetDetailsController < ApplicationController
# GET /budget_details
# GET /budget_details.json
def index
@budget_details = BudgetDetail.all
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @budget_details }
end
end
# GET /budget_details/1
# GET /budget_details/1.json
def show
@budget_detail = BudgetDetail.find(params[:id])
@user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @budget_detail }
end
end
.....
show.html.erb <%= notice %>
<p>
<b>Username:</b>
<%= @user.name %>
</p>
<p>
<b>Budget:</b>
<%= @budget_detail.budget_id %>
</p>
<p>
<b>Itemname:</b>
<%= @budget_detail.itemname %>
</p>
<p>
<b>Amount:</b>
<%= @budget_detail.amount %>
</p>
<p>
<b>Amount type:</b>
<%= @budget_detail.amount_type %>
</p>
<%= link_to 'Edit', edit_budget_detail_path(@budget_detail) %> |
<%= link_to 'Back', budget_details_path %>
index.html.erb
<h1>Listing budget_details</h1>
<table>
<tr>
<th>Username</th>
<th>Itemname</th>
<th>Budget</th>
<th>Amount</th>
<th>Amount type</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @budget_details.each do |budget_detail| %>
<tr>
<td><%= @user.name %></td>
<td><%= budget_detail.itemname %></td>
<td><%= budget_detail.budget_id %></td>
<td><%= budget_detail.amount %></td>
<td><%= budget_detail.amount_type %></td>
<td><%= link_to 'Show', budget_detail %></td>
<td><%= link_to 'Edit', edit_budget_detail_path(budget_detail) %></td>
<td><%= link_to 'Destroy', budget_detail, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
<br />
In your index.html.erb
file:
<td><%= @user.name %></td>
You haven't defined @user
in your index
action - hence the error.
You can avoid this problem and altogether directly pulling @user
in the controller for either action, if just use the association:
<td><%= budget_detail.user.name %></td>
And to avoid the performance hit for doing so (N+1), you can eager-load them in your controller using includes
:
@budget_details = BudgetDetail.includes(:user).all
However, this association doesn't yet exist - you will need to add a 'has-one-through' relationship to your BudgetDetail
class - the reverse of what you did for your User
class.
has_one :user, :through => :budget
To summarize:
You should add a user
association to BudgetDetail
as 'has-one-through'.
Your controller actions should look like this:
def index
@budget_details = BudgetDetail.includes(:user).all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @budget_details }
end
end
def show
@budget_detail = BudgetDetail.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @budget_detail }
end
end
And in your views don't use @user.name
rather:
<%= budget_detail.user.name %>