Rails表单 - 我应该在Controller,Model还是View中构建`accepts_nested_attributes_for`关联?

时间:2013-07-04 18:14:34

标签: ruby-on-rails

问题

我有一个父母,accepts_nested_attributes_for是一个孩子。所以,当我有父母的表格时,我需要build孩子,所以我也可以为它显示表格字段。我想知道的是:我应该在哪里build孩子?在模型,视图或控制器中?

我为什么要这个

你可能会摇头,以为我是一个疯狂的人,因为他提出了这样的问题,但这里的思路让我在这里。

我有一个Customer模型,accepts_nested_attributes_for一个billing_address,如下所示:

class Customer
  belongs_to :billing_address, class_name: 'Address'
  accepts_nested_attributes_for :billing_address
end

当我向用户提供新Customer的表单时,我想确保空白billing_address,以便用户实际看到billing_address的字段。所以我的控制器中有这样的东西:

def new
  @customer = Customer.new
  @customer.build_billing_address
end

但是,如果用户未填写任何billing_address字段,但尝试提交无效表单,系统会向其显示一个不再包含billing_address字段的表单,除非我在我的控制器的create动作中添加了这样的内容:

def create
  @customer = Customer.new(params[:customer])
  @customer.build_billing_address if @customer.billing_address.nil?
end

还有另一个问题,即如果用户尝试编辑Customer,但Customer已经没有关联的billing_address,则他们将看不到字段对于billing_address。所以我必须向控制器添加这样的东西:

def edit
  @customer = Customer.find(params[:id])
  @customer.build_billing_address if @customer.billing_address.nil?
end

类似的东西需要在控制器的update方法中发生。

无论如何,这是高度重复的,所以我想在模型中做一些事情。我最初的想法是为模型的after_initialize事件添加一个回调,如下所示:

class CustomerModel
  after_initialize :build_billing_address, if: 'billing_address.nil?'
end

但我的蜘蛛般的感觉开始刺痛。谁会说我将来不会在我的代码的其他部分实例化Customer,并以某种意想不到的方式造成严重破坏。

所以我目前的想法是,最好的地方是在表单视图本身,因为我要完成的是为表单创建一个空白billing_address,表单本身是唯一的在我知道的代码中放置我将要显示billing_address的表单。

但是,你知道,我只是互联网上的一些人。我应该在哪里build_billing_address

5 个答案:

答案 0 :(得分:3)

尽管Xavier Shay的this advice是2011年,但他建议将其放入 视图 "因为这是一个视图问题(我们是否显示字段?)"

应用/助手/ form_helper.rb:

module FormHelper
  def setup_user(user)
    user.address ||= Address.new
    user
  end
end

应用/视图/用户/ _form.html.erb:

<%= form_for setup_user(@user) do |f| %>

请注意,我必须将帮助方法更改为以下内容:

  def setup_user(user)
    user.addresses.build if user.addresses.empty?
    user
  end

控制器保持完全不变。

答案 1 :(得分:1)

如果你想建立一些东西并稍后保存,你会使用build。我会说,在嵌套路线中使用它。

def create
 @address = @customer.billing_addresses.build(params[:billing_address])
 if @address.save
   redirect_to @customer.billing_addresses
 else
   render "create"
 end
end

这样的事情。我在控制台时也使用构建。

答案 2 :(得分:1)

你必须记住MVC的原则,即创建DRY(不要重复自己)代码,这些代码在应用程序的各个移动部分之间有效分配

accepts_nested_attributes_for非常适合保持干净

accepts_nested_attributes_for是一个模型函数,它允许您通过关联将数据传递给另一个模型。它存在的原因是为了让您能够基于单个表单填充另一个模型的数据,并且非常适合在没有太多额外代码的情况下扩展功能

您引用的问题是,如果您想在应用的其他区域使用该代码,您最终会遇到各种各样的问题

我的反驳是为了创建尽可能高效的应用程序,你想尽可能少地编写代码 - 让Rails处理所有事情。 accepts_nested_attributes_for函数允许您执行此操作,但显然需要付出代价,因为每次要使用它时都必须适应它

我的建议是使用您认为最有效的代码,但也要遵守惯例;因为这将确保速度和效率

答案 3 :(得分:1)

如果您知道您的模型总是有帐单邮寄地址,您可以按照the docs中所述覆盖模型类中此属性的getter:

def billing_address
    super || build_billing_address
end

根据您的特定需求,可以选择将任何属性传递给build_billing_address

答案 4 :(得分:1)

您应该在控制器中处理所有这些场景,因为它不是模型的责任。

就保持干燥而言,你可以写一个方法,

def build_customer(customer)
  customer.build_billing_address if customer.billing_address.nil?
  #add more code if needed
end

在控制器内部,您可以在需要的地方调用此方法。例如

def create
  @customer = Customer.new(params[:customer])
  if @customer.save
    redirect_to @customer.billing_addresses
  else
    build_customer(@customer)
    render "new"
  end 
end