Skip to main content

Users/Classroom Associations

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

Popular posts from this blog

Fade Inline-Block with CSS and Velocity JS

I'm using Velocity JS to fade a list object. The problem is that Velocity applies display: list-item; opacity: 1; to the element and overrides my display: inline-block; and my list is now vertical. How can I force it to always use inline-block? https://jsfiddle.net/01qvqmxa/ HTML test test test CSS li { opacity: 0; display: inline-block; padding: 1em; } JS $(".myList li").velocity("fadeIn", { delay: 300, duration: 2000 }); Solved Try using the !important notation: li { opacity: 0; display: inline-block !important; padding: 1em; } revised fiddle

Converting categorical variables into numbers

I have a categorical variable educa that needs to be converted into numbers for readability. educa can take on 6 values. I want to convert them into 1-6. How do I do this? This doesn't work: brfs2013educationsummarycleaned % mutate( educa_level = ifelse(educa == "Never attended school or only kindergarten", 1, educat == "Grades 1 through 8 (Elementary)", 2, "not a real category")) What am I doing wrong? The above is a portion of what I intend to do. Solved We can use match if the values in 'educa' have to be converted based on the order in which they appear in the data library(tidyverse) brfs2013educationsummary %>% mutate(educa_level = match(educa, unique(educa))) as.numeric(educa) will do what you want and if you want it as new column brfs2013educationsummarycleaned % mutate(educa_level = as.numeric(educa))