accept_nested_attributes_for的替代方案 - 也许是virtus

时间:2013-07-13 22:10:28

标签: ruby-on-rails ruby nested-forms nested-attributes

我对rails很新,最后找到了使用accepts_nested_attributes_for的正确方法。

但是,网络上有一些严肃的资源说使用accepts_nested_attributes_for通常是一种不好的做法(例如one)。

需要进行哪些更改才能避免accepts_nested_attributes_for以及在哪个文件夹中放置其他类文件(我猜需要一个额外的类)。

我读到virtus适合于此。是吗?

以下是一个使用accepts_nested_attributes_for的基本示例(查找完整示例here):

模型

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 个答案:

答案 0 :(得分:16)

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

我首先要说的是,您不需要替代accept_nested_attributes_for,但我会在本文末尾介绍。

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

  

以我的拙见,应该弃用

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

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

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

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

更新 - 跟进评论

  

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

O.K。让我们清楚一点。 首先,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的railscasts获得一些好处。

答案 1 :(得分: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
    
  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

  6. 就是这样。也许它可以做得更优雅。也许只使用Contacts-class,也可以用于其他CRUD操作。我还没试过......

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

答案 2 :(得分: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?

答案 3 :(得分:-3)

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

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