views:

34

answers:

1

Here's a Ruby OO head scratcher for ya, brought about by this Rails scenario:

class Product < ActiveRecord::Base
  has_many(:prices)
  # define private helper methods 
end

module PrintProduct
  attr_accessor(:isbn)
  # override methods in ActiveRecord::Base
end

class Book < Product
  include PrintProduct
end

Product is the base class of all products. Books are kept in the products table via STI. The PrintProduct module brings some common behavior and state to descendants of Product. Book is used inside fields_for blocks in views. This works for me, but I found some odd behavior:

  • After form submission, inside my controller, if I call a method on a book that is defined in PrintProduct, and that method calls a helper method defined in Product, which in turn calls the prices method defined by has_many, I'll get an error complaining that Book#prices is not found.

Why is that? Book is a direct descendant of Product!

More interesting is the following..

As I developed this hierarchy PrintProduct started to become more of an abstract ActiveRecord::Base, so I thought it prudent to redefine everything as such:

class Product < ActiveRecord::Base
end

class PrintProduct < Product
end

class Book < PrintProduct
end

All method definitions, etc. are the same. In this case, however, my web form won't load because the attributes defined by attr_accessor (which are "virtual attributes" referenced by the form but not persisted in the DB) aren't found. I'll get an error saying that there is no method Book#isbn. Why is that?? I can't see a reason why the attr_accessor attributes are not found inside my form's fields_for block when PrintProduct is a class, but they are found when PrintProduct is a Module.

Any insight would be appreciated. I'm dying to know why these errors are occurring!

A: 

You might have better luck delaying the attr_accessor call in PrintProduct until mixin-time:

module PrintProduct
  def self.included(base)
    base.attr_accessor :isbn
  end
  # other instance methods here
end

The problem is likely something to do with timing of the attr_accessor call and how that applies to modules mixed in. I'm not certain that the timing is defined by the Ruby spec, so it might vary betweeen implementations or versions.

James A. Rosen
When PrintProduct is a module the attr_accessor declaration of #isbn works as expected. When PrintProduct is changed to a class #isbn is not found. Perhaps it would work if I did as you said using self.extended, but that's not the issue. I'm wondering why these errors occur because there doesn't seem to be anything "wrong" with either design (or maybe there is, and that's why the errors are occurring). Anyhow, thanks for the answer!
Chris