Accessing a has_one associations' attributes
Asked Answered
P

4

6

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!

Prolusion answered 5/11, 2012 at 1:56 Comment(0)
C
3

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>
Chaucerian answered 5/11, 2012 at 2:1 Comment(3)
Ah! That makes so much sense. I made this much more complicated than it was. Just breaking down what you've written ... I haven't seen this syntax (the second question mark) in rails before, but it seems very similar to SQL queries. Cool!Prolusion
It's called a ternary operator. (condition) ? "true case" : "false case"Chaucerian
Stylish way to avoid ternaries with rails: user.chore.try(:name) apidock.com/rails/Object/tryHildebrand
S
5

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.

Sweetie answered 5/11, 2012 at 2:1 Comment(1)
Ah, you're right. Sorry, I wan't paying attention to the rest of the table.Sweetie
S
5

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.

Sweetie answered 5/11, 2012 at 5:24 Comment(1)
:allow_nil - if set to true, prevents a NoMethodError to be raisedNorling
C
3

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>
Chaucerian answered 5/11, 2012 at 2:1 Comment(3)
Ah! That makes so much sense. I made this much more complicated than it was. Just breaking down what you've written ... I haven't seen this syntax (the second question mark) in rails before, but it seems very similar to SQL queries. Cool!Prolusion
It's called a ternary operator. (condition) ? "true case" : "false case"Chaucerian
Stylish way to avoid ternaries with rails: user.chore.try(:name) apidock.com/rails/Object/tryHildebrand
R
0

Another option:

<td><%= user.chore.try(:name) %></td>

This will return nil if there is no user.chore.

Reachmedown answered 31/12, 2016 at 17:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.