Reputation: 478
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 />
Upvotes: 0
Views: 55
Reputation: 35533
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 %>
Upvotes: 1
Reputation: 10856
The error indicates that @user.name
errors because @user
is nil.
In your controller you currently seem to fetch:
def index
@budget_details = BudgetDetail.all
@users = User.all
end
def show
@budget_detail = BudgetDetail.find(params[:id])
@user = User.find(params[:id]) # THIS WOULD MEAN THAT A USER HAS THE SAME ID AS THE BUDGET_DETAIL
end
As the index action fetches the variables for the index template, you see that you aren't fetching any @user
there, just an @users
variable that holds all users.
Instead, you should remove the user fetching in both actions, and fetch them in the view through the budget_detail.user
association.
def index
@budget_details = BudgetDetail.all
end
def show
@budget_detail = BudgetDetail.find(params[:id])
end
show.html.erb
<p>
<b>Username:</b>
<%= @budget_detail.user.name %>
</p>
<% @budget_details.each do |budget_detail| %>
<tr>
<td><%= budget_detail.user.name %></td>
...
</tr>
<% end %>
Upvotes: 1