在Rails(4.1.5 / ruby 2.0.0p481 / win64)应用程序中,我在学生和课程之间存在多对多关系,并且有一个表示关联的连接模型StudentCourse,它具有一个名为&#的附加属性34;已启动",默认设置为" false"。
我还在student_id和course_id组成的连接表中添加了一个索引,并设置了一个唯一的检查,就像这样
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
现在我看到关联是通过以下方式创建的:
Student.first.courses.create(:name => "english")
或
Course.first.students << Student.first
这很好,我认为这是预期的行为。
我正在关注的是获取和设置&#34;开始&#34;的正确方法。属性。 当从其他模型访问该属性时,我看到一种奇怪的行为,而不是直接来自连接模型。
s = Student.create
c = Course.create(:name => "english")
s.student_courses.first
=&GT; | &#34;英语&#34; |假| #(表示为实用的表格)
s.student_courses.first.started = true
=&GT; | &#34;英语&#34; |是的|
s.save
=&GT;真
好的,这看起来已经保存但是当我掠夺ak:
StudentCourse.first
=&GT; | 1 | 1 | false |
因此,如果我查看学生嵌套属性,则设置为true,但在连接模型中它仍然是假的。我也尝试过#34;重装!&#34;但它没有任何区别,他们将瞄准自己不同的价值。
如果事情变得如此糟糕以至于价值实际上并未持续存在,我应该被告知而不是获得&#34; true&#34;在保存的时候,因为否则会有多么糟糕?我在这里缺少什么?
无论如何,如果我尝试修改&#34;开始&#34;直接在连接模型上的属性,我遇到了另一种问题:
StudentCourse.first.started = true
StudentCourse负载(1.0ms)SELECT&#34; student_courses&#34;。* FROM&#34; student_courses&#34;限制1 =&GT;真
StudentCourse.first.started
=&GT;假
它没有改变!
StudentCourse.find_by(:student_id => "10", :course_id => "1").started = true
=&GT;真
StudentCourse.find_by(:student_id => "10", :course_id => "1").started
=&GT;假
与之前相同..我尝试:
StudentCourse.find(1).started = true
ActiveRecord :: UnknownPrimaryKey:模型StudentCourse中的表student_courses的未知主键。
然后用:
sc = StudentCourse.first
sc.started = true
=&GT;真
sc
=&GT; | 1 | 1 |是的|
似乎很棒,但在保存时:
sc.save
(0.0ms)开始交易
SQL(1.0ms)UPDATE&#34; student_courses&#34; SET&#34;开始&#34; =?哪里 #&34; student_courses&#34;&#34;&#34; IS NULL [[&#34;已启动&#34;,&#34; true&#34;]] SQLite3 :: SQLException:没有这样的列:student_courses:UPDATE &#34; student_courses&#34; SET&#34;开始&#34; =?在哪里&#34; student_courses&#34;。&#34;&#34;一片空白 (1.0ms)回滚事务ActiveRecord :: StatementInvalid: SQLite3 :: SQLException:没有这样的列:student_courses:UPDATE &#34; student_courses&#34; SET&#34;开始&#34; =?在哪里&#34; student_courses&#34;。&#34;&#34; IS 来自的NULL C:/Ruby200-x64/lib/ruby/gems/2.0.0/gems/sqlite3-1.3.9-x64-mingw32/lib/sqlite3/database.rb:91:在 `初始化&#39;
所以我认为这都与没有主键有关 加入表
但我不确定如何使用它,如果它代表一个 我试图解决的案例的良好做法?
此外,如果这是问题,为什么我不会得到同样的警告
我在这之后拯救了学生
s.student_courses.first.started = true
,如示例所示
以上?
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(联接表的迁移)
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
答案 0 :(得分:4)
好的,我终于得到了这里发生的事情:
如果您使用#create_join_table
在迁移中创建联接表,则此方法将不创建名为&#34; id&#34;的默认主键。 (而不是为它添加索引)这是rails在使用#create_table
时默认执行的操作。
ActiveRecord需要一个主键来构建其查询,因为它是在执行Model.find(3)
之类的操作时默认使用的列。
此外,如果您认为可以通过执行StudentCourse.find_by(:course_id => "1", :student_id => "2").update_attributes(:started => true)
[0] 之类的事情来解决这个问题,那么它仍会失败,因为在找到记录之后,AR仍然会尝试更新它,看看&#34; id&#34;它找到的记录。
同样StudentCourse.find_by(:course_id => "1", :student_id => "2").started = true
将返回true,但当然在你调用#save之前它不会被保存。如果您将其分配给var relationship
,然后拨打relationship.save
,您会看到由于上述原因而无法保存。
<小时/> 的 [0] 强> 在联接表中,我不想要#34; student_id&#34;的重复记录。和&#34; course_id&#34;所以在迁移中我明确地为它们添加了一个唯一的约束(使用唯一索引)。
这让我觉得我不再需要一个唯一标识记录的主键了,因为我有这两个值......我认为在它们上添加一个索引就足以让它们作为主键工作了......但事实并非如此。当您不使用默认的&#34; id&#34;时,您需要明确定义主键。之一。
同样证明 Rails不支持复合主键,所以即使我想在这两个值上添加主键构建(所以使它们成为主键和唯一索引,如默认轨道&#34; id&#34;工作)这是不可能的。
存在的宝石:https://github.com/composite-primary-keys/composite_primary_keys
所以,故事的结尾,我修复它的方式只是将t.column :id, :primary_key
添加到用于创建连接表的迁移中。此外,我还没有使用#create_join_table
创建联接表,而只使用#create_table
(这将创建&#34; id&#34;自动&#34;)。
希望这有助于其他人。
同样this回答另一个问题非常有帮助,谢谢@Peter Alfvin!
答案 1 :(得分:1)
好的,您的联接表中似乎没有主键(我们很快就会收到确认)。尝试访问连接表时,您需要拥有主键。
我建议您迁移:
class CreateStudentCourses < ActiveRecord::Migration
def change
create_table :student_courses do |t|
t.references :course
t.references :student
t.boolean :started, default: false
t.timestamps
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
end
end
end
模型定义看起来很好,所以这是我能看到的唯一需要做的改变。
之后,做你正在做的事情应该正常工作。您将创建连接,然后在创建后访问它。如果要在创建时将布尔值赋值为true,则需要通过StudentCourse
模型创建记录,其中包含您需要的信息(student_id,course_id和started = true),而不是通过任何关联。
StudentCourse.create(course_id: course.id, student_id: student.id, started: true)
答案 2 :(得分:-1)
s = Student.create
c = Course.create(:name => "english")
s.student_courses.first.started = true
s.save
我认为这里的线索在你发布的第一行(如上所示)。 s是学生的一个实例,当你打电话给s.save时,你会要求学生保存对其属性的任何更改。但是,保存没有任何更改,因为您对关联进行了更改。
你有几个选择。如果您更喜欢代码段中的直接访问方法,则以下内容应该有效。
s = Student.create
c = Course.create(:name => 'english')
s.courses << c
s.student_courses.first.update_attributes(:started => true)
另一种方法是使用accepts_nested_attributes_for宏从学生的角度公开已启动的属性。
class Student
has_many :student_courses, :inverse_of => :student
has_many :courses, :through => :student_courses
accepts_nested_attributes_for :student_courses
end
s = Student.create
c = Course.create(:name => 'english')
s.courses << c
s.update_attributes(:student_courses_attributes=>[{:id => 1, :started => true}])