Modules vs. Classes and their influence on descendants of ActiveRecord::Base
Here's a Ruby OO head scratcher for ya, brought about by 开发者_StackOverflowthis 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 inProduct
, which in turn calls theprices
method defined byhas_many
, I'll get an error complaining thatBook#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!
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.
精彩评论