19

我对 Rails 比较陌生,终于找到了正确的使用方法accepts_nested_attributes_for

然而,网络上有一些严肃的资源说使用accepts_nested_attributes_for通常是一种不好的做法(比如这个

需要避免哪些更改accepts_nested_attributes_for以及将附加类文件放在哪个文件夹中(我想需要一个附加类)。

我读到virtus是适合的。那正确吗?

这是一个仍在使用的非常基本的示例(在此处accepts_nested_attributes_for找到完整示例):

楷模

class Person < ActiveRecord::Base

    has_many :phones
    accepts_nested_attributes_for :phones

end

class Phone < ActiveRecord::Base

    belongs_to :person

end

控制器

class PeopleController < ApplicationController

    def new

        @person = Person.new
        @person.phones.new

    end

    def create

        @person = Person.new(person_params)
        @person.save

        redirect_to people_path

    end

    def index

        @people = Person.all

    end

private

    def person_params

        params.require(:person).permit(:name, phones_attributes: [ :id, :number ])

    end

end

查看 (people/new.html.erb)

<%= form_for @person, do |f| %>
    <p>
        <%= f.label :name %><br />
        <%= f.text_field :name %>
    </p>
    <%= f.fields_for :phones do |builder| %>
    <p>
            <%= builder.label :number %><br />
            <%= builder.text_field :number %>
    </p>
    <% end %>
    <%= f.submit %>
<% end %>

[编辑]
使用服务对象是个好主意吗?

4

4 回答 4

16

您的问题意味着您认为 Accepts_nested_attributes 功能是一件坏事,但事实并非如此,而且效果很好。

首先我会说你不需要替代accepts_nested_attributes_for,但我会在这篇文章的最后介绍。

参考您提供的链接,它没有引用为什么张贴者认为 Accepts_nested_attributes_for 应该被弃用,而只是声明

在我的拙见中,应该被弃用

嵌套属性是一个非常重要的概念,在考虑如何以单个表单捕获与父级相关的多个记录时,这不仅仅是 Ruby on Rails 的事情,而且在大多数复杂的 Web 应用程序中用于将数据从浏览器发送回服务器,无论用于开发网站的语言。

我根本没有批评你所指的文章。对我来说,它只是指出了用大量不一定与业务逻辑相关的代码填充数据库支持模型的明显替代方案。使用的具体示例仅仅是编码风格偏好的替代方案。

当时间就是金钱并且压力很大并且一行代码就可以完成工作而不是示例中显示的 22 行代码时,我在大多数情况下(不是所有情况)的偏好是在模型中使用一行代码(accepts_nested_attributes_for ) 接受从表单回发的嵌套属性。

正确回答您的问题是不可能的,因为您没有真正说明为什么您认为 accept_nested_attributes_for 不是好的做法,但是最简单的替代方法是在您的控制器操作中提取 params 哈希属性并在事务中单独处理每条记录。

更新 - 跟进评论

我认为链接文章的作者认为,按照 oop 范式,每个对象都应该只读取和写入自己的数据。但是,使用accepts_nested_attributes_for,一个对象会更改一些其他对象的数据。

好的,让我们澄清一下。首先,面向对象范式表明没有这样的事情。类应该是谨慎的,但允许它们与其他类交互。事实上,如果是这种情况,那么在 Ruby 中使用 OO 方法是没有意义的,因为 ruby​​ 中的所有东西都是一个类,因此没有任何东西可以与其他任何东西对话。想象一下,如果恰好是控制器实例的对象无法与模型或其他控制器交互会发生什么?

但是,使用accepts_nested_attributes_for,一个对象会更改一些其他对象的数据。

关于该声明的几点,因为它是一个复杂的,我会尽量简短。

1)模型实例保护数据。在涉及任何/大多数其他语言(C、Delphi、VB 等)的数百个表的非常复杂的场景中,3 层解决方案中的中间层就是这样做的。在 Rails 术语中,模型是业务逻辑的地方,并在 3 层解决方案中完成中间层的工作,该解决方案通常由 RDBMS 中的存储过程和视图支持。模型完全正确地应该能够相互交谈。

2)accepts_nested_attributes_for 根本不违反任何 OO 原则。如果该方法不存在(正如您所发现的那样),它只是简化了您需要编写的代码量。如果您接受嵌套在子模型的 params 哈希中的属性,那么您所做的就是允许子模型以与控制器操作必须执行的方式相同的方式处理该数据。没有业务逻辑被绕过,您可以获得额外的好处。

最后

我有能力关心代码的优雅(不仅仅是时间)

我可以向您保证,编写超过您需要的 20 多行代码并从 gem 中添加数百行代码,其中一行代码将为您完成工作,这并不优雅。正如其他人(包括我)所说的那样,accepts_nested_attributes_for 并不总是适合使用的 ActiveRecord 方法,通过查看不同的方法,您正在做一件好事,因为最终您将能够对何时使用内置做出更明智的判断在方法中以及何时编写自己的方法。但是,我建议要完全了解正在发生的事情(正如您所说的那样),您最好编写自己的代码来处理表单对象并接受嵌套属性替代方案。这样你会发现自己理解得更多。

希望这是有道理的,祝你学习顺利。

更新 2

为了最终达到您的目的并参考您自己的答案,并考虑到其他人对您自己的答案表单对象所做的出色评论,由 virtus gem 支持是一个完全合理的解决方案,尤其是在处理数据的方式时集。这种组合有助于将用户界面逻辑与业务逻辑分开,只要您最终将数据传递给模型以便不绕过业务逻辑(正如您所展示的那样),那么您就有了一个很好的解决方案.

只是不排除失控的accepts_nested_attributes。

您还可以通过观看Ryan Bates 对表单对象的railscasts获得一些好处。

于 2013-07-14T11:37:33.070 回答
5

使用 virtusaccepts_nested_attributes_for比我想象的要容易得多。最重要的要求是敢于做出我读过的任何教程都没有涉及的东西。

一步步:

  1. 我添加gem 'virtus'到 Gemfile 并运行bundle install.
  2. 我写了一个文件models/contact.rb并写了以下代码:

    class Contact
      include Virtus
    
      extend ActiveModel::Naming
      include ActiveModel::Conversion
      include ActiveModel::Validations
    
      attr_reader :name
      attr_reader :number
    
    
      attribute :name, String
      attribute :number, Integer
    
      def persisted?
        false
      end
    
      def save
        if valid?
          persist!
          true
        else
          false
        end
      end
    
    private
    
      def persist!
        @person = Person.create!(name: name)
        @phones = @person.phones.create!(number: number)
      end
    end
    
  3. 然后我运行rails generate controller contacts并填充 *models/contacts_controller.rb*

    class ContactsController < ApplicationController
    
      def new
    
        @contact = Contact.new
    
      end
    
      def create
    
        @contact = Contact.new(contact_params)
        @contact.save
        redirect_to people_path
    
      end
    
      def contact_params
    
        params.require(:contact).permit(:name, :number)
    
      end
    
    end
    
  4. 下一步是视图。我创建了views/contacts/new.html.erb并编写了这个基本表单

    <%= form_for @contact do |f| %>
      <p>
        <%= f.label :name %><br />
        <%= f.text_field :name %>
      </p>
    
      <p>
        <%= f.label :number %><br />
        <%= f.text_field :number %>
      </p>
    
      <%= f.submit %>
    <% end %>
    
  5. 当然我还需要添加路线resources :contacts

而已。也许它可以做得更优雅。也许只使用 Contacts 类也是值得的,对于其他 CRUD 操作也是如此。我还没试过。。。

您可以在此处找到所有更改:https ://github.com/speendo/PhoneBook/tree/virtus/app/models

于 2013-07-14T20:36:54.183 回答
5

因此,接受的答案只是说明了为什么accepts_nested_attributes_for通常是一个好的解决方案,但从未真正提供有关如何做到这一点的解决方案。如果您想让表单接受动态数量的嵌套对象,链接文章中的示例将遇到问题。这是我找到的唯一解决方案

https://coderwall.com/p/kvsbfa/nested-forms-with-activemodel-model-objects

对于后代,这是基础知识,但网站上还有更多内容:

class ContactListForm
include ActiveModel::Model

attr_accessor :contacts

def contacts_attributes=(attributes)
  @contacts ||= []
  attributes.each do |i, contact_params|
    @contacts.push(Contact.new(contact_params))
  end
end
end

class ContactsController < ApplicationController
   def new
      @contact_list = ContactListForm.new(contacts: [Contact.new])
    end
  end

并且f.fields_for :contacts应该表现得像一种has_many关系,并且可以由您的表单对象轻松处理。

如果Contact不是AR模型,您也需要进行恶搞persisted?

于 2016-05-08T03:49:21.560 回答
-3

Railscasts 也有一集关于嵌套表单模型: http ://railscasts.com/episodes/196-nested-model-form-revised?view=asciicast

如评论所示,cocoon gem 简化了很多: https ://github.com/nathanvda/cocoon

于 2013-07-14T10:32:34.207 回答