47

Chciałbym ustawić relację polimorficzną z accepts_nested_attributes_for. Oto kod:accepts_nested_attributes_for with belongs_to polimorficzny

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 

Kiedy próbuję uzyskać dostęp Job.create(..., :client_attributes=>{...} daje mi NameError: uninitialized constant Job::Client

+0

Czy widziałeś railscast na temat skomplikowanych formularzy? http://railscasts.com/episodes/75-complex-forms-part-3 –

+0

Tak, przetasowałem własne rozwiązanie. – dombesz

Odpowiedz

5

Tylko zorientowali się, że szyny nie obsługuje tego rodzaju zachowania więc wymyśliłem następujący Obejście:

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 

daje mi skonfigurować moją postać tak:

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

Nie jest to dokładne rozwiązanie, ale idea jest ważna.

+1

Jest to poniżej podoptymalnego dla rozwiązania Dmitry/ScotterC poniżej, ponieważ Railsy przewidywały tę sprawę i umożliwiają prawidłowe zachowanie. –

+1

W tym przykładzie oceniasz wprowadzane przez użytkownika dane. Rozważ użycie zamiast tego 'constantize'. – anarchocurious

8

Powyższa odpowiedź jest świetna, ale nie działa z pokazaną konfiguracją. To zainspirowało mnie i udało mi się stworzyć Roztwór roboczy:

pracuje dla tworzenia i aktualizowania

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

Jest to poniżej poziomu rozwiązania Dmitry/ScotterC poniżej, ponieważ Railsy przewidywały tę sprawę i umożliwiają prawidłowe zachowanie. –

+0

@MarkP Ale podczas aktualizacji tego samego rekordu, który został utworzony, z powodu nadpisania 'client_attributes', tworzy nowy rekord zamiast go aktualizować. Czy mamy jakieś rozwiązanie? –

55

Miałem też problem z „ArgumentError. Nie można budować stowarzyszenie MODEL_NAME Próbujesz budować polimorficzne relacje jeden-do-jednego? "

I znalazłem lepsze rozwiązanie dla tego rodzaju problemu. Możesz użyć natywnej metody. Spójrzmy na realizację nested_attributes, wewnątrz 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 

Czyli co zrobić, musimy zrobić tutaj? Jest po prostu utworzyć build _ # {association_name} wewnątrz naszego modelu. Ja nie całkowicie przykład pracujący na dole:

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

Użyłem tej sztuczki 'build_xyz' z powodzeniem. Myślę, że brakuje ci linii, aby uzyskać 'contact_type' z paramów (a potem musisz usunąć to z params wysłanych do nowych) – tokland

+0

Może brakuje mi czegoś, ale wierzę w użycie słowa" kontakt "przez cały czas Metoda powinna być faktycznie zmieniona na 'client' ... Dodatkowo miałem problem z przekazaniem dwóch argumentów do tej metody zamiast jednej, gdy jest wywoływana. Drugi argument to pusty hash. – JackCA

+1

Dzięki @JackCA! Naprawiono kod i ostatni kod Rails 3.2 zostały zmienione, ponieważ w drugim argumencie teraz przechodzi opcje przypisania: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L351 –

10

I wreszcie got to do pracy z Rails 4.x Jest to oparte na odpowiedzi Dmitry/ScotterC, więc daj im +1.

KROK 1. Aby rozpocząć, tutaj jest pełny model z polimorficznych asocjacji:

# 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 

Tak, to nic nowego. Jednak możesz się zastanawiać, skąd pochodzi polymorph_type i jak jest ustawiona jego wartość? Jest to część bazowego rekordu bazy danych, ponieważ skojarzenia polimorficzne dodają do tabeli kolumny <association_name>_id i. W stanie, w którym jest wykonywany build_associable, wartość .

KROK 2. przepustkę i zaakceptować typu potomnego

Czy Pana zdaniem forma wysłać child_type wraz z typowymi danymi formularz, a kontroler musi pozwalać je sprawdzić jego mocne parametry.

# 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 

Oczywiście, twój widok (y) będzie musiał obsłużyć różne typy modeli, które są "powiązane", ale to demonstruje.

Mam nadzieję, że to komuś pomaga.(Dlaczego tak czy inaczej potrzebujesz polimorficznych kurczaków?)

+1

Inna sprawa: Jeśli żądanie aktualizacji/poprawki modyfikuje typ elementu podrzędnego, musisz zachować szczególną ostrożność, aby zachować integralność bazy danych. Jednym z rozwiązań jest dodanie kodu zapobiegającego lub ignorującego żądanie modyfikacji 'polymorph_type' w' controller # update' dla * istniejących obiektów *. Dodaję również ukryte pole typu polimorficznego tylko wtedy, gdy "record_new?" Jest prawdziwe. – rodamn

Powiązane problemy