Ruby on Rails 嵌套属性
介绍
嵌套属性是一种功能,可让您通过其关联的父级保存记录的属性。在此示例中,我们将考虑以下场景:
我们正在创建一个包含大量产品的在线商店。每个产品可以有零个或多个变体。变体就是字面意思;它们代表同一产品的不同变体,例如颜色不同。两者都有名称和价格。每个产品还将与一个图像记录相关联,其中包含一个网址、alt 和一个标题。
在本教程的后面,我们将改进这些模型:
      class Product < ActiveRecord::Base
  has_many :variants
  has_one :image
  # Attributes: name:string, price:float
end
    
      class Variant < ActiveRecord::Base
  belongs_to :product
  # Attributes: name:string, price:float
end
    
      class Image < ActiveRecord::Base
  belongs_to :product
  # Attributes: url:string, alt:string caption:string
end
    
一对一关联
嵌套属性最简单的例子是一对一关联。要为产品模型添加嵌套属性支持,只需添加以下行:
      class Product < ActiveRecord::Base
  has_many :variants
  accepts_nested_attributes_for :image
end
    
这到底是做什么的?它将把已保存的属性从产品模型代理到图像模型。在产品表单中,您需要添加用于图像关联的其他字段。您可以使用 fields_for助手来完成此操作。
      = form_for @product do |f|
  // Product attributes
  .form-group
    = f.label :name
    = f.text_field :name  
  .form-group
    = f.label :price
    = f.text_field :price
  // Image attributes
  = f.fields_for :image do |f|
    = f.label :url
    = f.text_field :url
    = f.label :alt
    = f.text_field :alt
    = f.label :caption
    = f.text_field :caption
  = f.submit
    
现在,剩下的唯一部分就是修改控制器以接受这些新属性。嵌套属性背后的整个想法是,您不必在控制器中添加额外的代码来处理此输入和关联,但您确实需要允许这些属性到达模型,而强参数默认会阻止这种情况。因此,您需要将以下内容添加到ProductsController中的product_params方法中。
      def product_params
  params.require(:product).permit(
    :name, :price,
    image_attributes: [ :id, :url, :alt, :caption ]
  )
end
    
瞧!现在您可以从同一表单内联编辑产品模型的图像关联。现在让我们看看如何通过多对多关系构建相同的行为。
多对多关联
产品变体非常简单(只有两个字段),因此没有必要创建单独的页面来编辑它们。相反,我们希望从同一个产品表单中内联编辑它们以及产品属性。由于每个产品可以有多种变体,这意味着我们必须处理多个项目。我们还需要添加新变体并删除旧变体。让我们逐一解决这些问题。
显示多个关联
fields_for方法为每个关联记录生成一个块,因此我们不需要更改任何内容 - 但因为我们需要重复使用此表单(为了通过 JavaScript 自动添加新字段),所以我们需要将其移动到单独的文件中。我们将创建一个名为_variant_fields.slim的新部分,其中仅包含变体字段,如下所示:
      = f.label :name
= f.text_field :name
= f.label :price
= f.text_field :price
    
回到产品表单,为了呈现字段,我们只需利用fields_for为每个关联产生一个块的事实,并将表单帮助对象传递给部分。
      = f.fields_for :variants do |f|
  = render 'variant_fields', f: f
    
添加新关联
为了添加新的关联,我们需要创建一些添加新字段的 JavaScript。我喜欢创建一个链接,当单击该链接时,它将添加一个新的字段元组。如下所示:
      = link_to_add_fields 'Add Product Variant', f, :variants
    
这是我编写的一个有用的辅助方法,它将创建一个带有data-form-prepend属性的链接,其中包含_variant_fields.slim部分的全部内容。这里的想法是,当您单击它时,您将使用一些简单的可重复使用的 JavaScript 将这些字段附加到表单的末尾。
实际的帮助程序看起来相当复杂和混乱,但请耐心等待——我保证它和大多数代码一样简单。它只处理参数,关键逻辑位于最后七行。您可以将此代码放在 application_helper.rb中。
      def link_to_add_fields(name = nil, f = nil, association = nil, options = nil, html_options = nil, &block)
  # If a block is provided there is no name attribute and the arguments are
  # shifted with one position to the left. This re-assigns those values.
  f, association, options, html_options = name, f, association, options if block_given?
  options = {} if options.nil?
  html_options = {} if html_options.nil?
  if options.include? :locals
    locals = options[:locals]
  else
    locals = { }
  end
  if options.include? :partial
    partial = options[:partial]
  else
    partial = association.to_s.singularize + '_fields'
  end
  # Render the form fields from a file with the association name provided
  new_object = f.object.class.reflect_on_association(association).klass.new
  fields = f.fields_for(association, new_object, child_index: 'new_record') do |builder|
    render(partial, locals.merge!( f: builder))
  end
  # The rendered fields are sent with the link within the data-form-prepend attr
  html_options['data-form-prepend'] = raw CGI::escapeHTML( fields )
  html_options['href'] = '#'
  content_tag(:a, name, html_options, &block)
end
    
在 JavaScript 方面,我使用 jQuery 查找每个将 name 属性设置为new_record的元素,并将其替换为时间戳。这解决了添加多条新记录时的问题;两条记录将具有相同的 id ( new_record )。
      $("[data-form-prepend]").click(function(e) {
  var obj = $($(this).attr("data-form-prepend"));
  obj.find("input, select, textarea").each(function() {
    $(this).attr("name", function() {
      return $(this)
        .attr("name")
        .replace("new_record", new Date().getTime());
    });
  });
  obj.insertBefore(this);
  return false;
});
    
删除关联
幸运的是,accepts_nested_attributes_for有一些删除关联的巧妙功能。如果我们将allow_destroy: true参数传递给accepts_nested_attributes_for ,它将销毁包含_destroy键的属性中的任何成员。
      accepts_nested_attributes_for :variants, allow_destroy: true
    
在视图中,这可以通过一个简单的复选框来实现。所以我在_variant_fields.slim中添加了一个复选框:
      = f.check_box :_destroy
= f.label :delete
    
对模型和控制器的修改
再次,与以前一样, ProductsController中添加的只是product_params方法,现在还应该包括variants_attributes。
      def product_params
  params.require(:product).permit(
    :name, :price,
    image_attributes: [ :id, :url, :alt, :caption ],
    variants_attributes: [ :id, :name, :price, :_destroy ]
  )
end
    
产品模型只需添加以下内容即可为变体关联启用嵌套属性:
      accepts_nested_attributes_for :variants, reject_if: :all_blank, allow_destroy: true
    
注意reject_if :all_blank选项。这意味着任何属性全为空白(不包括_destroy的值)的记录都将被拒绝。reject_if还支持传递一个Proc,可用于一些额外的验证,它还会检查是否拒绝/包含关联。
还有两个有用的选项。第一个是limit选项,它指定将处理的最大记录数。第二个选项是update_only,它适用于一对一关联,并且具有相当有趣的行为。如果将其设置为 true,它将仅更新关联记录的属性。如果在更改时设置为 false,它不会触及旧记录,但会使用新属性创建一个新记录。默认情况下,它是 false,并且会创建一个新记录,除非记录包含id属性,这正是我们在product_params中包含id属性的原因,用于一对一图像关联。另一种解决方案是定义嵌套属性,如下所示:
      accepts_nested_attributes_for :image, update_only: true
    
您可以在 Ruby on Rails 文档中阅读有关嵌套属性的更多信息,其中每个配置选项都有演示和详尽的记录。
关于作者
Itay Grudev 是一名学生,目前正在英国阿伯丁大学攻读计算机科学和物理学学位。</p
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
                                
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                    
                                
                            
                                    
                                    
                                    
                                    
    
    
            
  
        
请先 登录后发表评论 ~