简体   繁体   中英

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! :)

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 , eg in such a way.

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

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?

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 .

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