Error: undefined method `contact' for nil:NilClass - ruby-on-rails

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, e.g 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.

Related

RoR: ActiveRecord.friendly.find() causes redirect

I'm supporting an application on RoR. One part of it I don't understand.
There is a model:
class Country < ActiveRecord::Base
extend FriendlyId
friendly_id :prefix, :use => [:finders]
...
end
If I call Country.find("value") or Country.friendly.find("value") with value that doesn't exist in the database the call causes HTTP 302 immediately. If I change it to Country.find_by(prefix: "value") it works as expected except it returns nil for not found values.
Documentation says .find() should raise ActiveRecord::RecordNotFound exception. But it doesn't happen in my case.
I'm pretty new in RoR, so probably I've missed something obvious. Will be appreciated if you show me the direction.
First of all, as you said find raise an ActiveRecord::RecordNotFound if the record with the given id is not found, where find_by just returns nil if the record with the given attribute(s) is not found.
That said, I think you have a rescue_from call, or something similar, in your application somewhere, maybe ApplicationController, which redirects when a RecordNotFound is catched.

Rails stack level too deep in after_commit

I have a TreatmentEvent model. Here are the relevant parts:
class TreatmentEvent < ActiveRecord::Base
attr_accessible :taken #boolean
attr_accessible :reported_taken_at #DateTime
end
When I set the taken column, I want to set reported_taken_at if taken is true. So I tried an after_save callback like so:
def set_reported_taken_at
self.update_attribute(:reported_taken_at, Time.now) if taken?
end
I think update_attribute calls save, so that's causing the stack level too deep error. But using the after_commit callback is causing this to happen, too.
Is there a better way to conditionally update one column when another changes? This answer seems to imply you should be able to call update_attributes in an after_save.
Edit
This also happens when using update_attributes:
def set_reported_taken_at
self.update_attributes(reported_taken_at: Time.now) if self.taken?
end
As a note, stack level too deep generally means an infinite loop
--
In your case, the issue will almost certainly be caused by:
after_commit :set_reported_token_at
def set_reported_taken_at
self.update_attribute(:reported_taken_at, Time.now) if taken?
end
--
The problem is after_commit is going to try and save the reported_taken_at even if you've just saved a record. So you're going to go over the record again and again and again and again...
Often known as a recursive loop - it's used a lot in native development, but for request (HTTP) based apps, it's bad as it leads to a never-ending processing of your request
Fix
Your fix should be like this:
#model
before_save :set_reported_token_at
def set_reported_taken_at
self.reported_taken_at = Time.now if taken? #-> assuming you have a "taken" method
end
Can't you use a before_save? You can see if the other field value has changed and if so update this field. That way you just have one DB call.

Rails association.create and association.create! returns nil

I'm just throwing this out there because I really can't figure this out. When I call for instance user.articles.create! { title: 'blah' } nil is returned but the object is created. I've not seen anything like this before and was wondering if someone else has?
I've tried rails 3.2.13 and 3.2.12 and they both do the same thing.
EDIT
In active record both create and create! ends up IN THIS METHOD that is supposed to return the record or throw an exception.
def create_record(attributes, options, raise = false, &block)
unless owner.persisted?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
if attributes.is_a?(Array)
attributes.collect { |attr| create_record(attr, options, raise, &block) }
else
transaction do
add_to_target(build_record(attributes, options)) do |record|
yield(record) if block_given?
insert_record(record, true, raise)
end
end
end
end
If I'm not mistaken Factory Girl mimic the actual object you're dealing with through your predefined factory. Therefor User#articles might not return what you think it is when called on a factory.
Changing
user.articles.create! { title: 'blah' }
to
create(:article, user: user, title: 'blah')
should enforce the association through Factory Girl's interface.
I believe there is something going on with your attr_accessible or attr_accessor in your Article class. I you might have not included the user_id or something else...
There is also a similar question here: rails Model.create(:attr=>"value") returns model with uninitialized fields
I had the same symptom, and this question is the only relevant hit that I could find. I'll throw my solution into the mix in case it helps anyone else.
The code worked in real life, and only failed under rspec. All the troubleshooting I did made no sense, pointing to create! being broken, which I never believed.
As it turns out, I was mocking create! so it never got called. Adding .and_call_original to my mock solved the problem.
My model was something like this: (not really...but compatible with this answer)
class Flight < ApplicationRecord
has_many :seats
def create_seats(seat_count)
seat_count.times { Seat.create!(flight: self) }
seats.each(&:raise_seatback_and_lock_tray)
end
And my test was:
it 'creates enough empty seats' do
expect(LicenseFile).to receive(:create!).twice
flight.create_seats(2)
end
The expectation was met (confirmed manually), but an error was raised:
NoMethodError:
undefined method `raise_seatback_and_lock_tray=' for nil:NilClass
Changing my mock to allow create! to actually be called solved the problem:
it 'creates a LicenseFile for each destination rule' do
expect(LicenseFile).to receive(:create!).twice.and_call_original
flight.create_seats(2)
end
This now passed:
creates enough empty seats
1 example, 0 failures
If you are expecting the object to be returned use
user.articles.create { title: 'blah' }
Why some methods have bang (!), you can read this topic
Why are exclamation marks used in Ruby methods?

What is causing a NoMethodError '_view_paths' exception?

First off, the exception in question:
undefined method '_view_paths' for nil:NilClass`
The related routes:
get 'payments/index' => 'payments#index'
get 'payments/class' => 'payments#class'
get 'payments/kids' => 'payments#kids'
get 'payments/donate' => 'payments#donate'
The associated controller:
class PaymentsController < ApplicationController
def index
end
def class
end
def kids
end
def donate
end
end
So, the exception occurs every time I try to access one of the routes. The views for the routes described above are the simple ones generated with scaffolding and use no other rails API calls. I can't seem to find any other information on this '_view_paths' method. The only assumption I can make thus far is that the proper view isn't being found, but all views reside exactly where expected according to rails conventions (app/views/payments/*).
Has anyone stumbled upon this issue and found a solution?
You can't define a method named "class" as it's already a reserved method to refer to the object's class, for example:
Object.new.class #=> Object
Technically I suppose you can override it (as you have), but doing so is mostly likely going to have some bizarre consequences unless you know what you're doing.
The error is probably happening when the code tries to call something like self.class._view_paths. It expects to be calling PaymentsController._view_paths. However, you've overridden the instance method class with an empty method returning nil, hence the nil exception.

TypeError when trying to use observers and STI

I'm trying to follow along with the thread on implementing an achievement system (located at How to implement an achievement system in RoR), and am running into a TypeError when the object is saved and the method awarded? gets called. The error looks like:
TypeError (can't dump anonymous class Class):
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/yaml/rubytypes.rb:6:in `to_yaml'
app/models/user.rb:18:in `awarded?'
The model logic is exactly as defined in that previous question:
def awarded?(achievement)
achievements.count(:conditions => { :type => achievement }) > 0
end
Any idea what's happening here?
UPDATE:
Based on the info below, I ended up removing the abstraction and placing the award methods directly in each controller for now. Not very DRY and when I have time to play around with it more I'll try to abstract it out again, but works for now. An example:
if #user.achievements.count(:conditions => { :type => "CommentAchievement" }) < 1 and #comments > 1000
#user.achievements << CommentAchievement.new(:group_id => #group)
end
Not sure of the exact problem but it looks like whatever is being provided to the the method that is assigning the achievement is wrong, which is causing the value of 'type' to be stored incorrectly. Check your database and make sure that the value being written to 'type' is a subclass of your parent, and not the parent (or any other incorrect value).
If that value is stored incorrectly rails will not be able to instantiate an object, and it must look that value up before it attempts to create the object which is why its throwing that error.

Resources