【问题标题】:Rails Nested Attributes properly assigning parent_id as index, but isn't assigning additional attributesRails嵌套属性正确地将parent_id分配为索引,但没有分配其他属性
【发布时间】:2017-02-09 13:59:44
【问题描述】:

我一直致力于实现一个新模型,该模型属于我们的一个网络应用现有模型。最终,我想构建一个嵌套表单。我知道表单和强大的参数可能有它自己的一系列问题,但我目前正在努力让模型按照我在 Rails 控制台中的预期行为。

Rails 4.2.7,Postgres 数据库

更新 - 2016 年 10 月 3 日 - 仍在尝试找到正确的解决方案,但已进行了一些更改

我们的工作涉及学校和学区,这个特殊案例涉及调查以及如何将调查分配给学校和学区。到目前为止,已将调查分配给具有 SurveyAssignment 模型的学区,并且一些下线逻辑假设一个学区中的所有学校也被“分配”到该调查。现在,我们希望能够为 SurveyAssignment 添加更多粒度,并在学校级别允许一些特殊性。

所以我创建了一个 SchoolSurveyAssignment 模型并开始着手准备。

以下是相关型号信息:

class District < ActiveRecord::Base
  ...
  has_many :schools, dependent: :destroy
  has_many :survey_assignments, dependent: :destroy
  ...
end

class School
  ...
  belongs_to :district
  has_many :school_survey_assignments
  has_many :survey_assignments, :through => :school_survey_assignments

  ...
end

class SurveyAssignment
  belongs_to :district
  belongs_to :survey
  has_one :survey_version, through: :survey
  has_many :school_survey_assignments, inverse_of: survey_assignment
  has_many :schools, :through => :school_survey_assignments

  accepts_nested_attributes_for :school_survey_assignments

  attr_accessor :survey_group, :survey_version_type, :survey_version_id, :school_survey_assignments_attributes
  validates :survey_id, presence: true
end 

class SchoolSurveyAssignment
  belongs_to :survey_assignment, inverse_of: :school_survey_assignments
  belongs_to :school

  attr_accessor :school_id, :survey_assignment_id, :grades_affected, :ulc_affected
  validates_presence_of :survey_assignment
  validates :school_id, presence: true, uniqueness: {scope: :survey_assignment_id}
end

相关控制器代码:

class SurveyAssignmentsController < ApplicationController
  before_action :set_district
  before_action :set_survey_assignment, only: [:show, :edit, :update, :destroy]

  respond_to :html, :json, :js

  def new
    @new_survey_assignment = SurveyAssignment.new()
    @district.schools.each do |school|
      @new_survey_assignment.school_survey_assignments.build(school_id: school.id)
    end
  end

  def create
    @survey_assignment = SurveyAssignment.new(survey_assignment_params)
    if @survey_assignment.save
      flash[:notice] = "Survey successfully assigned to #{@district.name}"
    else
      flash[:alert] = "There was a problem assigning this survey to #{@district.name}"
    end
    redirect_to district_survey_assignments_path(@district)
  end

  def survey_assignment_params
    params.require(:survey_assignment).permit(:survey_id, :status, :survey_version_id, school_survey_assignments_attributes: [:id, :survey_assignment_id, :school_id, grades_affected: [], ulc_affected: []]).tap do |p|
      p[:district_id] = @district.id
      p[:school_year] = session[:selected_year]
    end
  end

  def set_district
    @district = District.find(params[:district_id])
  end

这是相关的架构信息:

create_table "school_survey_assignments", force: :cascade do |t|
  t.integer "survey_assignment_id"
  t.integer "school_id"
  t.integer "grades_affected",      default: [], array: true
  t.string  "ulc_affected",         default: [], array: true
 end

add_index "school_survey_assignments", ["school_id"], name: "index_school_survey_assignments_on_school_id", using: :btree
add_index "school_survey_assignments", ["survey_assignment_id"], name: "index_school_survey_assignments_on_survey_assignment_id", using: :btree

create_table "survey_assignments", force: :cascade do |t|
  t.integer  "district_id"
  t.integer  "survey_id"
  t.integer  "status"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.integer  "school_year"
  t.integer  "last_response_status_id"
end

add_index "survey_assignments", ["district_id"], name: "index_survey_assignments_on_district_id", using: :btree

一旦这些都到位,我就进入了我的 Rails 控制台并尝试了以下操作:

2.3.1 :002 > sa1 = SurveyAssignment.create(district_id: 3, survey_id: 508, school_year: 2017)
  (0.2ms)  BEGIN
 SQL (0.7ms)  INSERT INTO "survey_assignments" ("district_id", "survey_id", "school_year", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["district_id", 3], ["survey_id", 508], ["school_year", 2017], ["created_at", "2016-09-30 21:30:20.205144"], ["updated_at", "2016-09-30 21:30:20.205144"]]
  (7.2ms)  COMMIT
=> #<SurveyAssignment id: 369, district_id: 3, survey_id: 508, status: nil, created_at: "2016-09-30 21:30:20", updated_at: "2016-09-30 21:30:20", school_year: 2017, last_response_status_id: nil>
2.3.1 :003 > sa2 = SurveyAssignment.create(district_id: 3, survey_id: 508, school_year: 2017)
  (0.3ms)  BEGIN
 SQL (0.4ms)  INSERT INTO "survey_assignments" ("district_id", "survey_id", "school_year", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["district_id", 3], ["survey_id", 508], ["school_year", 2017], ["created_at", "2016-09-30 21:30:30.701197"], ["updated_at", "2016-09-30 21:30:30.701197"]]
  (0.5ms)  COMMIT
=> #<SurveyAssignment id: 370, district_id: 3, survey_id: 508, status: nil, created_at: "2016-09-30 21:30:30", updated_at: "2016-09-30 21:30:30", school_year: 2017, last_response_status_id: nil>

所以现在,我已经成功创建了两个调查作业。我现在要从 sa1 创建两个学校调查作业:

2.3.1 :004 > [{school_id: 5}, {school_id: 6}].each do |ssa|
2.3.1 :005 >     sa1.school_survey_assignments.create(ssa)
2.3.1 :006?>   end
  (0.2ms)  BEGIN
 SchoolSurveyAssignment Exists (2.4ms)  SELECT  1 AS one FROM "school_survey_assignments" WHERE ("school_survey_assignments"."school_id" = 5 AND "school_survey_assignments"."survey_assignment_id" = 369) LIMIT 1
 SQL (0.4ms)  INSERT INTO "school_survey_assignments" ("survey_assignment_id") VALUES ($1) RETURNING "id"  [["survey_assignment_id", 369]]
  (6.4ms)  COMMIT
  (0.6ms)  BEGIN
 SchoolSurveyAssignment Exists (0.4ms)  SELECT  1 AS one FROM "school_survey_assignments" WHERE ("school_survey_assignments"."school_id" = 6 AND "school_survey_assignments"."survey_assignment_id" = 369) LIMIT 1
  SQL (0.3ms)  INSERT INTO "school_survey_assignments" ("survey_assignment_id") VALUES ($1) RETURNING "id"  [["survey_assignment_id", 369]]
  (0.4ms)  COMMIT
=> [{:school_id=>5}, {:school_id=>6}]
2.3.1 :007 > sa1.save
  (0.3ms)  BEGIN
  (0.4ms)  COMMIT
=> true

现在,我似乎已经成功创建了两个 SchoolSurveyAssignments,survey_assignment_id = 369 和 school_ids = 5 和 6

2.3.1 :008 > sa1.school_survey_assignments
  SchoolSurveyAssignment Load (0.3ms)  SELECT "school_survey_assignments".* FROM "school_survey_assignments" WHERE "school_survey_assignments"."survey_assignment_id" = $1  [["survey_assignment_id", 369]]
=> #<ActiveRecord::Associations::CollectionProxy [#<SchoolSurveyAssignment id: 5, survey_assignment_id: 369, school_id: nil, grades_affected: [], ulc_affected: []>, #<SchoolSurveyAssignment id: 6, survey_assignment_id: 369, school_id: nil, grades_affected: [], ulc_affected: []>]>

从 ActivRecord::Associations::CollectionProxy 可以看出,创建了两个 SchoolSurveyAssignments,survey_assignment_id:369,但 school_id 为零。这似乎令人不安

  1. 忽略传递给 create 函数的参数,并且
  2. 忽略 school_id 的验证

我不明白的另一个项目如下:

2.3.1 :009 > SchoolSurveyAssignment.find(5).survey_assignment_id
  SchoolSurveyAssignment Load (0.6ms)  SELECT  "school_survey_assignments".* FROM "school_survey_assignments" WHERE "school_survey_assignments"."id" = $1 LIMIT 1  [["id", 5]]
=> nil
2.3.1 :011 > SchoolSurveyAssignment.find(5).survey_assignment.id
  SchoolSurveyAssignment Load (0.3ms)  SELECT  "school_survey_assignments".* FROM "school_survey_assignments" WHERE "school_survey_assignments"."id" = $1 LIMIT 1  [["id", 5]]
  SurveyAssignment Load (0.4ms)  SELECT  "survey_assignments".* FROM "survey_assignments" WHERE "survey_assignments"."id" = $1 LIMIT 1  [["id", 369]]
 => 369

调用 .survey_assignment_id 应该返回 SchoolSurveyAssignment 上的属性并给出 369。.survey_assignment.id 只是获取父对象的 ID。我希望两者都返回相同的值,但一个返回 nil。

最终用例是制作一个 SurveyAssignment 表单,让用户可以为新的 SurveyAssignment 设置属性,并为 X 个 SchoolSurveyAssignment 设置属性(基于一个地区的学校数量;从 2 到 15 不等)。一旦我更好地掌握了这些模型如何交互,我就有信心执行这个目标,但我看到的行为对我来说没有意义,我希望在实现这些相关模型时找到一些清晰的东西。我觉得我在回答问题,但缺少一个关键细节。

谢谢,

亚历克斯

【问题讨论】:

  • 你能发布控制器代码吗?我假设您使用的是强参数?
  • 我正在使用强参数。我将使用控制器代码进行更新。这可能会暴露我的无知,但我的印象是控制器方法只会受到真正的 HTTP 请求/路由的影响,而从控制台运行的命令不会命中这些方法。

标签: ruby-on-rails postgresql nested-attributes has-many rails-console


【解决方案1】:

尝试删除您的 attr_accessor 代码行。 attr_accessor 不应该用于持久化在数据库中的属性,它可能会破坏 ActiveRecord 默认提供的方法,导致这些属性无法正确保存

class SurveyAssignment
  belongs_to :district
  belongs_to :survey
  has_one :survey_version, through: :survey
  has_many :school_survey_assignments, inverse_of: survey_assignment
  has_many :schools, :through => :school_survey_assignments

  accepts_nested_attributes_for :school_survey_assignments

  validates :survey_id, presence: true
end 

class SchoolSurveyAssignment
  belongs_to :survey_assignment, inverse_of: :school_survey_assignments
  belongs_to :school

  validates_presence_of :survey_assignment
  validates :school_id, presence: true, uniqueness: {scope: :survey_assignment_id}
end

【讨论】:

  • 您好,感谢您的帮助!我尝试根据您的建议进行创建,它创建了 SurveyAssignment,但没有创建 school_survey_assignments。我还尝试了 api.rubyonrails.org 文档对一对多示例 api.rubyonrails.org/v4.2/classes/ActiveRecord/NestedAttributes/… 所说的内容,这也导致创建了调查分配,但没有创建 SchoolSurveyAssignments。
  • 根据同事的一些建议,我已经转向使用自定义表单对象类来处理调查分配和 SchoolSurveyAssignments 的创建。它正在适当地处理参数,我知道我正在通过 SurveyAssignmentController 获取所有必需的参数并进入该对象。但是,它仍然没有保存子模型(school_survey_assignment)上的属性。看来我还有其他问题,而不是嵌套表单/表单对象的任何问题
  • 对原始帖子进行了编辑。尝试注释掉/删除attr_accessor
  • 是的,就是这样。我删除了 SchoolSurveyAssignment 模型中的 attr_accessor 行,现在我的表单对象可以完美运行。我想我可以回去让 nested_attributes 方法工作,但是这个表单对象似乎足够优雅。
【解决方案2】:

对于第一个问题,School 和 SurveyAssignment 互不认识,school_id 变为 nil。在你的应用中,这些模型之间存在间接的 m 对 n 关联,那么通过模型之间的关联来使用 has_many 怎么样?

在学校模型中,添加以下内容:

has_many :survey_assignments, :through => :school_survey_assignments

在 SurveyAssignments 模型中,添加以下内容:

has_many :schools, :through => :school_survey_assignments

对于最后一个问题,两个代码似乎相同..

【讨论】:

  • 感谢您指出所需的关联。我补充说,我仍然遇到一些问题。我已更新代码以反映更改。关于最后一个问题,区别在于 .id 与 _id
  • 抱歉,我忽略了这一点,但 .id 和 _id 都可以在我的 PC 中使用。您的问题仍然是 .id vs _id 吗?顺便说一句,在 SurveyAssignment 类中,我猜 inverse_of:survey_assignment 在 'survey_assignment' 之前缺少 ':'。
  • 感谢您的跟进。缺少的 : 是从我的编辑器转到 Stackoverflow 时的一个错字,但很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 2021-06-08
  • 2020-03-15
  • 1970-01-01
  • 2011-03-05
相关资源
最近更新 更多