190

Hay un atributo dinámico útil en activo disco llamado find_or_create_by:Rails find_or_create por más de un atributo?

Model.find_or_create_by_<attribute>(:<attribute> => "")

Pero lo que si necesito find_or_create por más de un atributo?

Supongamos que tengo un modelo para manejar una relación M: M entre un grupo y un miembro llamado GroupMember. Podría haber muchas instancias donde member_id = 4, pero no quiero más de una vez instancia donde member_id = 4 y group_id = 7. Estoy tratando de averiguar si es posible hacer algo como esto:

GroupMember.find_or_create(:member_id => 4, :group_id => 7) 

Me doy cuenta de que hay mejores maneras de manejar esto, pero me gusta la conveniencia de la idea de find_or_create.

Respuesta

443

atributos múltiples pueden ser conectados con un and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7) 

(find_or_initialize_by utilizar si no desea guardar el registro de inmediato)

Editar: El método anterior está en desuso en carriles 4. La nueva manera de hacerlo será:

GroupMember.where(:member_id => 4, :group_id => 7).first_or_create 

y

GroupMember.where(:member_id => 4, :group_id => 7).first_or_initialize 

Edición 2: No todos ellos se tuvieran en cuenta fuera de los carriles sólo los específicos de atributos.

https://github.com/rails/rails/blob/4-2-stable/guides/source/active_record_querying.md

Ejemplo

GroupMember.find_or_create_by_member_id_and_group_id(4, 7) 

convirtió

GroupMember.find_or_create_by(member_id: 4, group_id: 7) 
+45

Gracias por los carriles 4 de actualización. Me gusta;) – tybro0103

+0

También te puede interesar https://github.com/seamusabshere/upsert - el primer argumento es un hash de atributos que se usa para buscar o crear un registro –

+1

Es útil tener en cuenta que las llamadas de inicialización crean() en lugar de new() que podría llamarse en caso de first_or_create –

29

Para cualquier otra persona que se topa con este hilo, pero necesita encontrar o crear un objeto con atributos que podrían cambiar dependiendo de las circunstancias , agregue el siguiente método a su modelo:

# Return the first object which matches the attributes hash 
# - or - 
# Create new object with the given attributes 
# 
def self.find_or_create(attributes) 
    Model.where(attributes).first || Model.create(attributes) 
end 

Consejo de optimización:, independientemente de la solución que elija, considere agregar índices para los atributos que está consultando con más frecuencia.

+8

Una cosa a tener en cuenta es que esto no maneja atributos que no pueden ser masivos asignado, mientras que 'find_or_create_by' lo hará. – x1a4

+1

Marco, pruebe 'Model.where (attributes) .instance_eval {| q | q.primero || q.create} '. – hiroshi

+3

'' 'find_or_create''' también tiene la ventaja de usar una transacción, donde' '' donde ... .first || create''' introduce una condición de carrera – mrm

12

Al pasar un bloque a find_or_create, puede pasar parámetros adicionales que se agregarán al objeto si se crea nuevo. Esto es útil si está validando la presencia de un campo que no está buscando.

Suponiendo:

class GroupMember < ActiveRecord::Base 
    validates_presence_of :name 
end 

continuación

GroupMember.where(:member_id => 4, :group_id => 7).first_or_create { |gm| gm.name = "John Doe" } 

va a crear un nuevo GroupMember con el nombre "John Doe" si no encuentra uno con member_id 4and group_id 7

25

En Rails 4 usted podría hacer:

GroupMember.find_or_create_by(member_id: 4, group_id: 7) 

Y el uso de where es diferente:

GroupMember.where(member_id: 4, group_id: 7).first_or_create 

Para ello, será create en GroupMember.where(member_id: 4, group_id: 7):

GroupMember.where(member_id: 4, group_id: 7).create 

Por el contrario, la find_or_create_by(member_id: 4, group_id: 7) llamarán create en GroupMember:

GroupMember.create(member_id: 4, group_id: 7) 

favor ver esto relevante commit en rieles/rieles.

+0

+1 para apuntar a un compromiso por el contexto. – jrhorn424

3

que puede hacer:

User.find_or_create_by(first_name: 'Penélope', last_name: 'Lopez') 
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_create 

o simplemente para inicializar:

User.find_or_initialize_by(first_name: 'Penélope', last_name: 'Lopez') 
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_initialize 
+0

http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by – Duke

Cuestiones relacionadas