54 votes

accepts_nested_attributes_for with belongs_to polymorphique

Je voudrais mettre en place une relation polymorphe avec accepts_nested_attributes_for . Voici le code :

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

Lorsque j'essaie d'accéder Job.create(..., :client_attributes=>{...} me donne NameError: uninitialized constant Job::Client

59voto

Dmitry Polushkin Points 920

J'ai également eu un problème avec le "ArgumentError : Cannot build association model_name. Essayez-vous de construire une association polymorphe biunivoque ?"

Et j'ai trouvé une meilleure solution pour ce genre de problème. Vous pouvez utiliser la méthode native. Regardons l'implémentation de nested_attributes, dans 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

Donc en fait, que devons-nous faire ici ? C'est juste de créer build_#{nom_de_l'association} dans notre modèle. J'ai fait un exemple totalement fonctionnel en bas :

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

22voto

rodamn Points 788

I enfin J'ai réussi à faire fonctionner cette fonctionnalité avec Rails 4.x. Ceci est basé sur la réponse de Dmitry/ScotterC, donc +1 à eux.

ÉTAPE 1. Pour commencer, voici le modèle complet avec association polymorphe :

# 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

Oui, ce n'est pas vraiment nouveau. Cependant, vous pourriez vous demander, où se trouve polymorph_type provient-il et comment sa valeur est-elle fixée ? Elle fait partie de l'enregistrement de la base de données sous-jacente, puisque les associations polymorphes ajoutent une valeur à l'enregistrement. <association_name>_id y <association_name>_type colonnes dans le tableau. En l'état actuel des choses, lorsque build_associable s'exécute, le _type La valeur de l'article est nil .

ÉTAPE 2. Passez et acceptez le type d'enfant

Faites en sorte que votre vue de formulaire envoie le child_type avec les données de formulaire habituelles, et votre contrôleur doit l'autoriser dans sa vérification des paramètres forts.

# 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

Bien sûr, votre ou vos vues devront gérer les différents types de modèles qui sont "associables", mais ceci en démontre un.

J'espère que cela aidera quelqu'un d'autre. (Pourquoi avez-vous besoin de poulets polymorphes de toute façon ?)

8voto

MarkP Points 51

La réponse ci-dessus est excellente mais ne fonctionne pas avec la configuration indiquée. Elle m'a inspiré et j'ai pu créer une solution qui fonctionne :

fonctionne pour créer et mettre à jour

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

5voto

dombesz Points 5576

Je viens de découvrir que rails ne supporte pas ce genre de comportement et j'ai donc trouvé la solution suivante :

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

Cela me permet de configurer mon formulaire comme ceci :

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

Ce n'est pas la solution exacte mais l'idée est importante.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X