views:

191

answers:

5

Hi Stack Overflowers: I'm building a Ruby on Rails application that has several different models (e.g. Movie, Song, Photo) that I am storing movie clips, mp3s and photos. I'd like for users to be able to comment on any of those Models and have control over which comments are published.

Is the best practice to create a Comment model with:

belongs_to :movie
belongs_to :song
belongs_to :photo

And then tie each Model with:

has_many :comments

Then, I'm guessing in the Comment table, I'll need a foreign key for each Model:

comment, movie_id, song_id, photo_id

Is this the correct way to build something like this, or is there a better way? Thanks in advance for your help.

A: 

I have no RoR Experience, but in this case you'd probably better off using inheritance on the database level, assuming your dbms supports this:

CREATE TABLE item (int id, ...);
CREATE TABLE movie (...) INHERITS (item);
CREATE TABLE song (...) INHERITS (item);
[...]
CREATE TABLE comments (int id, int item_id REFERENCES item(id));

Another approach could be a single table with a type column:

CREATE TABLE item (int id, int type...);
CREATE TABLE comments (int id, int item_id REFERENCES item(id));

As expressed before, I can't tell you how to exactly implement this using RoR.

Jan Jungnickel
+4  A: 

Use acts_as_commentable. It creates a comment table with a commentable_type (model name of the commented-upon item) and commentable_id (the model's ID). Then all you need to do in your models:

class Photo < ActiveRecord::Base
  acts_as_commentable
end
Sarah Mei
The reason I don't like this approach, is because it ties your model name into the database records. If you end up changing the name of your model, it's not just as simple as altering the table name. You have to remember to change every entry in the commentable_type table to match your change.
berlin.ab
If you're changing the name of a model, the cost of writing a db migration to update the commentable_type column is trivial compared to the cost of changing your code.
Sarah Mei
+1  A: 

Create a table to hold the relationships for each type of comment:

movie_comments, song_comments, photo_comments

and then use:

class Movie < ActiveRecord::Base

  has_many :movie_comments
  has_many :comments, :through => :movie_comments

end

class MovieComment < ActiveRecord::Base
  include CommentRelationship
  belongs_to :comment
  belongs_to :movie
end

You can use a module (CommentRelationship) to hold all of the common functionality between your relationship tables (movie_comments)

This approach allows for the flexibility to be able to treat your comments differently depending on the type, while allowing for similar functionality between each. Also, you don't end up with tons of NULL entries in each column:

comment            | movie_id | photo_id | song_id
----------------------------------------------------
Some comment            10         null      null
Some other comment     null        23        null

Those nulls are definitely a sign you should structure your database differently.

berlin.ab
A: 

The best idea is probably to do what Sarah suggests and use one of the existing plugins that handle commenting.

If you wish to roll your own, or just understand what happens under the covers, you need to read about Single Table Inheritance, the way Rails handles inheritance. Basically, you need a single comments table ala:

# db/migrate/xxx_create_comments
create_table :comments do |t|
  t.string :type, :null => false
  t.references :movies, :songs, :photos
end

Now you can define your comment types as

class Comment < ActiveRecord::Base
  validates_presence_of :body, :author
  # shared validations go here
end

class SongComment < Comment
  belongs_to :song
end

class MovieComment < Comment
  belongs_to :movie
end

class PhotoComment < Comment
  belongs_to :photo
end

All your comments will be stored in a single table, comments, but PhotoComment.all only returns comments for which type == "Photo".

Daniel Schierbeck
Is there any way to define what is stored as the type, other than the name of the subclass? Example: t.references { :movies => 1, :photos => 2, :songs => 3 } and PhotoComment.all would be type == 2
berlin.ab
Not to my knowledge, but why would you do that?
Daniel Schierbeck
+1  A: 

Personally I would model it this way:

Media table (media_id, type_id, content, ...)
.
MediaType table (type_id, description, ... )
.
MediaComments table ( comment_id, media_id, comment_text, ...)

After all, there is no difference to the database between a Song, Movie, or Photo. It's all just binary data. With this model you can add new "media types" without having to re-code. Add a new "MediaType" record, toss the data in the Media table.

Much more flexible that way.

Ron

Ron Savage
This is a smart idea and makes changes easier.
Tilendor