Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #5767 (closed defect: fixed)

Opened 4 years ago

Last modified 3 years ago

[BUG] Server permanently breaks (sessions) when returning nil from has_one associations.

Reported by: jonathan.viney@gmail.com Assigned to: xal
Priority: normal Milestone: 1.2
Component: ActiveRecord Version: edge
Severity: major Keywords:
Cc: bitsweat

Description

These errors occur in r4721 and above, r4720 is fine.

NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:123:in `method_missing'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/callbacks.rb:344:in `callback'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/callbacks.rb:338:in `callback'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/callbacks.rb:330:in `callback'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/callbacks.rb:243:in `create_or_update'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/base.rb:1491:in `save_without_validation'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/validations.rb:744:in `save_without_transactions'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/transactions.rb:120:in `save'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:51:in `transaction'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/transactions.rb:86:in `transaction'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/transactions.rb:112:in `transaction'
        from ./script/../config/../config/../vendor/rails/activerecord/lib/active_record/transactions.rb:120:in `save'
        from (irb):4

I had a quick look and it seems that it's caused from line 587 of associations.rb. association.nil? is returning false when the association is nil. Weird.

My models look like this:

class Person < ActiveRecord::Base
  has_one :user
  
  def validate
    if user # The error goes away if I remove the if statement.
      # ...
    end
  end
end

class User < ActiveRecord::Base
  belongs_to :person
end

I can try and provide a failing test if needed.

Attachments

has_one_cached_association_error.diff (1.8 kB) - added by jonathan.viney@gmail.com on 08/09/06 02:18:14.
has_one_cached_nil_association.diff (1.7 kB) - added by jonathan.viney@gmail.com on 08/09/06 02:30:04.

Change History

08/09/06 02:17:50 changed by jonathan.viney@gmail.com

  • summary changed from Breakages with the has_one nil caching in r4721 onwards to [PATCH] Breakages with the has_one nil caching in r4721 onwards.

Here's a patch with a failing test. A fix is included, but it seems very strange to me.

08/09/06 02:18:14 changed by jonathan.viney@gmail.com

  • attachment has_one_cached_association_error.diff added.

08/09/06 02:20:23 changed by anonymous

I also changed the has_one after_save callback to be defined using a block rather than a string as it made the error easier to track down. Was there a reason for it being a string? Strings seem to be the normal way such callbacks are defined.

08/09/06 02:29:41 changed by jonathan.viney@gmail.com

I noticed in the belongs_to call back that association && association.target is used to check for the presence of the association. I've updated the patch to use the same method for has_one.

It still seems very strange. association == nil returns true, but association.nil? returns false. Why is that?

08/09/06 02:30:04 changed by jonathan.viney@gmail.com

  • attachment has_one_cached_nil_association.diff added.

08/09/06 07:39:48 changed by alisdair@randomoracle.org

  • cc set to http://spiro.fisica.unipd.it/HyperNews/get/test/9.html.
  • keywords set to rest routing.

Because association == nil passes through to the proxy target (the has_one object), but association.nil? is handled by the proxy itself (see line 8 of association_proxy.rb). I'm not sure if this makes sense with the new patch in place: loaded but empty associations will still == nil but will now not be nil?, so maybe .nil? should be proxied from now on. Any thoughts?

08/09/06 07:40:11 changed by anonymous

  • cc deleted.
  • keywords deleted.

(follow-up: ↓ 8 ) 08/17/06 01:46:23 changed by jonathan.viney@gmail.com

  • status changed from new to closed.
  • resolution set to fixed.

Fixed with r4773

(in reply to: ↑ 6 ) 09/24/06 20:17:20 changed by norbauer

  • status changed from closed to reopened.
  • resolution deleted.
  • severity changed from normal to major.
  • summary changed from [PATCH] Breakages with the has_one nil caching in r4721 onwards to [BUG] Server permanently breaks (sessions) when returning nil from has_one associations..

Replying to jonathan.viney@gmail.com:

Fixed with r4773

Attempting to call has_one associations in latest edge (Revision 5171) is giving me a similar (and serious) error, most likely related to this ticket. The fix appears not to have been complete.

The breakage results in mongrel serving the 500 page (rather than the normal exception explanation page shown in development mode) and the server's being *permanently* disabled until I both kill all sessions and restart. If I don't do this, I get 500 pages for everything--even if I navigate to pages without exceptions in them.

I can only cause this breakage by attempting to return a nil object via a has_one association. If an exception is thrown after attempting to do this, it will break the server as described above.

So if in User.rb we have has_one :subscription

Calling @user.subsription anywhere in the code for a user who doesn't have a subscription will disable the server.

The cause of the permanence of the disability appears to be in the session, both because killing the session and restarting the server fixes the problem, and also because throwing the error inserts error data into the session.

Before throwing this error, the session looks as normal. After attempting to call the has_one association, the raw session file contains:

... errorso: ActiveRecord::Errors;{:base:subscriptionU:2ActiveRecrord::Associations::HasOneAssociation ...

Reverting to r4770 fixes the problem for me. Anything later, it is broken. It looks like the fix for this ticket was only partial.

The relevant stack trace is:

You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
./script/../config/../vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:128:in `load_target'
./script/../config/../vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:29:in `respond_to?'
/usr/local/lib/ruby/1.8/pstore.rb:353:in `load'
/usr/local/lib/ruby/1.8/pstore.rb:306:in `transaction'
/usr/local/lib/ruby/1.8/cgi/session/pstore.rb:71:in `initialize'
/usr/local/lib/ruby/1.8/cgi/session.rb:273:in `initialize'
./script/../config/../vendor/rails/actionpack/lib/action_controller/cgi_process.rb:112:in `session'
./script/../config/../vendor/rails/actionpack/lib/action_controller/cgi_process.rb:142:in `stale_session_check!'
./script/../config/../vendor/rails/actionpack/lib/action_controller/cgi_process.rb:108:in `session'
./script/../config/../vendor/rails/actionpack/lib/action_controller/base.rb:979:in `assign_shortcuts_without_flash'
./script/../config/../vendor/rails/actionpack/lib/action_controller/flash.rb:140:in `assign_shortcuts'
./script/../config/../vendor/rails/actionpack/lib/action_controller/base.rb:419:in `process_without_filters'
./script/../config/../vendor/rails/actionpack/lib/action_controller/filters.rb:620:in `process_without_session_management_support'
./script/../config/../vendor/rails/actionpack/lib/action_controller/session_management.rb:114:in `process'
./script/../config/../vendor/rails/actionpack/lib/action_controller/base.rb:328:in `process'

09/24/06 20:41:58 changed by norbauer

For what it's worth, changing line 128 in association_proxy.rb r5171

from

if !loaded? and (!@owner.new_record? || foreign_key_present)

to

if loaded? == false and (!@owner.new_record? || foreign_key_present)

fixes the problem for me.

However, I'm having trouble figuring out how to run the built-in tests for Rails, so I can't tell whether this "fix" merely breaks other things.

(follow-up: ↓ 12 ) 09/24/06 22:12:27 changed by bitsweat

  • owner changed from David to xal.
  • status changed from reopened to new.
  • version set to edge.

Associations are cleared for AR instances stored in session. You should be able to formulate at test case by setting a record's has_one associate to nil, clearing the record's associations, and accessing the has_one.

09/24/06 22:12:33 changed by bitsweat

  • status changed from new to closed.
  • resolution set to untested.

(in reply to: ↑ 10 ) 09/24/06 23:15:04 changed by norbauer

Replying to bitsweat:

Associations are cleared for AR instances stored in session. You should be able to formulate at test case by setting a record's has_one associate to nil, clearing the record's associations, and accessing the has_one.

Hi Jeremy,

Did you mean I should run a test case to see if my changed code is breaking has_one associations, or that I should create a test case to show when the error is being created?

The former would be easier for me, but I'll be happy to give a crack at either. :) A bit of clarification on your instructions would be most appreciated.

r

(follow-up: ↓ 14 ) 09/25/06 01:27:28 changed by ulysses

He means please write a test to ensure what you have fixed does not break in the future. :-)

(in reply to: ↑ 13 ) 09/25/06 01:49:01 changed by norbauer

Replying to ulysses:

He means please write a test to ensure what you have fixed does not break in the future. :-)

Ok. Will do.

I figured out how to run the tests built into AR and they all seem to pass with my change. I'll write a test and submit the whole thing as a patch shortly.

thx Nicholas.

(follow-up: ↓ 16 ) 09/25/06 07:16:31 changed by bitsweat

  • cc set to bitsweat.

I couldn't reproduce the problem with the testcase above.

What exactly are you storing in session? The user instance?

What session store are you using?

(This change may have caused the problem, but this should really be a separate ticket..)

(in reply to: ↑ 15 ) 09/25/06 14:27:08 changed by norbauer

Replying to bitsweat:

I couldn't reproduce the problem with the testcase above. What exactly are you storing in session? The user instance? What session store are you using? (This change may have caused the problem, but this should really be a separate ticket..)

Hi Jeremy,

I've opened a new ticket (6279) with a specific formula for recreating a test case on your local machine.

09/25/06 19:11:34 changed by bitsweat

  • status changed from closed to reopened.
  • resolution deleted.

Thanks norbauer.

09/25/06 19:11:39 changed by bitsweat

  • status changed from reopened to closed.
  • resolution set to fixed.