47

Tôi muốn thiết lập mối quan hệ đa hình với accepts_nested_attributes_for. Đây là mã:accept_nested_attributes_for với thuộc_to polymorphic

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 

Khi tôi cố gắng truy cập vào Job.create(..., :client_attributes=>{...} mang lại cho tôi NameError: uninitialized constant Job::Client

+0

Bạn đã xem bản tin về các hình thức phức tạp chưa? http://railscasts.com/episodes/75-complex-forms-part-3 –

+0

Có, tôi đã tự giải pháp của riêng mình. – dombesz

Trả lời

5

Chỉ cần hình dung ra rằng đường ray không hỗ trợ loại hành vi vì vậy tôi đã đưa ra những cách giải quyết như sau:

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 

này mang lại cho tôi để thiết lập hình thức của tôi như thế này:

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

Không phải là giải pháp chính xác nhưng ý tưởng là quan trọng.

+1

Đây là điều tối ưu cho giải pháp của Dmitry/ScotterC bên dưới, vì Rails đã dự đoán trường hợp này và cho phép bạn ghi đè hành vi đúng cách. –

+1

Trong ví dụ này, bạn đang đánh giá đầu vào của người dùng. Hãy xem xét sử dụng 'constantize' để thay thế. – anarchocurious

8

Câu trả lời ở trên rất tuyệt nhưng không hoạt động với thiết lập được hiển thị. Nó cảm hứng cho tôi và tôi đã có thể tạo ra một giải pháp làm việc:

công trình để tạo và cập nhật

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

Đây là điều tối ưu cho giải pháp của Dmitry/ScotterC dưới đây, vì Rails đã dự đoán trường hợp này và cho phép bạn ghi đè hành vi đúng cách. –

+0

@MarkP Nhưng trong khi cập nhật cùng một bản ghi đã được tạo, vì ghi đè 'client_attributes', nó tạo bản ghi mới thay vì cập nhật nó. Chúng ta có giải pháp nào cho điều đó không? –

55

Tôi cũng đã có một vấn đề với "ArgumentError:. Không thể xây dựng hiệp hội model_name bạn đang cố gắng xây dựng một hiệp hội một-một-một-đa-một? "

Và tôi đã tìm thấy giải pháp tốt hơn cho loại sự cố này. Bạn có thể sử dụng phương thức gốc. Hãy xem xét triển khai nested_attributes, bên trong 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 

Vì vậy, thực sự chúng ta cần làm gì ở đây? Chỉ để tạo dựng _ # {association_name} bên trong mô hình của chúng tôi. Tôi đã không hoàn toàn ví dụ làm việc ở phía dưới:

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

Tôi đã sử dụng thủ thuật 'build_xyz' này với thành công. Tôi nghĩ rằng bạn đang thiếu một dòng để có được 'contact_type' từ params (và sau đó bạn cần phải loại bỏ nó để params gửi đến mới) – tokland

+0

Tôi có thể thiếu một cái gì đó, nhưng tôi tin rằng việc sử dụng từ' contact' trong suốt phương pháp thực sự nên được thay đổi thành 'client' ... Ngoài ra, tôi đã có một vấn đề với hai đối số được truyền vào phương thức này chứ không phải là một khi xây dựng được gọi. Đối số thứ hai là một băm trắng. – JackCA

+1

Cảm ơn @JackCA! Mã cố định và trong mã Rails 3.2 mới nhất đã được thay đổi, bởi vì trong đối số thứ hai bây giờ chuyển các tùy chọn gán: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L351 –

10

tôi cuối cùng bị này để làm việc với Rails 4.x. Điều này dựa trên câu trả lời của Dmitry/ScotterC, vì vậy +1 cho họ.

BƯỚC 1. Để bắt đầu, đây là mô hình đầy đủ với hiệp hội đa hình:

# 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 

Vâng, đó là không có gì thực sự mới. Tuy nhiên bạn có thể tự hỏi, đâu là polymorph_type đến từ đâu và giá trị của nó được đặt như thế nào? Đó là một phần của bản ghi cơ sở dữ liệu bên dưới vì các liên kết đa hình thêm các cột <association_name>_id<association_name>_type vào bảng. Khi nó đứng, khi build_associable thực thi, giá trị của _typenil.

BƯỚC 2. đèo trong và chấp nhận Type Child

Có xem mẫu của bạn gửi child_type cùng với các dữ liệu mẫu điển hình, và điều khiển của bạn phải cho phép nó trong thông số mạnh mẽ của nó kiểm tra.

# 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 

Tất nhiên, (các) chế độ xem của bạn sẽ cần xử lý các loại mô hình khác nhau, nhưng điều này thể hiện.

Hy vọng điều này sẽ giúp ai đó ở ngoài đó.(Tại sao bạn lại cần gà đa hình?)

+1

Một điều nữa: Nếu yêu cầu cập nhật/vá sửa đổi child_type, bạn cần thận trọng hơn để duy trì tính toàn vẹn của cơ sở dữ liệu. Một giải pháp là thêm mã ngăn chặn hoặc bỏ qua yêu cầu sửa đổi 'polymorph_type' trong' controller # update' cho * đối tượng hiện có *. Tôi cũng thêm trường ẩn polymorph_type chỉ khi 'record_new?' Là đúng. – rodamn

Các vấn đề liên quan