In the Rails application (4.1.5 / ruby ββ2.0.0p481 / win64), I have a many-to-many relationship between Student and Course, and the StudentCourse join model, which is an association that has the additional βstartβ attribute, which set to false by default.
I also added an index to the connection table made from student_id and course_id, and set a unique check for it, like this
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
Now I see that associations are created either:
Student.first.courses.create(:name => "english")
or
Course.first.students << Student.first
This is good, and I guess this is the expected behavior.
What I care for is the right way to get and set the "start" attribute. I see strange behavior when accessing this attribute from other models, and not directly from the join model.
s = Student.create c = Course.create(:name => "english") s.student_courses.first
=> | "English" | false | # (presented as a table for practicality)
s.student_courses.first.started = true
=> | "English" | true |
s.save
=> true
Ok, it looks like it was saved, but when I mine ak:
StudentCourse.first
=> | 1 | 1 | false |
Thus, it is set to true if I look at the studentβs nested attributes, but it is still erroneous in the connection model. I also tried doing a "reboot"! but it doesnβt matter, and they will mantle their own value.
If something goes so bad that the values ββare not really saved, I need to say, instead of getting the βtruthβ when saving, because otherwise how bad can the consequences be? What am I missing here?
In any case, if I try to change the "start" attribute in the connection model directly, I run into another problem:
StudentCourse.first.started = true
Student download (1.0ms) SELECT "student_courses". * FROM "student_courses" LIMIT 1 => true
StudentCourse.first.started
=> false
He has not changed!
StudentCourse.find_by(:student_id => "10", :course_id => "1").started = true
=> true
StudentCourse.find_by(:student_id => "10", :course_id => "1").started
=> false
Same as before .. I try:
StudentCourse.find(1).started = true
ActiveRecord :: UnknownPrimaryKey: unknown primary key for student_courses table in StudentCourse model.
Then with:
sc = StudentCourse.first sc.started = true
=> true
sc
=> | 1 | 1 | true |
seems great, but when saved:
sc.save
(0.0ms) start a transaction
SQL (1.0ms) UPDATE "student_courses" SET "start" =? WHERE "student_courses". "IS NULL [[" start "," true "]] SQLite3 :: SQLException: there is no such column: student_courses .: UPDATE" student_courses "SET" start "=? WHERE" student_courses "." "NULL (1.0 ms) rollback transaction ActiveRecord :: StatementInvalid: SQLite3 :: SQLException: there is no such column: student_courses .: UPDATE "student_courses" SET "start" =? WHERE is "student_cursses". " "NULL from C: /Ruby200-x64/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.9-x64-mingw32/lib/sqlite3/database.rb: 91: in` Initialization '
So, I think all this is due to the lack of a primary key in the join table?
But I'm not sure how to use it, and if this is a good practice for the case I'm trying to solve?
Also, if this is a problem, why then I do not get the same warning here when I save the student after I do s.student_courses.first.started = true , as shown in the examples above?
Code
student.rb
class Student < ActiveRecord::Base has_many :student_courses has_many :courses, :through => :student_courses end
course.rb
class Course < ActiveRecord::Base has_many :student_courses has_many :students, :through => :student_courses end
student_course.rb
class StudentCourse < ActiveRecord::Base belongs_to :course belongs_to :student end
schema.rb
ActiveRecord::Schema.define(version: 20141020135702) do create_table "student_courses", id: false, force: true do |t| t.integer "course_id", null: false t.integer "student_id", null: false t.string "started", limit: 8, default: "pending", null: false end add_index "student_courses", ["course_id", "student_id"], name: "by_course_and_student", unique: true create_table "courses", force: true do |t| t.string "name", limit: 50, null: false t.datetime "created_at" t.datetime "updated_at" end create_table "students", force: true do |t| t.string "name", limit: 50, null: false t.datetime "created_at" t.datetime "updated_at" end end
create_join_table.rb (transfer for connection table)
class CreateJoinTable < ActiveRecord::Migration def change create_join_table :courses, :students, table_name: :student_courses do |t| t.index [:course_id, :student_id], :unique => true, :name => 'by_course_and_student' t.boolean :started, :null => false, :default => false end end end