Linus
Linus

Reputation: 4783

Rails belongs_to through association

I'm trying to add a new model to an existing model mesh. The existing one works perfectly but I can't get the new one to work properly and am wondering if the association is able to work the way I'm trying to make it work. Update: As I just got asked: belongs_to through was something I've read while gooling about the problem. If it doesn't exist, would has_one through be the correct way? I tried it as well but it also didn't work.

Here is the existing mesh:

class Course
  has_many :users, through: :enrollments
  has_many :enrollments
end

class User
  has_many :courses, through: :enrollments
  has_many :enrollments
end

class Enrollment
  belongs_to :course
  belongs_to :user

  # has fields :user_id, :course_id
end

Now a user should be able to rate a course he's completed. (If he has, there is an enrollment with his id and a course id.) I thought it would be best to write it as follows:

class Course
  has_many :users, through: :enrollments
  has_many :enrollments
  has_many :ratings, through: :enrollments
end

class User
  has_many :courses, through: :enrollments
  has_many :enrollments
  has_many :ratings, through: :enrollments
end

class Enrollment
  belongs_to :course
  belongs_to :user
  has_one :rating

  # has fields :user_id, :course_id
end

class Rating
  belongs_to :enrollment
  belongs_to :course, through: :enrollment
  belongs_to :user, through: :enrollment
end

When I try to create a Rating in the console, I get the following error:

User.first.ratings.create(text: "test", course_id: Course.first.id)
ArgumentError: Unknown key: through

Update When I use has_one through insted, I get the following error:

ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection: Cannot modify association 'User#ratings' because the source reflection class 'Rating' is associated to 'Enrolment' via :has_one.

Is it possible to do it this way at all? Thanks!

Upvotes: 1

Views: 5384

Answers (2)

waqar mirza
waqar mirza

Reputation: 555

class Course
  has_many :users, through: :enrollments
  has_many :enrollments
  has_many :ratings, through: :enrollments
end

class User
  has_many :courses, through: :enrollments
  has_many :enrollments
  has_many :ratings, through: :enrollments
end

class Enrollment
  belongs_to :course
  belongs_to :user
  belongs_to :rating

  # has fields :user_id, :course_id, rating_id
end

class Rating
  has_one :enrollment
  has_one :course, through: :enrollment
  has_one :user, through: :enrollment
end

Note: Add foreignkey columns

And if you there is just one/two columns in ratings table merge them into enrollments like this.

class Course
  has_many :users, through: :enrollments
  has_many :enrollments
end

class User
  has_many :courses, through: :enrollments
  has_many :enrollments
end

class Enrollment
  belongs_to :course
  belongs_to :user

  # has fields :user_id, :course_id, rating-columns...
end

Upvotes: 4

Richard Peck
Richard Peck

Reputation: 76784

Structure

Maybe you're complicating this too much

class Enrollment
  belongs_to :course
  belongs_to :user
end

This means you have a join model which stores unique records, referencing both course and user. Your ratings are on a per user and course basis?

Why don't you just include rating as an attribute of your enrolment model?:

#enrolments
id | user_id | course_id | rating | created_at | updated_at

If you give rating a numeric value (1 - 5), it will give you the ability to rate the different enrolments like this:

user = User.first
course = Course.first

user.enrolments.create(course: course, rating: 5)

--

Ratings

This is, of course, based on your current model structure.

If you want to include ratings for courses by users (not tied to enrolment), you may wish to use a join model called course_ratings or similar:

#app/models/user.rb
Class User < ActiveRecord::Base
   has_many :enrolments
   has_many :courses, through: :enrolments
   has_many :ratings, through: :courses, class_name: "CourseRating"
end

#app/models/course.rb
Class Course < ActiveRecord::Base
   has_many :enrolments
   has_many :students, through: :enrolments, class_name: "User"
   has_many :ratings, class_name: "CourseRating"
end

#app/models/course_rating.rb
Class CourseRating < ActiveRecord::Base
   belongs_to :course
   belongs_to :user
end

This will allow you to call:

user = User.first
user.courses.first.ratings

Upvotes: 2

Related Questions