Reputation: 1355
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
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.
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
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
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