Reputation: 19688
I have a pretty standard devise user model schema with roles and a many to many model for userRoles:
#teacher or student
create_table "roles", :force => true do |t|
t.string "name"
end
create_table "user_roles", :force => true do |t|
t.integer "user_id"
t.integer "role_id"
end
add_index "user_roles", ["role_id"], :name => "index_user_roles_on_role_id"
add_index "user_roles", ["user_id"], :name => "index_user_roles_on_user_id"
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
........more stuff....
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
end
The roles are teacher and student. I want to create a Clazz
(as in school class) model with many to many students (users with role of students) and many to many teachers (users with role of teachers).
I think it would look something like this, but can't quite get it:
rails g scaffold Clazz name:string time:datetime has_many:users(of type student) has_many:users(of type teacher)
How do I do the last two? Do I create the has_many, :through association migrations (teacherClass and studentClass) first? or is there a helper method?
Maybe I just couldn't find an appropriate tutorial or example, so i could use one of those as well... Thanks in advance
This is more information about my attempt at @Cody Caughlan 's answer
Schema.rb :
ActiveRecord::Schema.define(:version => 20130523111519) do
create_table "class_instructions", :force => true do |t|
t.string "name"
t.datetime "time"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "class_instructions", ["user_id"], :name => "index_class_instructions_on_user_id"
create_table "roles", :force => true do |t|
t.string "name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "songs", :force => true do |t|
t.string "title"
t.string "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "user_id"
t.string "fractionRepresentation"
t.string "measureRepresentation"
end
create_table "user_roles", :force => true do |t|
t.integer "user_id"
t.integer "role_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "user_roles", ["role_id"], :name => "index_user_roles_on_role_id"
add_index "user_roles", ["user_id"], :name => "index_user_roles_on_user_id"
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
end
User.rb model :
class User < ActiveRecord::Base
#DEVISE STUFF
#== Associations
has_many :user_roles, :dependent => :destroy
has_many :roles, :through => :user_roles
has_many :songs
#== Instance Methods
def thisUsersID
self.id
end
def student_classes(class_instruction_name)
member_classes(class_instruction_name, 'Student')
end
def teacher_classes(class_instruction_name)
member_classes(class_instruction_name, Role::TEACHER)
end
private
def member_classes(class_instruction_name, type)
ClassInstruction \
.joins(:user_role) \
.where(["user_role.user_id = ?", id]) \
.joins("INNER JOIN roles ON roles.id = user_roles.role_id") \
.where("roles.name = ?", type) \
.where("class_instruction.name = ?", class_instruction_name)
end
end
user_role.rb model:
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
has_many :class_instructions, :dependent => :destroy
end
role.rb model
class Role < ActiveRecord::Base
has_many :user_roles, :dependent => :destroy
end
class_instruction.rb model (names are 'ss' and'math-101')
class ClassInstruction < ActiveRecord::Base
belongs_to :user_role
end
Error Stack
Loading development environment (Rails 3.2.2)
1.9.3-p429 :001 > U = User.find(3)
User Load (3.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]]
=> #<User id: 3, email: "[email protected]", encrypted_password: "$2a$10$2DxWWV34BRFJoLboYyWCIeXEtCPYOSe8JqpTmFU6W2i1...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil>
1.9.3-p429 :002 > U.student_classes('math-101').all
ClassInstruction Load (0.2ms) SELECT "class_instructions".* FROM "class_instructions" INNER JOIN "user_roles" ON "user_roles"."id" = "class_instructions"."user_role_id" INNER JOIN roles ON roles.id = user_roles.role_id WHERE (user_role.user_id = 3) AND (roles.name = 'Student') AND (class_instruction.name = 'math-101')
SQLite3::SQLException: no such column: user_role.user_id: SELECT "class_instructions".* FROM "class_instructions" INNER JOIN "user_roles" ON "user_roles"."id" = "class_instructions"."user_role_id" INNER JOIN roles ON roles.id = user_roles.role_id WHERE (user_role.user_id = 3) AND (roles.name = 'Student') AND (class_instruction.name = 'math-101')
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: user_role.user_id: SELECT "class_instructions".* FROM "class_instructions" INNER JOIN "user_roles" ON "user_roles"."id" = "class_instructions"."user_role_id" INNER JOIN roles ON roles.id = user_roles.role_id WHERE (user_role.user_id = 3) AND (roles.name = 'Student') AND (class_instruction.name = 'math-101')
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/sqlite3-1.3.7/lib/sqlite3/database.rb:91:in `initialize'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/sqlite3-1.3.7/lib/sqlite3/database.rb:91:in `new'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/sqlite3-1.3.7/lib/sqlite3/database.rb:91:in `prepare'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/sqlite_adapter.rb:246:in `block in exec_query'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:280:in `block in log'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activesupport-3.2.2/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:275:in `log'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/sqlite_adapter.rb:242:in `exec_query'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/sqlite_adapter.rb:460:in `select'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/abstract/database_statements.rb:18:in `select_all'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/connection_adapters/abstract/query_cache.rb:63:in `select_all'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/querying.rb:38:in `block in find_by_sql'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/explain.rb:40:in `logging_query_plan'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/querying.rb:37:in `find_by_sql'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/relation.rb:171:in `exec_queries'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/relation.rb:160:in `block in to_a'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/explain.rb:33:in `logging_query_plan'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/relation.rb:159:in `to_a'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/activerecord-3.2.2/lib/active_record/relation/finder_methods.rb:159:in `all'
from (irb):2
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/railties-3.2.2/lib/rails/commands/console.rb:47:in `start'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/railties-3.2.2/lib/rails/commands/console.rb:8:in `start'
from /Users/phycom06/.rvm/gems/ruby-1.9.3-p429/gems/railties-3.2.2/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'1.9.3-p429 :003 >
Upvotes: 0
Views: 1658
Reputation: 32748
Firstly, Class
is a reserved word in Ruby so you will not be able to use it as a Model name.
http://www.java2s.com/Code/Ruby/Language-Basics/Rubysreservedwords.htm
So pick a different name. In cases like this I have done Clazz
.
As for the data model - yes you would specify the relationships in the ActiveRecord class definition and not in the migration.
Its not clear what your Clazz
model looks like. But I will assume it has 2 columns: name
and a user_role_id
. You'd probably want to pull out the name into a separate table and have it be replaced with like a clazz_id
- but for now we will keep it flat.
I will use an instance method to build the associations, but you might be able to get crazy in the has_many
definitions.
class Clazz < ActiveRecord::Base
belongs_to :user_role
end
class User
#== Associations
has_many :user_roles, :dependent => :destroy
has_many :roles, :through => :user_roles
#== Instance Methods
def student_classes(clazz_name)
member_classes(clazz_name, Role::STUDENT)
end
def teacher_classes(clazz_name)
member_classes(clazz_name, Role::TEACHER)
end
private
def member_classes(clazz_name, type)
Clazz \
.joins(:user_role) \
.where(["user_role.user_id = ?", id]) \
.joins("INNER JOIN roles ON roles.id = user_roles.role_id") \
.where("roles.name = ?", type) \
.where("clazzes.name = ?", clazz_name)
end
end
class UserRole
has_many :clazzes, :dependent => :destroy
end
class Role
has_many :user_roles, :dependent => :destroy
end
So this will enable you to do something like:
user = User.find(99) # we know this user is a student
user.student_classes('Social Studies 101').all
Something like that.
Upvotes: 3