ActiveRecord Error?

We spent a big chunk of yesterday hunting down a strange bug in one of
our rails apps and created a test project to try out different theories
on what was going on. The result so far is a very small test case app
that perfectly reproduces the problem. The question is, is there
anything wrong with what we’re doing here, or is it a problem with
ActiveRecord? We’re on rails 1.1.6 at the moment, so if this is a known
bug that’s fixed in 1.2, just club me over the head with the trac
reference.

Given the following two models…

class Gadget < ActiveRecord::Base
has_one :widget
end

class Widget < ActiveRecord::Base
belongs_to :gadget
def save
gadget.save
super
end
end

and the following test…

class WidgetTest < Test::Unit::TestCase
fixtures :widgets, :gadgets

Replace this with your real tests.

def test_update
w = Widget.find(1)
assert_equal(w,w.gadget.widget)
w.save
end
end

Obviously this is a contrived example (why would anyone every want to
call w.gadget.widget?). However, it’s a greatly boiled down version of
our problem app that has many models and a long series of methods
calling each other from different models. Calling w.gadget.widget in
this example doesn’t make any sense, however, to the best of my
knowledge it’s not illegal either.

Given the above, if I comment out either the ‘gadget.save’ line in
widget.rb, or the ‘… w.gadget.widget)’ assert line in the test,
the test runs successfully. However, with both lines left in place, we
get the following error from deep within the bowels of ActiveRecord.
So, my question is, is there a Rails bug here, or are we doing something
fundamentally inappropriate?

  1. Error:
    test_update(WidgetTest):
    ArgumentError: wrong number of arguments (1 for 0)

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`save’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`send’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`method_missing’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:349:in
`callback’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:346:in
`callback’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:341:in
`each’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:341:in
`callback’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:254:in
`create_or_update’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1392:in
`save_without_validation’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:736:in
`save_without_transactions’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:126:in
`save’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/database_statements.rb:51:in
`transaction’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:91:in
`transaction’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:118:in
`transaction’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:126:in
`save’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`send’

/usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`method_missing’

/home/jgarvin/workspace/test_case/config/…/app/models/widget.rb:5:in
save' ./test/unit/widget_test.rb:10:intest_update’

The most immediate problem is that you’ve overriden “save” but you
haven’t matched the method signature of the superclass. Despite what the
documentation says, “save” actually takes an argument (a boolean
parameter that indicates whether validations are being performed).

If you change the method to look something like this:

def save(perform_validations = true)
gadget.save
super
end

or better yet, this:

def save(*args)
gadget.save
super
end

the problem should disappear.

I think a more fundamental problem might be that you’ve overriden the
save method at all - I don’t think it is supposed to be overriden. Have
you tried using one of the save hooks instead (before_save, after_save)?

Hi

IMVHO is always better not to play redefining such method like save.
Anyway I suspect sometime behind the scene the save method can be
passed an argument.
In your code the save method is defined that way

def save
gadget.save
super
end

so it won’t accept any argument and will raise

ArgumentError: wrong number of arguments (1 for 0)

when 1 argument is passed.

I’d try to redefine the method this way

def save(*args)
gadget.save
super(*args)
end

I’m not 100% sure and you may need some further inspection, but really
smells like you’ve got this kind of problem.

Paolo

HA! That’s it. adding (*args) solved the problem instantly.

I agree that overriding the save was questionable, but there’s a rather
complex series of things and conditions happening on save and at the
time this code was written, that was what worked for us. It wasn’t
until yesterday that some other code caused this code to break, and our
wonderful tests identified the problem immediately, long before we
deployed the revision. :wink: Hooray for tests!