访问存储在使用#create_join_table创建的连接表中的连接模型属性

时间:2014-11-05 18:00:13

标签: ruby-on-rails ruby activerecord many-to-many nested-attributes

在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

3 个答案:

答案 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}])