Reputation: 10902
I have three models:
User(name: string)
Course(name: string)
Assoc(user_id: integer, ta_id: integer, teach_id: integer, student_id: integer)
I want to be able to associate a user with a course, as a ta, a teacher, or a student. I am not sure this matches the :polymorphic idea in ActiveRecord. Here's what I have so far, but it's not quite working:
class User < ApplicationRecord
has_many :tas, class_name: "Assoc", foreign_key: :ta_id
has_many :teaches, class_name: "Assoc", foreign_key: :teach_id
has_many :takes, class_name: "Assoc", foreign_key: :student_id
has_many :ta_courses, through: :tas
has_many :taken_courses, through: :tas
has_many :taught_courses, through: :tas
end
It's not working:
irb(main):056:0> User.find(1).ta_courses
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Traceback (most recent call last):
1: from (irb):56
ActiveRecord::HasManyThroughSourceAssociationNotFoundError (Could not find the source association(s) "ta_course" or :ta_courses in model Assoc. Try 'has_many :ta_courses, :through => :tas, :source => <name>'. Is it one of ?)
irb(main):057:0>
Any pointers would be appreciated!
Upvotes: 1
Views: 166
Reputation: 102343
If I really had to be able to query all the user -> course relations as a single table I would set it up as so:
# rails g model assoc user:belongs_to course:belongs_to
class Assoc < ApplicationRecord
enum role: [:student, :teacher, :assistent]
belongs_to :user
belongs_to :course
end
We then want to setup scoped assocations in User and Course for each role:
has_many :student_assocs, -> { where(role: :student) }
class_name: 'Assoc'
Since we want to have the exact same assocations in both lets keep it DRY by using a module:
app/models/concerns/associated.rb
# dynamically creates assocations for each role in Assoc.roles enum
module Associated
extend ActiveSupport::Concern
included do
# the plain base assocation
has_many :assocs
# this creates the assocations student_assocs, teacher_assocs, etc
Assoc.roles.keys.each do |role|
# We need to use eval for the second argument as we are creating the lambda dynamically
has_many :"#{role}_assocs", eval( "->{ where(role: #{Assoc.roles[role]})}" ),
class_name: 'Assoc'
end
end
end
Assoc.roles
gives a hash of the enum mappings we set up in Assoc.
We can then include our module in Course and User and setup the indirect assocations:
class Course < ApplicationRecord
include Associated
has_many :users, through: :assocs
# this creates the assocations students, teachers, etc
Assoc.roles.keys.each do |role|
has_many role.pluralize.to_sym,
through: "#{role}_assocs".to_sym,
source: :user
end
end
class User < ApplicationRecord
include Associated
has_many :courses, through: :assocs
# this creates the assocations courses_as_student, courses_as_teacher, etc
Assoc.roles.keys.each do |role|
has_many "course_as_#{role}".to_sym,
through: "#{role}_assocs".to_sym,
source: :course
end
end
Upvotes: 1
Reputation: 71
It looks like you haven't built the relationships from the Assoc model to the Course model. Your Assoc model should have a course_id so it would look like:
Assoc(user_id: integer, ta_id: integer, teach_id: integer, student_id: integer, course_id: integer)
Then you would need a belongs_to relationship on the Assoc model:
class Assoc < ApplicationRecord
belongs_to :course
end
Finally, your has_many through relationships in the User model aren't built correctly (all are through tas). Your error message gives you a clue as to the last thing you need to do, which is identify the aliased relationship using source:
class User < ApplicationRecord
has_many :tas, class_name: "Assoc", foreign_key: :ta_id
has_many :teaches, class_name: "Assoc", foreign_key: :teach_id
has_many :takes, class_name: "Assoc", foreign_key: :student_id
has_many :ta_courses, through: :tas, source: :course
has_many :taught_courses, through: :teaches, source: :course
has_many :taken_courses, through: :takes, source: :course
end
Upvotes: 1