user275801
user275801

Reputation: 1355

Error: undefined method `contact' for nil:NilClass

I have a ruby/rails/hobo system that someone wrote a couple years ago, that I need to port to the latest version of ruby/rails/hobo. It seems that ruby doesn't care about backward compatibility, so code that used to work in the old app doesn't work anymore:

In the observation.rb model file, the old app has this:

belongs_to :survey
has_one :site, :through => :survey

def create_permitted?
  acting_user == self.survey.contact or acting_user.administrator?
end

survey.rb model file has this:

belongs_to :contact, :class_name => 'User', :creator => true

Unfortunately the code in observation.rb doesn't work under the new ruby/rails/hobo, and it gives me the error:

NoMethodError in Observations#index

Showing controller: observations; dryml-tag: index-page where line #1 raised:

undefined method `contact' for nil:NilClass
Extracted source (around line #1):

0
Rails.root: /home/simon/ruby/frogwatch2

Application Trace | Framework Trace | Full Trace
app/models/observation.rb:48:in `create_permitted?'

How should the "create_permitted" method be changed? I'm finding that the documentation for ruby/rails/hobo is pretty atrocious (which is fair enough as it is free software). Also I don't even know how to begin searching for this on google (i've been trying for days).

Please help! :)

Upvotes: 0

Views: 800

Answers (3)

Sean Hill
Sean Hill

Reputation: 15056

I'm going to echo what the other two have said. The survey you are trying to reference is nil, and nil does not have a method called contact. I am going to offer up a slightly different solution:

def create_permitted?
  acting_user == survey.try(:contact) or acting_user.administrator?
end

The #try method exists on nil and on survey. It basically wraps the method call in a rescue. Conceptually, it looks like:

def try(method_name, *args)
  self.send(method_name, args) rescue nil
end

This may reduce the amount of code that you have to write to catch conditions where a relationship may not be present, preventing a NoMethodError exception.

#try is part of the Rails core extensions for Object. In reality, it does not work as I have above, since exceptions arising from calls to Object#try will still happen as they normally should. Instead, it extends Object by calling send. It extends NilClass by returning nil, so it does not try to send any method to NilClass, preventing a NoMethodError. As tadman points out in the comments, a catch-all exception handler is normally not a good idea.

https://github.com/rails/rails/blob/6ef9fda1a39f45e2d18aba4881f60a19589a2c77/activesupport/lib/active_support/core_ext/object/try.rb

Update

A better solution, and one that I forgot about, is to use delegate.

For example:

class User < ActiveRecord::Base
  delegate :contact, to: :survey, prefix: true, allow_nil: true
end

Then you would call user.survey_contact, and would fail gracefully if the survey is nil.

Upvotes: 1

Thomas Klemm
Thomas Klemm

Reputation: 10856

Apart from differing views on the docs around Rails, you are calling contact on a survey which does not exist in this case, resulting in a call nil.contact.

An alternative would be to check for the presence of the survey before calling contact, e.g in such a way.

def create_permitted?
  acting_user == (survey && survey.contact) or acting_user.administrator?
end

Upvotes: 2

siddick
siddick

Reputation: 1478

You are getting the error because reference column(survey_id) may contain a null or invalid reference id.

If the null or invalid reference is allowed then, change the code to handle it

( self.survey and acting_user == self.survey.contact ) or acting_user.administrator?

Upvotes: 1

Related Questions