简体   繁体   中英

Accessing data through associations

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 %>

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 %>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM