I'm a fairly new to rails, and I'm trying to create an app where users can own many classrooms, and classrooms can have many users. I'm not sure how to set up the models, however.
Let’s say I have a teacher named Joe. Joe has many classrooms. Each of Joe’s classrooms can have many students, so from those many classrooms Joe also has many students. But I also want Joe to be able to be part of a classroom as a student, and I want students to be able to create their own classrooms, as teachers, from the same account.
Classrooms should also be able to have more than one teacher.
I also want to be able to do something like user.classrooms.first.users, where user is Joe, and users are his students in his first classroom.
What models should I create, and how would I set up the associations?
I was thinking has_and_belongs_to_many, but apparently it's not preferred anymore, and has_many :through would be better. What it's looking like to me is something like:
class User
has_many :classroom_users
has_many :classrooms, :through => :classroom_users
end
class Classroom
has_many :classroom_users
has_many :users, :through => :classroom_users
end
class Classroom_User
belongs_to :classroom
belongs_to :user
end
Is this correct? I took it from here. Also, how should I mirror these models in the database with migrations?
Solved
What you've got at the moment will work for the case of associating users with classrooms, but not teachers as well, since at the moment the table to associate the two models can only signify one type of relationship.
NB: Rails expects model names to be singular and without underscores i.e. ClassroomUser instead of Classroom_Users in your example.
To associate teachers with classrooms, one approach would be to create an extra joining model:
user.rb:
class User < ActiveRecord::Base
has_many :classroom_teachers
has_many :classroom_students
has_many :teaching_classrooms, through: :classroom_teachers
has_many :attending_classrooms, through: :classroom_students
end
classroom.rb:
class Classroom < ActiveRecord::Base
has_many :classroom_teachers
has_many :classroom_students
has_many :teachers, through: :classroom_teachers
has_many :students, through: :classroom_students
end
classroom_student.rb:
class ClassroomStudent < ActiveRecord::Base
belongs_to :student, class_name: 'User', foreign_key: 'user_id'
belongs_to :attending_classroom, class_name: 'Classroom', foreign_key: 'classroom_id'
end
classroom_teacher.rb:
class ClassroomTeacher < ActiveRecord::Base
belongs_to :teacher, class_name: 'User', foreign_key: 'user_id'
belongs_to :teaching_classroom, class_name: 'Classroom', foreign_key: 'classroom_id'
end
Rails normally works out what type of model relates to a field based on the name of the field, e.g. a users field will link to a collection of User models. With the above schema, Rails can't infer the types of the models from the names of the associating fields, since it doesn't know that a teacher is an alias for a user. To overcome this, the class_name attributes define the model types of the joining fields.
For the same reason, Rails needs some guidance knowing what database key relates to which field, which is what the foreign_key attribute is for.
And finally the migration:
class CreateClassroomUsers < ActiveRecord::Migration
def change
create_table :users do |t|
end
create_table :classrooms do |t|
end
create_table :classroom_students do |t|
t.belongs_to :user, index: true
t.belongs_to :classroom, index: true
end
create_table :classroom_teachers do |t|
t.belongs_to :user, index: true
t.belongs_to :classroom, index: true
end
end
end
Edit:
Alternatively, instead of using two joining models, you could add an extra field to the ClassroomUser model that you originally had, describing the role of the user (e.g. an enum that can be either student or teacher). That would allow more roles to be added in the future and might be easier to query than my previous suggestion. For example, to check whether a user is a student or teacher you'd need only one query:
example_user.classroom_users
and could then check the role fields on the returned ClassroomUser records. See this question for an example of that approach.
Comments
Post a Comment