47

Me gustaría establecer una relación polimórfica con accepts_nested_attributes_for. Aquí está el código:accepts_nested_attributes_for con belongs_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 

Cuando intento acceder a Job.create(..., :client_attributes=>{...} me da NameError: uninitialized constant Job::Client

+0

¿Has visto los rieles sobre formas complejas? http://railscasts.com/episodes/75-complex-forms-part-3 –

+0

Sí, lancé mi propia solución. – dombesz

Respuesta

5

Sólo cuenta de que los carriles no es compatible con este tipo de comportamiento por lo que me ocurrió con la siguiente solución:

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 

esto me da a configurar mi formulario como el siguiente:

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

No es la solución exacta, pero la idea es importante.

+1

Esto no es óptimo para la solución de Dmitry/ScotterC a continuación, ya que Rails anticipó este caso y le permite anular el comportamiento correctamente. –

+1

En este ejemplo, está evaluando la entrada del usuario. Considere usar 'constantize' en su lugar. – anarchocurious

8

La respuesta anterior es excelente, pero no funciona con la configuración que se muestra. Me inspiró y me fue capaz de crear una solución de trabajo:

obras para crear y actualizar

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

Esto no es óptimo para la solución de Dmitry/ScotterC a continuación, ya que Rails anticipó este caso y le permite anular el comportamiento correctamente. –

+0

@MarkP Pero al actualizar el mismo registro que se ha creado, debido a anulado 'client_attributes', crea un nuevo registro en lugar de actualizarlo. ¿Tenemos alguna solución para eso? –

55

También he tenido un problema con el "ArgumentError:. No se puede construir nombre_del_modelo asociación ¿Estás tratando de construir una asociación polimórfica uno-a-uno? "

Y encontré una mejor solución para este tipo de problema. Puedes usar el método nativo. Vamos a mirar a la aplicación nested_attributes, dentro 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 

Así que en realidad lo que hacemos tenemos que hacer aquí? Es solo para crear build _ # {association_name} dentro de nuestro modelo. He hice ejemplo de trabajo totalmente en la parte inferior:

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

He usado este truco 'build_xyz' con éxito. Creo que te falta una línea para obtener 'contact_type' de params (y luego debes quitarlo a los parámetros enviados a los nuevos) – tokland

+0

Me puede estar perdiendo algo, pero creo que el uso de la palabra' contact' durante el último El método en realidad debe cambiarse a 'client' ... Además, tuve un problema con dos argumentos que pasaron a este método en lugar de uno cuando se invoca build. El segundo argumento es un hash en blanco. – JackCA

+1

¡Gracias @JackCA! Se corrigió el código fijo y en el último código de Rails 3.2, porque en el segundo argumento ahora se pasan las opciones de asignación: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L351 –

10

I finalmente conseguí que esto funcione con rieles 4.x. Esto se basa en la respuesta de Dmitry/ScotterC, entonces +1 a ellos.

PASO 1. Para empezar, aquí es el modelo completo con la asociación polimórfica:

# 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 

Sí, eso no es nada realmente nuevo. Sin embargo, podría preguntarse, ¿de dónde viene polymorph_type y cómo se establece su valor? Es parte del registro de la base de datos subyacente, ya que las asociaciones polimórficas añaden columnas <association_name>_id y <association_name>_type a la tabla. Tal como está, cuando se ejecuta build_associable, el valor _type es nil.

PASO 2. Pase y aceptar el tipo de Niños

Haga que su opinión de la forma enviar el child_type junto con los datos del formulario típicos, y su controlador deben permitir que en sus fuertes parámetros de verificación.

# 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 

Por supuesto, su vista (s) tendrá que manejar los diferentes tipos de modelos que son 'asociables', pero esto demuestra uno.

Espero que esto ayude a alguien por ahí.(¿Por qué necesitas pollos polimórficos de todos modos?)

+1

Otra cosa: si una solicitud de actualización/parche modifica child_type, debe tener una precaución especial para mantener la integridad de la base de datos. Una solución es agregar código para evitar o ignorar una solicitud para modificar 'polymorph_type' en' controller # update' para * objetos existentes *. También agrego el campo oculto polymorph_type solo cuando 'record_new?' Es verdadero. – rodamn

Cuestiones relacionadas