accept_nested_attributes_for的替代方案 - 也许是virtus

spe*_*ndo 19 ruby ruby-on-rails nested-forms nested-attributes

我对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
Run Code Online (Sandbox Code Playgroud)

调节器

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
Run Code Online (Sandbox Code Playgroud)

查看(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 %>
Run Code Online (Sandbox Code Playgroud)

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

jam*_*esc 16

你的问题暗示你认为accepts_nested_attributes功能是一件坏事,完全不是这样,而且效果非常好.

我首先要说你不需要accept_nested_attributes_for的替代方案,但我将在本文末尾介绍.

参考您提供的链接,它没有说明为什么海报认为accepted_nested_attributes_for应该被弃用而只是仅仅是状态

在我的拙见中,应该弃用

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

我不批评你指的那篇文章.对我而言,它只是指出了填充数据库支持模型的明显替代方案,其中包含许多与业务逻辑无关的代码.使用的具体示例仅仅是编码样式偏好替代.

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

正确回答你的问题是不可能的,因为你还没有说明为什么你认为accepts_nested_attributes_for不是好习惯,但最简单的选择是在控制器动作中提取params散列属性并在事务中单独处理每条记录.

更新 - 跟进评论

我认为链接文章的作者认为,遵循oop-paradigms,每个对象应该只读取和写入自己的数据.使用accepts_nested_attributes_for,一个对象会更改其他一些对象数据.

好让我们清楚一点.首先,OO范式表明没有这样的事情.类应该是谨慎的,但允许它们与其他类交互.事实上,如果出现这种情况,那么Ruby中的OO方法是没有意义的,因为ruby中的一切都是一个类,因此没有什么能够与其他任何东西交谈.试想一下,如果碰巧是控制器实例的对象无法与模型或其他控制器交互,会发生什么?

使用accepts_nested_attributes_for,一个对象会更改其他一些对象数据.

在该声明中有几点,因为它是一个复杂的点,我会尽量简短.

1)模型实例保护数据.在涉及数百个表中任何/大多数其他语言(C,Delphi,VB等)的非常复杂的场景中,3层解决方案中的中间层就是这样.在Rails术语中,模型是业务逻辑的一个位置,在三层解决方案中完成中间层的工作,该解决方案通常由RDBMS中的存储过程和视图备份.模型应该能够相互通信.

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

最后

我可以负担得起代码的优雅(超过时间)

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

希望你的学习有意义,祝你好运.

更新2

为了最终达到你的观点并参考你自己的答案,并考虑到其他人对你自己的答案形式的优秀评论,由virtus gem支持的对象是一个非常合理的解决方案,特别是在处理数据必须的方式时集.这种组合有助于将用户界面逻辑与业务逻辑分离,只要您最终将数据传递给模型,以便不绕过业务逻辑(正如您所示,这样做),那么您就拥有了一个很好的解决方案.

只是不要排除accept_nested_attributes失控.

您还可以通过观看Ryan Bates在表单对象上的railscast获得一些好处.

  • 从长远来看,选择最佳模式最终会为您节省更多时间.使用许多不同的顾虑和责任对您的模型进行污染只会让您后来头疼.在适当的情况下使用表单对象比使用嵌套属性更好.关键是"在适当的地方" - 有些情况下使用嵌套属性是更好的解决方案. (3认同)

spe*_*ndo 5

使用virtus accepts_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
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)
  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 %>
    
    Run Code Online (Sandbox Code Playgroud)
  5. 当然我还需要添加路线 resources :contacts

而已.也许它可以做得更优雅.也许只使用Contacts-class,也可以用于其他CRUD操作.我还没试过......

你可以在这里找到所有的变化:https://github.com/speendo/PhoneBook/tree/virtus/app/models

  • 在这里进行繁重的工作不是Virtus.您只是使用表单对象(如您链接的博客文章中所述).Virtus只是一个帮助制作表格对象(以及其他许多东西)的图书馆. (4认同)

MCB*_*MCB 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
Run Code Online (Sandbox Code Playgroud)

并且f.fields_for :contacts应该表现得像一个has_many关系,并且易于由表单对象处理.

如果Contact不是AR模特,你也需要欺骗persisted?.