问题
I'm still quite new to Rails so hopefully this isn't a silly question.
I have two models: User and Chore. User has_one chore, and Chore belongs to User
class User < ActiveRecord::Base
attr_accessible :chore_done, :email, :name, :phone
has_one :chore
class Chore < ActiveRecord::Base
attr_accessible :name, :user_id
belongs_to :user
In my user index, I'm trying to show all users and display the chore that is associated with him or her. I'm doing this by passing User.all to the view and using a .each to iterate through each user:
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.chore.name %></td>
<td><%= user.chore_done? %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
Unfortunately, I cannot access the name attribute of the Chore. I get this error:
undefined method `name' for nil:NilClass
If I remove the .name attribute, it just returns a pointer to the Chore object.
I have a feeling this has something to do with passing the User.all object to the view and then iterating over that. Just accessing a specific User object (e.g. User.find(1)) in the console, and then accessing user.chore.name works fine.
1.9.3-p194 :045 > user = User.find(4)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 4]]
=> #<User id: 4, name: "Example User", email: "example@dot.com", phone: 8675309, chore_done: false, created_at: "2012-11-05 01:53:33", updated_at: "2012-11-05 01:53:33">
1.9.3-p194 :046 > user.chore.name
Chore Load (0.3ms) SELECT "chores".* FROM "chores" WHERE "chores"."user_id" = 4 LIMIT 1
=> "Living room"
I read a bit about AssociationProxy in the APIDock, but I don't think I understand it completely nor do I know how to work with it. From what I gathered, accessing all objects in a model returns an Array of objects but doesn't return a complete set of attributes for its dependencies? So, in this case, I get a pointer to the Chore object but no access to any of its attributes.
I could certainly just add Chore as another column in my user table, but I may add to that model in the future + I just want to figure out how the has_one association works anyway.
Any help is appreciated!
回答1:
undefined method `name' for nil:NilClass
This says that you are trying to call some method name()
on an an instance of Nil
. Within your code's context, that says that chore
on this line is nil
<td><%= user.chore.name %></td>
Simply put, one of the User
instances in @user
has no associated Chore
. One simple way to accomodate this in your view is by checking if user.chore
exists.
<td><%= user.chore.nil? ? "some default name" : user.chore.name %></td>
回答2:
It's because not every user has a chore associated with it.
There are a few ways you can deal with this; I'm partial to the iteration pattern using Array()
to coerce a collection. You can do this in your view:
<% Array(user.chore).each do |chore| %>
<td><%= chore.name %></td>
<td><%= chore.done? %></td>
<% end %>
Array()
will instantiate an empty array if chore
is nil, meaning nothing happens when you try to iterate over it. If a chore is present, it will iterate over [chore]
.
The benefit of this pattern is that it doesn't violate Tell don't ask. Asking an object if it is nil?
is also possible, but it's more of a procedural technique that doesn't take advantage of Ruby's strong object-oriented features.
Edit
As Deefour points out, this will result in a table with mismatched columns. You should probably use his solution for this case.
回答3:
Here's another option that uses Rails' Delegate module:
class User < ActiveRecord::Base
has_one :chore
delegate :name, to: :chore, prefix: true, allow_nil: true
end
This will allow you to call <%= user.chore_name %>
, which will pass the name
call onto chore, returning nil
if there is none.
回答4:
Another option:
<td><%= user.chore.try(:name) %></td>
This will return nil
if there is no user.chore
.
来源:https://stackoverflow.com/questions/13225002/accessing-a-has-one-associations-attributes