访问存储在连接表的连接模型的属性与#create_join_table创建 [英] Accessing a join-model attribute stored in a join table created with #create_join_table

查看:166
本文介绍了访问存储在连接表的连接模型的属性与#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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆