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:in `test_update'
on 2007-01-10 17:39
on 2007-01-10 19:33
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)?
on 2007-01-10 19:40
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
on 2007-01-10 19:59
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. ;-) Hooray for tests!