嵌套关联不保存

问题描述

我有一个客户的嵌套关联。设置是这样的:

地址模型

  belongs_to :customer,optional: true # Yes,optional
  
  has_one :physical_address,dependent: :destroy
  has_one :postal_address,dependent: :destroy

  accepts_nested_attributes_for :physical_address
  accepts_nested_attributes_for :postal_address

邮政地址模型

# same for physical_address.rb
belongs_to :address

客户控制器:

def create
  @customer = current_user.customers.build(customer_params)

  if @customer.save
    return puts "customer saves"
  end

  puts @customer.errors.messages
  #redirect_to new_customer_path
  render :new
end

private
  def customer_params
    params.require(:customer).permit(
      address_attributes: address_params
    )
  end

  def address_params
    return ([
      postal_address_attributes: shared_address_params,#physical_address_attributes: shared_address_params
    ])
  end

  def shared_address_params
    params.fetch(:customer).fetch("address").fetch("postal_address").permit(
      :street,etc...
    )
  end

客户模型:

has_one     :address,dependent: :destroy
accepts_nested_attributes_for :address

可以创建一个客户,但不能创建地址。例如,这是表格:

<form>
  <input name="customer[address][postal_address][street]" value="Foo Street" />
</form>

记录“参数”,我看到所有值,但地址未创建。我相信错误在于shared_address_params。有什么想法吗?

解决方法

我认为您只是迷失了该参数白名单中的间接性和复杂性。

您基本上想要的是:

def customer_params
  params.require(:customer)
        .permit(
          address_attributes: {
            physical_address_attributes: [:street,:foo,:bar,:baz],postal_address: [:street,:baz]
          }
        )
end

正如您在此处看到的那样,您不仅需要customer[address_attributes],还需要参数键customer[address]

现在,让我们重构以减少重复:

def customer_params
  params.require(:customer)
        .permit(
          address_attributes: {
            physical_address_attributes: address_attributes,postal_address: address_attributes
          }
        )
end

def address_attributes
  [:street,:baz]
end

如您所见,这里应该几乎没有增加任何复杂性,并且如果需要使其更加灵活,则可以向address_attributes方法添加参数-毕竟,建立白名单只是简单的数组和哈希操作。 / p>

如果要处理将某种共享属性映射到这两种地址类型的操作,您实际上应该在模型中执行此操作,而不是使用业务逻辑来膨胀控制器。例如,通过为“虚拟属性”创建setter和getter:

class Address < ApplicationController
  def shared_address_attributes
    post_address_attributes.slice("street","foo","bar","baz")
  end

  def shared_address_attributes=(**attrs)
    # @todo map the attributes to the postal and
    # visiting address
  end
end

通过这种方式,您只需像其他属性一样设置表单并将其列入白名单,并且控制器就不必担心具体细节。

def customer_params
  params.require(:customer)
        .permit(
          address_attributes: {
            shared_address_attributes: address_attributes,physical_address_attributes: address_attributes,postal_address: address_attributes
          }
        )
end