61

¿Podría decirme cuál es la mejor práctica para crear has_one relations?Rails - Mejores prácticas: cómo crear relaciones has_one dependientes

f.e. si tengo un modelo de usuario, y debe tener un perfil ...

¿Cómo podría lograr eso?

Una solución sería:

# user.rb 
class User << ActiveRecord::Base 
    after_create :set_default_association 

    def set_default_association 
    self.create_profile 
    end 
end 

Pero eso no parece muy limpio ... Cualquier sugiere?

Respuesta

107

Las mejores prácticas para crear relación has_one es utilizar la devolución de llamada ActiveRecord before_create vez que after_create. O utilice una devolución de llamada incluso anterior y trate los problemas (si los hay) del niño que no pasa su propio paso de validación.

Porque:

  • con una buena codificación, usted tiene la oportunidad para las validaciones del registro hijo que se muestran al usuario si las validaciones fallan
  • está más limpia y apoyado explícitamente por ActiveRecord - AR llena automagicamente en la clave externa en el registro secundario después de guardar el registro principal (en crear). AR luego guarda el registro secundario como parte de la creación del registro principal.

Cómo hacerlo:

# in your User model... 
has_one :profile 
before_create :build_default_profile 

private 
def build_default_profile 
    # build default profile instance. Will use default params. 
    # The foreign key to the owning User model is set automatically 
    build_profile 
    true # Always return true in callbacks as the normal 'continue' state 
     # Assumes that the default_profile can **always** be created. 
     # or 
     # Check the validation of the profile. If it is not valid, then 
     # return false from the callback. Best to use a before_validation 
     # if doing this. View code should check the errors of the child. 
     # Or add the child's errors to the User model's error array of the :base 
     # error item 
end 
+3

+1 para conocimiento de la validación de niños. – PeterWong

+0

¿Podría eso también ser manejado con una sola línea? -> before_filter: build_profile? – Lichtamberg

+1

@Lichtamberg: Sí, pero agregaría un comentario: "Crea un perfil predeterminado. DEBE siempre validar". NOTA: sería "before_create: build_profile" y no "before_filter". Si no validar, obtendría un mensaje de error muy confuso para el usuario. O NO se crearía, lo que significaría que terminarías con un usuario sin perfil. También debe probar los casos de esquina en sus pruebas. –

24

Su solución es sin duda una buena manera de hacerlo (al menos hasta que la superan), pero se puede simplificarlo:

# user.rb 
class User < ActiveRecord::Base 
    has_one  :profile 
    after_create :create_profile 
end 
5
Probablemente no

la solución más limpia, pero ya tenía una base de datos con medio millón de registros, algunos de los cuales ya tenían el modelo 'Perfil' creado, y algunos de los cuales no. Fuimos con este enfoque, que garantiza que un modelo de perfil está presente en cualquier punto, sin necesidad de pasar y generar retroactivamente todos los modelos de perfil.

alias_method :db_profile, :profile 
def profile 
    self.profile = Profile.create(:user => self) if self.db_profile.nil? 
    self.db_profile 
end 
4

Así es como lo hago. No estoy seguro de cómo es este estándar, pero funciona muy bien y su perezosa en que no crea una carga extra a menos que sea necesaria la construcción de la nueva asociación (estoy feliz de ser corregido en esto):

def profile_with_auto_build 
    build_profile unless profile_without_auto_build 
    profile_without_auto_build 
end 

alias_method_chain :profile, :auto_build 

Esto también significa que la asociación está allí tan pronto como lo necesite. Supongo que la alternativa es enlazar en after_initialize, pero parece agregar bastante sobrecarga, ya que se ejecuta cada vez que se inicializa un objeto y puede haber ocasiones en las que no te importa acceder a la asociación. Parece un desperdicio verificar su existencia.

+0

Creo que esta respuesta es una mejor solución que otras, ya que evita problemas con la validación en el modelo de Perfil. Gracias – ole

+0

también puede tener autosave: 'has_one: profile,: autosave => true' – montrealmike

+0

@montrealmike, ¿eso tiene que ver con un perfil que falta para empezar? Es decir. si aún no se ha ejecutado build_profile, ¿esto crearía uno en guardar? También me he encontrado con esto: https://github.com/phildionne/associates, que podría proporcionar otro camino para los formularios de modelos múltiples. –

19

Si se trata de una nueva asociación en una gran base de datos existente, voy a gestionar la transición de esta manera:

class User << ActiveRecord::Base 
    has_one :profile 
    before_create :build_associations 

    def profile 
    super || build_profile(avatar: "anon.jpg") 
    end 

private 
    def build_associations 
    profile || true 
    end 
end 

de modo que los registros de usuario existentes obtener un perfil cuando se le preguntó por ella y se crean nuevos con él. Esto también coloca los atributos predeterminados en un lugar y funciona correctamente con accepts_nested_attributes_for en Rails 4 en adelante.