Accessing a has_one associations' attributes

jasdeepg picture jasdeepg · Nov 5, 2012 · Viewed 8.1k times · Source

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: "[email protected]", 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!

Answer

Zach Kemp picture Zach Kemp · Nov 5, 2012

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.