无法使用accepts_nested_attributes_for创建部分对象

时间:2012-12-01 17:00:04

标签: ruby-on-rails ruby-on-rails-3

我正在尝试构建一个允许用户更新某些记录的表单。但是,它们无法更新每个字段,因此我将进行一些显式处理(现在在控制器中)以相对于表单更新模型。以下是我试图这样做的方法:

家庭模特:

class Family < ActiveRecord::Base
  has_many :people, dependent: :destroy
  accepts_nested_attributes_for :people, allow_destroy: true, reject_if: ->(p){p[:name].blank?}
end

在控制器中

def check
  edited_family = Family.new(params[:family])
  #compare to the one we have in the db
  #update each person as needed/allowed
  #save it
end

形式:

= form_for current_family, url: check_rsvp_path, method: :post do |f|
  = f.fields_for :people do |person_fields|
    - if person_fields.object.user_editable
      = person_fields.text_field :name, class: "person-label"
    - else
      %p.person-label= person_fields.object.name

问题是,我想,Family.new(params[:family])试图将人们拉出数据库,我明白了:

ActiveRecord::RecordNotFound in RsvpsController#check

Couldn't find Person with ID=7 for Family with ID=

那是,我想,因为我没有在嵌套表单中添加一个家庭id的字段,我想我可以这样做,但实际上我并不需要从数据库加载任何东西,所以我宁愿不。我也可以通过自己挖掘params哈希来获取我需要的数据,但这并不是一件容易的事。从params散列中创建一个对象然后再使用它似乎是最好的。

有更好的方法吗?我怎样才能创建嵌套对象?

2 个答案:

答案 0 :(得分:1)

我不建议使用这些参数实例化一个新的Family对象,而是建议使用creating a member route来检查rsvp动作。路线将采取以下形式:

resources :families do
  member do
    post 'check_rsvp'
  end
end

然后form_for将自动传递current_family的id,以便检查操作如下所示:

def check
  edited_family = Family.find(params[:id])
  # ...
end

虽然这看起来在功能上可能等同于自己添加family id参数,但我认为它优于基于其他params实例化或基于其他params实例化的新Family对象,因为:

  1. 它更具惯用性(The Rails Way™)。
  2. 代码较少。
  3. 您为edited_family对象获得referential transparency,这可以减少由于基于已经保留的属性的新Active Record对象的临时实例化而可能发生的细微错误的可能性。

答案 1 :(得分:0)

我最终接受了@ 244an的建议:

class Person < ActiveRecord::Base
  belongs_to :family

  def temp_id
    @temp_id || self.id
  end

  def temp_id=(new_id)
    @temp_id = new_id
  end
end

表格形式:

= form_for current_family, url: check_rsvp_path, method: :post, html: {class: "form-inline"} do |f|
  #people-list
    = f.fields_for :people, include_id: false do |person_fields|
      = person_fields.hidden_field :temp_id
      #rest of the form here

然后在我的控制器中:

def check
    @edited_family = Family.new(params[:family])

    current_people = Hash[current_family.people.map{|p| [p.id, p]}]

    @edited_family.people.each do |person|
      current_person = current_people[person.temp_id.to_i]

      next unless current_person

      current_person.name = person.name if current_person.user_editable
      current_person.attending = person.attending
      current_person.save!
    end

    current_family.responded = true
    current_family.save!

  end

我将该字段添加到模型的原因是我无法想出使用hidden_field重命名字段的好方法。

无论如何,它感觉超级苛刻,但它的确有效。我真正想要的只是一种告诉new不要尝试将子对象与数据库匹配的方法,即使它们有ID。