47

我想建立一個與accepts_nested_attributes_for多態關係。下面是代碼:accepted_nested_attributes_for belongs_to polymorphism

class Contact <ActiveRecord::Base 
    has_many :jobs, :as=>:client 
end 

class Job <ActiveRecord::Base 
    belongs_to :client, :polymorphic=>:true 
    accepts_nested_attributes_for :client 
end 

當我嘗試訪問Job.create(..., :client_attributes=>{...}給我NameError: uninitialized constant Job::Client

+0

你見過關於複雜形式的railscast嗎? http://railscasts.com/episodes/75-complex-forms-part-3 – 2010-10-20 00:16:30

+0

是的,我推出了我自己的解決方案。 – dombesz 2010-10-20 07:49:22

回答

5

就想通了,所以我想出了以下解決方法軌不支持這種行爲:

class Job <ActiveRecord::Base 
    belongs_to :client, :polymorphic=>:true, :autosave=>true 
    accepts_nested_attributes_for :client 

    def attributes=(attributes = {}) 
    self.client_type = attributes[:client_type] 
    super 
    end 

    def client_attributes=(attributes) 
    self.client = type.constantize.find_or_initialize_by_id(attributes.delete(:client_id)) if client_type.valid? 
    end 
end 

這讓我建立我的形式是這樣的:

<%= f.select :client_type %> 
<%= f.fields_for :client do |client|%> 
    <%= client.text_field :name %> 
<% end %> 

不是確切的解決方案,但這個想法很重要。

+1

這對Dmitry/ScotterC的解決方案來說不太理想,因爲Rails預計這種情況會讓你正確地覆蓋行爲。 – 2013-04-06 01:29:59

+1

在本例中,您正在評估用戶輸入。考慮使用'constantize'來代替。 – anarchocurious 2015-06-19 15:18:02

8

上述答案很好,但不能與所示的設置一起工作。這啓發了我,我能創造一個工作的解決方案:

作品用於創建和更新

class Job <ActiveRecord::Base 
    belongs_to :client, :polymorphic=>:true 
    attr_accessible :client_attributes 
    accepts_nested_attributes_for :client 

    def attributes=(attributes = {}) 
    self.client_type = attributes[:client_type] 
    super 
    end 

    def client_attributes=(attributes) 
    some_client = self.client_type.constantize.find_or_initilize_by_id(self.client_id) 
    some_client.attributes = attributes 
    self.client = some_client 
    end 
end 
+0

這對Dmitry/ScotterC的解決方案來說不太理想,因爲Rails預計這種情況會讓你正確地覆蓋行爲。 – 2013-04-06 01:31:27

+0

@MarkP但是,在更新已創建的相同記錄時,由於「client_attributes」被覆蓋,它會創建新記錄而不是更新它。我們有任何解決方案嗎? – 2017-11-06 07:09:53

55

我也有一個問題,在「引發ArgumentError:不能建立關聯MODEL_NAME你是不是想建立一個多態的一對一關聯?「

我發現這種問題更好的解決方案。你可以使用本地方法。讓我們看看到nested_attributes實現,裏面Rails3中:

elsif !reject_new_record?(association_name, attributes) 
    method = "build_#{association_name}" 
    if respond_to?(method) 
    send(method, attributes.except(*UNASSIGNABLE_KEYS)) 
    else 
    raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" 
    end 
end 

因此,實際上做了什麼,我們需要在這裏做什麼?只是在我們的模型中創建build##{association_name}。我已經在底部沒有完全工作示例:

class Job <ActiveRecord::Base 
    CLIENT_TYPES = %w(Contact) 

    attr_accessible :client_type, :client_attributes 

    belongs_to :client, :polymorphic => :true 

    accepts_nested_attributes_for :client 

    protected 

    def build_client(params, assignment_options) 
    raise "Unknown client_type: #{client_type}" unless CLIENT_TYPES.include?(client_type) 
    self.client = client_type.constantize.new(params) 
    end 
end 
+0

我已經成功地使用了這個'build_xyz'技巧。我認爲你錯過了從params中獲得'contact_type'的行(然後你需要將它移除到發送給新的參數) – tokland 2012-05-04 08:21:35

+0

我可能會丟失一些東西,但我相信在整個過程中使用「contact」這個詞方法實際上應該改爲'client' ...另外,當調用構建時,我有兩個參數傳入此方法而不是一個問題。第二個參數是空白哈希。 – JackCA 2012-08-20 06:30:17

+1

謝謝@JackCA!固定代碼和最新的Rails 3.2代碼都被更改了,因爲第二個參數現在傳遞了賦值選項:https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L351 – 2012-09-05 19:36:12

10

終於得到這個與Rails的4.x的工作這是基於Dmitry/ScotterC的回答,所以給他們+1。

STEP 1.首先,這裏是整個模型用的多態關聯:

# app/models/polymorph.rb 
class Polymorph < ActiveRecord::Base 
    belongs_to :associable, polymorphic: true 

    accepts_nested_attributes_for :associable 

    def build_associable(params) 
    self.associable = associable_type.constantize.new(params) 
    end 
end 

# For the sake of example: 
# app/models/chicken.rb 
class Chicken < ActiveRecord::Base 
    has_many: :polymorphs, as: :associable 
end 

是的,這是沒有什麼新的。但是,您可能會想知道polymorph_type從哪裏來,它的值是如何設置的?它是底層數據庫記錄的一部分,因爲多態關聯將<association_name>_id<association_name>_type列添加到表中。按照現狀,執行build_associable時,_type的值爲nil

STEP 2.通行證並接受兒童型

讓你的表單視圖發送child_type與典型的表單數據一起,和你的控制器必須允許它在其強大的參數檢查。

# app/views/polymorph/_form.html.erb 
<%= form_for(@polymorph) do |form| %> 
    # Pass in the child_type - This one has been turned into a chicken! 
    <%= form.hidden_field(:polymorph_type, value: 'Chicken' %> 
    ... 
    # Form values for Chicken 
    <%= form.fields_for(:chicken) do |chicken_form| %> 
    <%= chicken_form.text_field(:hunger_level) %> 
    <%= chicken_form.text_field(:poop_level) %> 
    ...etc... 
    <% end %> 
<% end %> 

# app/controllers/polymorph_controllers.erb 
... 
private 
    def polymorph_params 
    params.require(:polymorph).permit(:id, :polymorph_id, :polymorph_type) 
    end 

當然,你的看法(S)將需要處理不同類型的模型是「可關聯」,但是這展示一個。

希望這可以幫助那裏的人。(爲什麼你需要多形雞?)

+1

另一件事:如果更新/修補程序請求修改了child_type,則需要格外小心以保持數據庫的完整性。一種解決方案是添加代碼,以防止或忽略爲*現有對象*修改'controller#update'中的'polymorph_type'的請求。只有當'record_new?'爲真時,我才添加polymorph_type隱藏字段。 – rodamn 2015-10-02 20:42:45