Michael Daines
2005-May-02 21:35 UTC
Mixing in functionality in ActiveRecord? (was Re: has_many and belongs_to associations...)
> but it just seems to add model complexity, not functionality.Well, I''d rather have a bit of generalized complexity in the model that I could wrap up in a module or something than double the number of tables in my database. In fact, this is what I''m attempting to do at present, but I''ve hit a little bit of a snag. I''ve written an attach_file function for one of the classes that will have attachments, the Newsletter class, and I have a matching association that that function uses, which is generalized... has_many :attachments, :foreign_key => ''record_id'', :conditions => ''record_table = \''#{self.class.name.tableize}\'''' def attach_file(file) ... end [code that supports attach_file] I''d like to be able to put this in my classes that need attachments, but what I''ve attempted to do doesn''t work: require ''has_attachments'' class Newsletter < ActiveRecord::Base include HasAttachments ... end has_attachments.rb is in my lib/ directory, and it defines a module with the contents of the first snippet. The trouble seems to be the has_many association: if I put this in the Newsletter class instead of the HasAttachments module, everything works.
David Goodlad
2005-May-02 22:29 UTC
Re: Mixing in functionality in ActiveRecord? (was Re: has_many and belongs_to associations...)
On 5/2/05, Michael Daines <mdaines-fVOoFLC7IWo@public.gmane.org> wrote:> > but it just seems to add model complexity, not functionality. > > Well, I''d rather have a bit of generalized complexity in the model that > I could wrap up in a module or something than double the number of > tables in my database. > > In fact, this is what I''m attempting to do at present, but I''ve hit a > little bit of a snag. > > I''ve written an attach_file function for one of the classes that will > have attachments, the Newsletter class, and I have a matching > association that that function uses, which is generalized... > > has_many :attachments, :foreign_key => ''record_id'', :conditions => > ''record_table = \''#{self.class.name.tableize}\'''' > > def attach_file(file) > > ... > > end > > [code that supports attach_file] > > I''d like to be able to put this in my classes that need attachments, > but what I''ve attempted to do doesn''t work: > > require ''has_attachments'' > > class Newsletter < ActiveRecord::Base > > include HasAttachments > > ... > > end > > has_attachments.rb is in my lib/ directory, and it defines a module > with the contents of the first snippet. The trouble seems to be the > has_many association: if I put this in the Newsletter class instead of > the HasAttachments module, everything works. > > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >It looks to me like you''re trying to call ''has_many'' within your module, which simply won''t work. What I would do, instead, is a little bit of meta programming magic. I''d add a method to the ActiveRecord::Base class called ''has_attachments'', for example. This method would have code to add methods like ''attach_file'' to whatever class it''s called from. It would also create the necessary relationship to the attachments table. Check out http://poignantguide.net/dwemthy/ if you''d like a great example of how to do some meta programming. Dave -- Dave Goodlad dgoodlad-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org or dave-eHfbeeWWzZOw5LPnMra/2Q@public.gmane.org http://david.goodlad.ca/
On May 2, 2005, at 2:35 PM, Michael Daines wrote:> Well, I''d rather have a bit of generalized complexity in the model > that I could wrap up in a module or something than double the > number of tables in my database. > > In fact, this is what I''m attempting to do at present, but I''ve hit > a little bit of a snag. > > I''ve written an attach_file function for one of the classes that > will have attachments, the Newsletter class, and I have a matching > association that that function uses, which is generalized...Ruby saves the day. When a module Foo is included in a class Bar, Foo.append_features is called with Bar as the argument. You can use this "callback" to do more advanced stuff like: module HasAttachments def self.append_features(base) super base.has_many :attachments, :foreign_key => ''record_id'', :conditions => ''record_table = \''#{base.name.tableize}\'''' end def attach_file(file) # ... end end An even more AR-like way would define a has_attachments class method on AR::Base that wraps this up for you. Since Ruby allows you to reopen classes, throw this in your has_attachments.rb: class ActiveRecord::Base def self.has_attachments(attr_name = :attachments, options = {}) # Set up defaults. options = { :foreign_key = ''record_id'' }.merge(options) # Manage potential conditions collision. record_table_condition = "record_table = ''#{name.tableize}''" options[:conditions] = if options.has_key?(:conditions) "#{options[:conditions]} AND #{record_table_condition}" else record_table_condition end # Make the has_many association. has_many attr_name, options # Include attach_file and friends. include HasAttachments end end Despite all this, the number of tables in your database shouldn''t be a huge concern. Having an attachments table for each applicable model looks non-DRY, but joins on primary keys are more efficient and you get referential integrity for free. Best, jeremy