访问存储在连接表的连接模型的属性与#create_join_table创建 [英] Accessing a join-model attribute stored in a join table created with #create_join_table
问题描述
在一个Rails(4.1.5 /红宝石2.0.0p481 / Win64上)应用程序,我有很多一对多的学生和课程并重新$ P $联接模型StudentCourse关系psents的关联,它有一个附加属性叫做开始,这是默认的设置假。
我也开始加入在取得的STUDENT_ID和COURSE_ID的连接表的索引,并设置在该一个独特支票,这样
t.index [:STUDENT_ID,:COURSE_ID]:独特=>如此,:名称=> by_student_and_course
现在我看到,协会是由要么做创建:
Student.first.courses.create(:名称=>中英文)
或
Course.first.students<< Student.first
这是罚款,这是预期的行为,我想。
我在找后是正确的方式来获取和设置的启动属性。 我看到来自其他车型访问时属性,而不是直接从连接模型的异常行为。
S = Student.create
C = Course.create(:名称=>中英文)
s.student_courses.first
=> | 英语|假| #(重新psented作为practicity表$ P $)
s.student_courses.first.started =真
=> | 英语|真|
s.save
=>真正
确定这看起来像它已被保存,但是当我战利品AK:
StudentCourse.first
=> | 1 | 1 |假|
因此,它是在真实的设定,如果我去通过学生嵌套的属性,但它仍然是假的加盟模式。我也试着做重装!但它没有什么区别,他们将mantaint自己不同的价值。
如果有什么东西会如此糟糕,价值实际上并没有坚持我应该被告知,而不是保存时得到真,否则怎么不好可能是这样做的后果是什么?我是什么在这里失踪?
无论如何,如果我尝试修改了开始直接属性的加盟模式,我遇到另一种问题:
StudentCourse.first.started =真
StudentCourse负荷(1.0ms的)选择student_courses。* FROMstudent_coursesLIMIT 1 =>真正
StudentCourse.first.started
=>假
这并没有改变!
StudentCourse.find_by(:STUDENT_ID =>中10:COURSE_ID =>中1)。开始=真
=>真正
StudentCourse.find_by(:STUDENT_ID =>中10:COURSE_ID =>中1)。开始
=>假
和以前一样。我尝试用:
StudentCourse.find(1).started =真
的ActiveRecord :: UnknownPrimaryKey:未知主键模型StudentCourse表student_courses
然后用
SC = StudentCourse.first
sc.started =真
=>真正
SC
=> | 1 | 1 |真|
看起来不错,但在保存时:
sc.save
(0.0ms)开始交易。
SQL(1.0ms的)UPDATEstudent_coursesSET开始=?哪里 student_courses。IS NULL [[开始,真] :: sqlite3的的SQLException:没有这样的列:student_courses:UPDATE student_coursesSET开始=? WHEREstudent_courses。IS NULL (1.0ms的)回滚事务的ActiveRecord :: StatementInvalid: :: sqlite3的的SQLException:没有这样的列:student_courses:UPDATE student_coursesSET开始=? WHEREstudent_courses。是 从空 C:/Ruby200-x64/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.9-x64-mingw32/lib/sqlite3/database.rb:91:在 `初始化
-
所以我觉得这一切,是因为有没有在主键 加入表?
-
但我不知道足够的关于如何使用它,如果想重新present一个 因为我试图解决的情况下,好的做法呢?
-
另外,如果是这样的问题,那么为什么我没有得到同样的警告 在这里,当我保存的学生后,我做的
s.student_courses.first.started = TRUE
,如图所示的例子 上面的?
code
student.rb
班学生和LT;的ActiveRecord :: Base的
的has_many:student_courses
的has_many:课程:通过=> :student_courses
结束
course.rb
类课程<的ActiveRecord :: Base的
的has_many:student_courses
的has_many:学生:通过=> :student_courses
结束
student_course.rb
类StudentCourse<的ActiveRecord :: Base的
belongs_to的:当然
belongs_to的:学生
结束
schema.rb
的ActiveRecord :: Schema.define(版本:20141020135702)办
CREATE_TABLEstudent_courses,ID:假的,力:真正做| T |
t.integerCOURSE_ID,空:假的
t.integerSTUDENT_ID,空:假的
t.string开始,限制:8,默认:待定,空:假的
结束
add_indexstudent_courses,[COURSE_ID,STUDENT_ID],名称:by_course_and_student,独特的:真正的
CREATE_TABLE课程,力:真正做| T |
t.string名,限制:50,空:假的
t.datetimecreated_at
t.datetime的updated_at
结束
CREATE_TABLE学生,力:真正做| T |
t.string名,限制:50,空:假的
t.datetimecreated_at
t.datetime的updated_at
结束
结束
create_join_table.rb(迁移的连接表)
类CreateJoinTable< ActiveRecord的::迁移
高清变化
create_join_table:课程:学生,TABLE_NAME:student_courses办| T |
t.index [:COURSE_ID,:STUDENT_ID]:独特=>如此,:名称=> by_course_and_student
t.boolean:开始:空=>假:默认=>假
结束
结束
结束
好吧,我终于得到了什么回事:
如果您创建使用迁移连接表 #create_join_table
,此方法将不可以创建一个名为ID的默认主键(而不是增加这就是轨道时默认使用做 #create_table
。
ActiveRecord的需要主键生成的查询,因为它是,它会做这样的事情时默认使用列Model.find(3)
。
另外,如果你认为你可以做一些像 StudentCourse.find_by解决这个问题(:COURSE_ID =>中1,:STUDENT_ID =>中2)update_attributes的(:开始= >真)
[0] 它仍然会失败,因为记录后,它的发现,AR仍会尝试更新它看记录的身份证,它找到。
还有 StudentCourse.find_by(:COURSE_ID =>中1,:STUDENT_ID =>中2)。开始= TRUE
将retrun真实的,但它当然不保存,直到调用#save就可以了。如果你将它分配给一个变种相关
然后调用 relationship.save
你会看到它将无法保存由于上述原因。
[0] 在连接表我没有为STUDENT_ID和COURSE_ID所以在迁移我已经明确地增加了一个唯一约束他们想要的(使用唯一索引)重复的记录。
这使我觉得我没有必要再一个主键来唯一标识一个纪录,因为我有这两个值...我想这对他们添加索引,就足以让他们工作,作为主键......但事实并非如此。你需要明确地定义,当你不使用默认的ID一主键。
另外原来,的Rails不支持复合主键等,即使我想添加一个主键建立在这两个值(这样使它们的主键和独特的指数,如默认情况下轨ID的作品),它会不会成为可能。
一个创业板存在: https://github.com/composite-primary-keys/ composite_primary_keys
所以,故事的结局,的方式,我不动它被简单地添加 t.column:ID,:primary_key
为迁移连接表的创建。此外,我也还没有创建的连接表 #create_join_table
,但使用而不是仅仅 #create_table
(这将创建一个 ID为自动)。
希望这可以帮助别人。
此外这回答另一个问题是非常有益的,谢谢@Peter Alfvin!
In a Rails ( 4.1.5 / ruby 2.0.0p481 / win64 ) application I have a many-to-many relationship between Student and Course and a join model StudentCourse which represents the association, which has an additional attribute called "started", which is set by default on "false".
I also have added an index in the join table made of the student_id and the course_id, and set a unique check on that, like this
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
Now I see that associations are created by either doing:
Student.first.courses.create(:name => "english")
or
Course.first.students << Student.first
This is fine and it's the expected behaviour, I suppose.
What I am looking after is the correct way to get and set the "started" attribute. I am seeing an odd behaviour when accessing that attribute from the other models and not straight from the join model.
s = Student.create
c = Course.create(:name => "english")
s.student_courses.first
=> | "english" | false | # (represented as a table for practicity)
s.student_courses.first.started = true
=> | "english" | true |
s.save
=> true
Ok this looks like it has been saved but when I loot ak:
StudentCourse.first
=> | 1 | 1 | false |
So it is set on true if I go through the student nested attributes, but it's still false in the join model. I also tried doing "reload!" but it makes no difference and they will mantaint their own different value.
If something is going so bad that values are not actually persisted I should be told instead of getting "true" when saving, because otherwise how bad could be the consequences of this ? What am I missing here?
Anyway, if I try modifying the "started" attribute on the join model directly, I meet another kind of problem:
StudentCourse.first.started = true
StudentCourse Load (1.0ms) SELECT "student_courses".* FROM "student_courses" LIMIT 1 => true
StudentCourse.first.started
=> false
It 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 with:
StudentCourse.find(1).started = true
ActiveRecord::UnknownPrimaryKey: Unknown primary key for table student_courses in model StudentCourse.
Then with:
sc = StudentCourse.first
sc.started = true
=> true
sc
=> | 1 | 1 | true |
seems great but when saving:
sc.save
(0.0ms) begin transaction
SQL (1.0ms) UPDATE "student_courses" SET "started" = ? WHERE "student_courses"."" IS NULL [["started", "true"]] SQLite3::SQLException: no such column: student_courses.: UPDATE "student_courses" SET "started" = ? WHERE "student_courses"."" IS NULL (1.0ms) rollback transaction ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: student_courses.: UPDATE "student_courses" SET "started" = ? WHERE "student_courses"."" IS NULL from C:/Ruby200-x64/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.9-x64-mingw32/lib/sqlite3/database.rb:91:in `initialize'
So I think this all has to do with not having a primary key in join-table?
But I am not sure enough on how to use it and if that'd represent a good practice for the case I am trying to solve ?
Also, if this is the problem, why then I don't 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 (migration for join 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
Ok I finally got what was going on here:
If you create a join table in a migration using #create_join_table
, this method will not create the default primary key called "id" (and not add an index for it) which is what rails does by default when using #create_table
.
ActiveRecord needs a primary key to build its queries, because it is the column that it will be used by default when doing things like Model.find(3)
.
Also if you think you can get around this by doing something like StudentCourse.find_by(:course_id => "1", :student_id => "2").update_attributes(:started => true)
[0] it will still fail, because after the record it's found, AR will still try to update it looking at the "id" of the record it found.
Also StudentCourse.find_by(:course_id => "1", :student_id => "2").started = true
will retrun true but of course it is not saved until you call #save on it. If you assign it to a var relationship
and then you call relationship.save
you will see it will fail to save for the above reasons.
[0] In the join table I didn't want duplicate records for a "student_id" and "course_id" so in the migration I had explicitely added a unique constraint for them (using unique index).
This led me to think that I did not need anymore a primary key to uniquely identify a record, because I had those two values... I thought that adding an index on them was enough for they to work as a primary key... but it is not. You need to explicitely define a primary-key when you are not using the default "id" one.
Also turns out that Rails does not support composite primary keys and so even if I wanted to add a primary key build on those two values (so making them primary-key and unique-index, like default rails "id" works) it would have not been possible.
A gem for that exists: https://github.com/composite-primary-keys/composite_primary_keys
So, end of the story, the way I fixed it was simply adding t.column :id, :primary_key
to the migration for the join table creation. Also I could have not created the join table with #create_join_table
but instead using just #create_table
(which would create an "id" automatically").
Hope this helps someone else.
Also this answer to another question was very helpful, thank you @Peter Alfvin !
这篇关于访问存储在连接表的连接模型的属性与#create_join_table创建的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!