views:

145

answers:

4

Right now I have an initializer that does this:

ActiveRecord::Base.send :has_many, :notes, :as => :notable ActiveRecord::Base.send :accepts_nested_attributes_for, :notes

It builds the association just fine, except when I load a view that uses it, the second load gives me: can't dup NilClass from:

/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2184:in `dup'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2184:in `scoped_methods'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2188:in `current_scoped_methods'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2171:in `scoped?'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2439:in `send'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2439:in `initialize'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/reflection.rb:162:in `new'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/reflection.rb:162:in `build_association'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_collection.rb:423:in `build_record'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_collection.rb:102:in `build'
(my app)/controllers/manifests_controller.rb:21:in `show'

Any ideas? Am I doing this the wrong way? Interestingly if I move the association onto just the model I'm working with at the moment, I don't get this error. I figure I must be building the global association incorrectly.

A: 

take a look at unloadable it may help you

Salil
That blog post was interesting, but it didn't solve my problems. See my comments on the initial question as to why :)
joshsz
A: 

It's recommended to make each association in each model! It's a useless DRY way to make such things! At all, It's my opinion!

amrnt
+5  A: 

You state that you have many models, all of which require this association. If it were me, I'd would go with the approach of creating a base model class that contains the association and then have all the other models inherit from it. Something like:

class NotableModel < ActiveRecord::Base

  # Prevents ActiveRecord from looking for a database table for this class
  self.abstract_class = true

  has_many :notes, :as => :notable
  accepts_nested_attributes_for :notes  
end

class Foo < NotableModel
  ...
end

class Bar < NotableModel
  ...
end

In my opinion this approach is more self-documenting compared to using a little bit of metaprogramming hidden away in an initializer.

John Topley
I agree - explicitly define this in a base class or an included module.
Jonathan Julian
This method works, but it changes the inheritance chain. Generally that's fine, but I like the DRYer method
joshsz
This is DRY; what do you think is being repeated?
John Topley
@joshsz, this is DRY and far better than duck punching ActiveRecord::Base
macek
Especially because this new base class can be extended with other methods or properties that might be useful for all of your models.
Tilendor
If this application didn't have the requirements I've enumerated in comments to my answer below, this would absolutely be the right way to do this. It's only because this is a common practice for this application that I consider my method more 'correct'. FWIW, it is in practice repeating yourself to change the base class for a model from ActiveRecord::Base to NotableModel. Also it is technically possible (although admittedly unlikely) in this case for a developer to create a model and then forget to change its parent class, thereby not following the design tenets.
joshsz
A: 

Thanks to Rich Kilmer (of InfoEther), we found the elegant (and slightly opaque) way of fixing this:

# config/initializers/has_many_notes.rb
module ActiveRecord
  class Base
    def self.inherited(klass)
      super
      klass.send :has_many, :notes, :as => :notable
      klass.send :accepts_nested_attributes_for, :notes
    end
  end
end

Now no inheritance changes, and it's very DRY

joshsz
@joshsz, chances are, you'll encounter a model that you don't want to be `Notable` at some point. This is no less DRY than @John Topley's method. Plus, if you're so hung up on being DRY, you should also adhere to **Convention Over Configuration**. Your method is cryptic and non-self-documenting.
macek
DRY is not what you are doing. Every model currently inherits from ActiveRecord::Base. Making it inherit from a NotableModel does not add any repetition at all, its the exact same amount.Moving this Model related code away from the model folder is not intuitive at all. It absolutely works, don't get me wrong, but thinking of someone else approaching your code, I would have to do some hunting to find this. Inheritance exists to solve exactly this kind of problem. Inheritance should be preferred to monkey patching.
Tilendor
This is the correct solution for this project, but only for the following reasons:1) This project has other full codebase cross-cutting concerns (events, for example) that are handled in the same way.2) The tenets of this domain state that *everything* must always be notable, even (potentially) notes. Whether we surface the functionality per-model is a decision to be made later, but still a possibility.3) It's not uncommon to extend core classes. If you consider this a core extension, it's suddenly a little less weird.
joshsz