2013-07-13 50 views
14

我比較新的軌道,並最終找到正確的方式來使用accepts_nested_attributes_foraccept_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 

視圖(人/ 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 %> 

[編輯]
難道是使用一個服務對象是個好主意?

回答

18

你的問題意味着你相信accepts_nested_attributes功能是一個壞的事情完全不是這樣,並且完美地工作。

我首先要說的是,您不需要另一種方法來accept_nested_attributes_for,但我會在這篇文章的末尾介紹它。

參照您提供的鏈接,它引用一無所知爲什麼海報相信accepts_nested_attributes_for應在所有被棄用,只是僅僅說

愚見

,應該棄用

嵌套屬性在考慮如何以單一形式捕獲與父母相關的多條記錄時非常重要,這不僅僅是一個Ruby on Rails的事情,而是用於大多數複雜的Web應用程序,用於從瀏覽器將數據發送回服務器,而不管你的語言sed開發該網站。

我不是在批評你指向的文章。對我來說,它只是指出了用填充數據庫支持的模型的很多替代方法,這些模型有許多與業務邏輯無關的代碼。所使用的具體例子僅僅是一種編碼風格的偏好選擇。

當時間是金錢,壓力開始了,一行代碼將完成這個工作,而在這個例子中顯示的22行代碼中,我的偏好在大多數情況下(並非所有情況下)都是使用一行代碼一個模型(accepting_nested_attributes_for)接受從窗體返回的嵌套屬性。

要正確回答您的問題是不可能的,因爲您尚未明確說明爲什麼您認爲accepting_nested_attributes_for不是最佳做法,但最簡單的方法是僅在控制器操作中提取params哈希屬性,並在事務內單獨處理每條記錄。

更新 - 跟進評論

我想鏈接文章的作者認爲,以下 空中接力,範式,每個對象只能讀取和寫入自己的數據。 使用accepts_nested_attributes_for時,一個對象會更改某些其他對象數據。

O.K.讓我們清楚起來。首先OO範式不建議這樣的事情。班級應該謹慎,但他們可以與其他班級互動。事實上,Ruby中的OO方法是沒有意義的,如果是這樣的話,ruby中的所有東西都是一個類,所以沒有什麼能夠與其他任何東西進行交流。試想一下,如果恰好是您的控制器實例的對象無法與模型或其他控制器交互,會發生什麼?

使用accepting_nested_attributes_for,一個對象會改變其他對象數據的某些 。

該聲明中的幾點因爲它是一個複雜的點,我會盡量簡短。

1)模型實例保護數據。在涉及任何/大多數其他語言(C,Delphi,VB等)中數百個表格的非常複雜的情況下,3層解決方案中的中間層就是這樣做的。在Rails中,模型是業務邏輯的一個地方,在3層解決方案中執行中間層的工作,通常由RDBMS中的存儲過程和視圖進行備份。模型應該能夠相互交流。

2)accep_nested_attributes_for根本不會破壞任何面向對象的原則。它只是簡化了如果方法不存在就需要編寫的代碼量(正如你所發現的那樣)。如果您接受嵌套在子模型的params哈希中的屬性,您所做的只是允許子模型以與控制器的操作相同的方式處理該數據。沒有業務邏輯被繞過,你會得到額外的好處。

最後

我可以負擔得起關心的代碼優雅(比約時間更多)

我可以向你保證,沒有什麼高雅有關編寫20個+行的代碼比您需要添加數百行代碼,其中一行代碼將爲您完成工作。正如其他人所說的(包括我),accepts_nested_attributes_for並不總是一個合適的ActiveRecord方法,通過查看不同的方法,您可以做的是一件好事,因爲最終您可以對何時使用構建做出更明智的判斷在方法和什麼時候寫你自己的。不過,我建議要充分理解發生了什麼(因爲您聲明您有時間),您最好編寫自己的代碼來處理表單對象並接受嵌套屬性選項。這樣你會發現自己更加了解。

希望你的學習有意義,好運。

更新2

,最終得到你的觀點,並在參考自己的答案加上考慮到其他人由VIRTUS寶石支持自己的答案表單對象提出的很好的意見是完全特別是在處理數據收集方式時需要合理的解決方案。這種組合有助於將用戶界面邏輯與業務邏輯分開,只要您最終將數據傳遞給模型,以便業務邏輯不會被繞過(如您所示,您正在做這件事),那麼您就有了一個很好的解決方案。

只是不排除accep_nested_attributes失控。

您也可以從Ryan Bates的railscasts表單對象中獲得一些好處。

+0

我很理解你的論點,我在你描述的情況下支持它。由於我不是一名專業開發人員,但卻是最有錢的,我能負擔得起代碼的優雅(不止是時間)。我認爲鏈接文章的作者認爲,遵循oop範式,每個對象應該只能讀寫自己的數據。通過accept_nested_attributes_for,一個對象會改變一些其他的對象數據。 – speendo

+3

從長遠來看,選擇最佳模式將最終爲您節省更多時間。以許多不同的擔憂和責任污染你的模型只會讓你頭痛。在適當的情況下使用Form對象比使用嵌套屬性更好。關鍵是「在適當的地方」 - 有些情況下使用嵌套屬性是更好的解決方案。 –

+1

另外 - 爲什麼貶低可能有點極端,我仍然認爲讓新開發人員(speendo)嘗試替代「Rails方式」更好。最近似乎很多人已經開始使用諸如Form對象之類的東西,因爲在實現Rails不能擴展(代碼庫,而不是應用程序本身)和巨大的應用程序之後。不鼓勵人們這樣做會受到影響,而且Rails將會越來越難以向前推進,併成爲一個框架。 –

4

使用virtus比使用accepts_nested_attributes_for要容易得多。最重要的要求是敢於製作我讀過的任何教程都沒有涉及的東西。

一步一步:

  1. 我添加gem 'virtus'到的Gemfile就跑bundle install
  2. 我寫了一個文件型號/ 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,充滿*型號/ 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. 下一步是視圖。我創建的意見/聯繫人/ 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

就是這樣。也許它可以做得更加優雅。也許它也會支付僅使用通訊錄類,也用於其他CRUD操作。我沒有嘗試,但...

你可以在這裏找到所有的變化:https://github.com/speendo/PhoneBook/tree/virtus/app/models

+4

這不是在這裏舉重的Virtus。您只是使用表單對象(如鏈接的博客文章中所述)。 Virtus僅僅是一個圖書館,可以幫助你製作表格對象(還有很多其他的東西)。 –

+0

@LoganSerman當然,但沒有它,創建視圖會更困難,對吧? – speendo

+1

它允許你用屬性的散列來創建你的模型,這樣'Contact.new(contact_params)'是可能的。它本質上是使普通對象(你的Contact形式對象)更像ActiveRecord。 –

3

因此,公認的答案只是說爲什麼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?