Am I reinventing polymorphic associations?

Am I reinventing polymorphic associations?

I have a fairly standard blog with comments model, with the following additions:

(1) Multiple models can accept comments (blog post, bug report, etc).

(2) Each group of comments has a list of subscribers that will be emailed when a new comment is posted.

Requirement (1) leads me to a polymorphic association. But I can't see how to fit requirement (2) into that. Each group of comments needs some place to store the list of subscribers.

So I added a CommentGroup table.

class BlogPost < ActiveRecord::Base   belongs_to :comment_group end

class BugReport < ActiveRecord::Base   belongs_to :comment_group end

class CommentGroup < ActiveRecord::Base   has_one :blog_post   has_one :bug_report # note: one or the other will be nil

  has_and_belongs_to_many :subscribers end

class Comment < ActiveRecord::Base   belongs_to :comment_group end

I think you are. I'd make Comment polymorphic, a nested set, and has_many :subscribers, which should be based on the top parent of the comment thread.

-eric

A nested set seems a little overkill.

I realized I could make two polymorphic associations: one in Comments, and one in the join table for commentable_subscribers.

I'm not sure if that's a better plan or not, though. I like that I can enforce foreign key constraints when I use the extra CommentGroup table.

Scott Johnson wrote:

A nested set seems a little overkill.

It's not overkill at all if you're threading your comments.

I realized I could make two polymorphic associations: one in Comments, and one in the join table for commentable_subscribers.

I'm not sure if that's a better plan or not, though. I like that I can enforce foreign key constraints when I use the extra CommentGroup table.

Why not keep CommentGroup and have a polymorphic association that will bind it to either BlogPost or BugReport?

Best,

Scott Johnson wrote: > A nested set seems a little overkill.

It's not overkill at all if you're threading your comments.

Good point, and I hadn't thought of that, but I don't need nesting.

> I realized I could make two polymorphic associations: one in Comments, > and one in the join table for commentable_subscribers.

> I'm not sure if that's a better plan or not, though. I like that I can > enforce foreign key constraints when I use the extra CommentGroup > table.

Why not keep CommentGroup and have a polymorphic association that will bind it to either BlogPost or BugReport?

That sounds like the best plan. Thanks for the help.

I ended up refactoring this to remove CommentGroup. The controller code for CommentGroup was too ugly.

Here's how my associations ended up:

class User < ActiveRecord::Base   has_many :subscriptions end class Comment < ActiveRecord::Base   belongs_to :author, :class_name => "User"   belongs_to :commentable, :polymorphic => true end class Subscription < ActiveRecord::Base   belongs_to :subscribable, :polymorphic => true   belongs_to :user end class BlogPost < ActiveRecord::Base   has_many :subscriptions, :as => :subscribable   has_many :subscribers, :through => :subscriptions, :source => :user   has_many :comments, :as => :commentable end

Subscribable and Commentable are basically the same thing.

Then I added routes that appear as nested routes for any of the commentable/subscribable types:

  map.comments(":commentable_type/:commentable_id/comments",                :controller => :comments,                :action => :create,                :conditions => {:method => :post}                )

  map.subscribers(":subscribable_type/:subscribable_id/subscribers",                   :controller => :subscribers,                   :action => :update,                   :conditions => {:method => :post}                   )   map.subscribers_table(":subscribable_type/:subscribable_id/ subscribers/table",                         :controller => :subscribers,                         :action => :table,                         :conditions => {:method => :get}                         )

Then I have controllers and views for Comments and Subscribers that operate on commentable or subscribable and don't care what type they are.

To enable an additional model class as commentable (and subscribable) I just add the three has_many lines (like BlogPost), and add the comments partial to the appropriate view.

Next: how can I DRY up those three has_many lines? I want to put something like 'acts_as_commentable' in the model and have it do those three has_many lines.

Scott Johnson wrote: [...]

Next: how can I DRY up those three has_many lines? I want to put something like 'acts_as_commentable' in the model and have it do those three has_many lines.

Define it as an ActiveRecord::Base class method.

Best,

Sure, but where? In lib/* somewhere? I'll need it to get loaded before any of these models get loaded. (Or I would have to add a 'require' to each commentable model.)

Scott Johnson wrote:

Define it as an ActiveRecord::Base class method.

Sure, but where? In lib/* somewhere? I'll need it to get loaded before any of these models get loaded. (Or I would have to add a 'require' to each commentable model.)

You have a number of choices:

1. /lib/commentable.rb module Commentable   def self.included(base)     base.has_many :subscriptions, :as => :subscribable     # and so on   end end

/app/models/blog_post.rb class BlogPost < AR::B   include Commentable

  # rest of class definition end

2. /lib/commentable.rb # as in 1, but after the end of the module (or in environment.rb): class ActiveRecord::Base   def self.acts_as_commentable     include Commentable   end end

/app/models/blog_post.rb class BlogPost < AR::B   acts_as_commentable end

3 (probably not recommended). /config/environment.rb class ActiveRecord::Base   def self.acts_as_commentable     has_many :subscriptions, ;as => :subscribable     # and the other has_manys   end end

4. Write an acts_as plugin (but call it something else -- I think acts_as_commentable is already taken as a plugin name).

Does that help?

Best,

Many thanks... #2 is what I was thinking, but I don't understand how lib/commentable.rb would get loaded in that case?

Scott Johnson wrote:

Many thanks... #2 is what I was thinking, but I don't understand how lib/commentable.rb would get loaded in that case?

On Oct 23, 1:12 pm, Marnen Laibow-Koser <rails-mailing-l...@andreas-

Ah, good point -- I've never done it that way, so I didn't think of that. Rails' autoloading might take care of it, but in development mode maybe not. I'll have to check.

Best,

In most projects I'm using an initializer to load all lib/*.rb files, like:

config/initializers/requires.rb:

# auto-load all ruby files from RAILS_ROOT/lib/ Dir[File.join(RAILS_ROOT, 'lib', '*.rb')].each do |file|   require file

Note, if you have any files you do not want auto-loaded, you certainly don't want to use that initializer :). Alternate option would be to create a very simple plugin:

vender/plugins/acts_as_commentable/init.rb:

class ActiveRecord::Base   def self.acts_as_commentable     include Commentable   end end

This should work as well.

- lukas