Take the 2-minute tour ×

I am working on an app that will manage students enrolled in a course. The app will have users who can log in and manipulate students. Users can also comment on students. So three of our main classes are Student, User, and Comment. The problem is that I need to associate individual comments with both of the other models: User and Student. So I've started with some basic code like this...

class Student < ActiveRecord::Base
  has_many :comments

class User < ActiveRecord::Base
  has_many :comments

class Comment < ActiveRecord::Base
  belongs_to :student
  belongs_to :user
  attr_accessible :comment

So in the comments table, a single record would have the following fields:


This presents several problems. First, the nice Rails syntax for creating associated objects breaks down. If I want to make a new comment, I have to choose between the foreign keys. So...




Another option is to arbitrarily pick one of the foreign keys, and manually add it to the attrs hash. So...

User.comments.create(:comment => "Lorem ipsum", :student_id => 1)

The problem with this option is that I have to list student_id under attr_accessible in my Comment model. But my understanding is that this poses a security risk since someone could technically come along and reassociate the comment with a different student using mass assignment.

This leads to a further question about data modeling in general using Rails. The app I'm currently building in Rails is one that I originally wrote in PHP/MySQL a few years ago. When I first studied SQL, great importance was placed on the idea of normalization. So, for example, if you have a contacts table which stores names and addresses, you would use a lot of foreign key relationships to avoid repeating data. If you have a state column, you wouldn't want to list the states directly. Otherwise you could potentially have thousands of rows that all contain string values like "Texas." Much better to have a separate states table and associate it with your contacts table using foreign key relationships. My understanding of good SQL theory was that any values which could be repeating should be separated into their own tables. Of course, in order to fully normalize the database, you would likely end up with quite a few foreign keys in the contacts table. (state_id, gender_id, etc.)

So how does one go about this in "the Rails way"?

For clarification (sorry, I know this is getting long) I have considered two other common approaches: "has_many :through =>" and polymorphic associations. As best I can tell, neither solves the above stated problem. Here's why:

"has_many :through =>" works fine in a case like a blog. So we have Comment, Article, and User models. Users have many Comments through Articles. (Such an example appears in Beginning Rails 3 from Apress. Great book, by the way.) The problem is that for this to work (if I'm not mistaken) each article has to belong to a specific user. In my case (where my Student model is here analogous to Article) no single user owns a student. So I can't say that a User has many comments through Students. There could be multiple users commenting on the same student.

Lastly, we have polymorphic associations. This works great for multiple foreign keys assuming that no one record needs to belong to more than one foreign class. In RailsCasts episode #154, Ryan Bates gives an example where comments could belong to articles OR photos OR events. But what if a single comment needs to belong more than one?

So in summary, I can make my User, Student, Comment scenario work by manually assigning one or both foreign keys, but this does not solve the issue of attr_accessible.

Thanks in advance for any advice!

share|improve this question
add comment

3 Answers

up vote 4 down vote accepted

I had your EXACT question when I started with rails. How to set two associations neatly in the create method while ensuring the association_ids are protected.

Wukerplank is right - you can't set the second association through mass assignment, but you can still assign the association_id directly in a new line.

This type of association assignment is very common and is littered throughout my code, since there are many situations where one object has more than one association.

Also, to be clear: Polymorphic associations and has_many :through will not solve your situation at all. You have two separate associations (the 'owner' of a comment and the 'subject' of a comment) - they can't be rationalised into one.

EDIT: Here's how you should do it:

@student = Student.find_by_id(params[:id])
@comment = @student.comments.build(params[:comment]) #First association is set here
@comment.user = current_user #Second association is set here
if @comment.save
  # ...
  # ...

By using the Object.associations.build, Rails automatically creates a new 'association' object and associates it with Object when you save it.

share|improve this answer
EDIT: I should say that Wukerplank is wrong about the Polymorphic associations - you don't need to use this at all. Barring the first sentence though, his answer is spot on - his code example should be exactly what you need. –  Rob d'Apice May 12 '11 at 12:43
OK, thanks for the info. Could you elaborate more on your idea of assigning the association_id on a new line? I'd like to see an example of that. Are you saying something like @comment.student = @student followed by @comment.user = @user followed by @comment.save? I agree, Wukerplank's code looks good, but it doesn't include any association with the User model. –  joshukraine May 12 '11 at 12:54
Hi Josh - edited my answer with code that should get you there. Let me know if this doesn't work. –  Rob d'Apice May 12 '11 at 13:02
Hey Rob, OK that looks really good. And am I correct in assuming then that I can keep the foreign key fields out of attr_accessible? Thanks so much! –  joshukraine May 12 '11 at 13:10
Yes, neither of the foreign keys need to be accessible for this to work. Glad it's helped you out! –  Rob d'Apice May 12 '11 at 13:17
add comment

I think polymorphic association is the way to go. I'd recommend using a plugin instead of "rolling your own". I had great results with ActsAsCommentable (on Github).

As for your attr_accessible problem: You are right, it's more secure to do this. But it doesn't inhibit what you are trying to do.

I assume that you have something that holds the current user, in my example current_user

@student = Student.find(params[:id])
@comment = Comment.new(params[:comment]) # <= mass assignment
@comment.student = @student              # <= no mass assignment
@comment.user    = current_user          # <= no mass assignment
if @comment.save
  # ...
  # ...

The attr_accessible protects you from somebody sneaking a params[:comment][:student_id] in, but it won't prevent the attribute from being set another way.

You still can get all comments of your users through the has_many :comments association, but you can also display who commented on a student thanks to the belongs_to :user association:

<h1><%= @student.name %></h1>


<%- @student.comments.each do |comment| -%>
    <p><%= comment.text %><br />
    by <%= comment.user.name %></p>
<%- end -%>


Don't over engineer your app. Having a state:string field is perfectly fine unless you want to do something meaningful with a State object, like storing all districts and counties. But if all you need to know a students state, a text field is perfectly fine. This is also true for gender and such.

share|improve this answer
Thanks for the reply. I'm happy to go with a plugin, but I still don't see how the polymorphic approach can solve my problem. If a single comment is associated with a student, for example, then how would I also associate it with a user? In looking at examples of polymorphic tables, I only see one class column. So if I associate it with a Student, I then have nowhere to record the User id. Maybe this is something that ActsAsCommentable solves? –  joshukraine May 12 '11 at 12:37
Are users and students different things or is every student a user? Do you have more "person like" entities like professors or staff? –  Wukerplank May 12 '11 at 12:48
Users and students are different. So for example, Bob is a student who's personal data is recorded in the app, but Bob has no access to the app or even knowledge of it. (The app is a completely internal utility.) Bill and Sara are users who log in to the app to manage students like Bob. Bill (user) leaves a comment on Bob's (student) page saying "Hey, no one mailed out Bob's new textbook." To which Sara (user) replies, "OK, I'll send it tomorrow." So Bill's comment belongs to him and also to the student record for Bob. Sara's reply belongs to her and also to the same student record (Bob). –  joshukraine May 12 '11 at 13:00
OK, now I get it. So the Student is a commentable object and the User is the commenting object. If you have more objects that can be commented on (subjects, class rooms, etc.) I would strongly urge you to use a polymorphic association like ActsAsCommentable does. I also updated my answer. –  Wukerplank May 12 '11 at 13:24
Thanks for the info. I'll definitely check into it. I looked at a migration sample from ActsAsCommentable, and noticed that in addition to the classic polymorphic fields (like commentable_id and commentable_type) there was also a user_id field. So I assume that even though it's polymorphic, it's still storing multiple foreign keys in the same record. –  joshukraine May 12 '11 at 13:36
add comment

Well, for the first part. If I understood your question correctly, I think, that since comments are listed within students controller, you should associate them through Student model (it just seems logical to me). However, to protect it from assigning wrong user id, you could do something like this

@student = Student.find params[:id]
@student.comments.create :user => current_user

current_user might be a helper that does User.find session[:user_id] or something like that.

has_many :through association doesn't make sense here. You could use it to associate User and Student through Comment, but not User and Comment through Student

Hope that helps.

share|improve this answer
OK, that sounds good. I do have a current_user method, so no problem there. Are you suggesting that instead of saying a Comment belongs_to a User, I should just list the user as a field in the comments table? That seems OK, but then what if I want to search for all comments made by a particular user? Would just run the search with some kind of scope matching all records that contain that user's id? Thanks again for the info! –  joshukraine May 12 '11 at 12:35
No, mate. Having a Comment belongs_to User association is perfectly fine. You don't need to remove it. –  bassneck May 12 '11 at 13:14
add comment

Your Answer


