Rails find_or_create_by más de un atributo?

203

Hay un atributo dinámico útil en el registro activo llamado find_or_create_by:

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

Pero, ¿qué sucede si necesito encontrar_o_crear con más de un atributo?

Digamos que tengo un modelo para manejar una relación M: M entre Group y Member llamada GroupMember. Podría tener muchas instancias donde member_id = 4, pero nunca quiero más de una 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 puede haber mejores formas de manejar esto, pero me gusta la conveniencia de la idea de find_or_create.

tybro0103
fuente

Respuestas:

465

Se pueden conectar múltiples atributos con un and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(utilícelo find_or_initialize_bysi no desea guardar el registro de inmediato)

Editar: El método anterior está en desuso en Rails 4. La nueva forma 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 estos factores se tomaron de los rieles, solo los atributos específicos.

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)
x1a4
fuente
También le puede gustar github.com/seamusabshere/upsert : el primer argumento es un hash de atributos que se utiliza para buscar o crear un registro
Seamus Abshere
1
Es útil tener en cuenta que initialize llama a create () en lugar de new () que podría llamarse en caso de first_or_create
Sumit Bisht
¿Alguien puede compartir un ejemplo esencial de este uso? No estoy familiarizado con su ubicación y llamadas.
6 pies Dan
Revertí la eliminación del código de Rails 3, ya que mucha gente todavía no está usando Rails 4.
Kyle Heironimus
Solo para agregar: las funciones find_or_create_by _ * () han quedado en desuso en Rails 4, sin embargo, find_or_create_by () NO. Está bien usar find_or_create_by (). Verifique el commit donde se agregó esto github.com/rails/rails/commit/… y la documentación oficial de Rails 4.2 para el uso: guides.rubyonrails.org/v4.2.0/…
CodeExpress
33

Para cualquier otra persona que se encuentre con este hilo pero necesite encontrar o crear un objeto con atributos que puedan cambiar según 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.

Marco
fuente
8
Una cosa a tener en cuenta es que esto no manejará atributos que no pueden asignarse en masa, mientras que lo find_or_create_byhará.
x1a4
1
Marco, inténtalo Model.where(attributes).instance_eval{|q| q.first || q.create}.
hiroshi
3
find_or_createtambién tiene el beneficio de usar una transacción, donde where….first || createintroduce una condición de carrera
mrm
Yo diría def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) endque no es necesario que repitasModel
Augustin Riedinger
@AugustinRiedinger Tampoco necesitas selfdentro del cuerpo del método (¡ya que estás buscando eliminar palabras!).
David Tuite
31

En Rails 4 podrías hacer:

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

Y el uso wherees diferente:

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

Esto llamará createa GroupMember.where(member_id: 4, group_id: 7):

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

Por el contrario, el find_or_create_by(member_id: 4, group_id: 7)llamará createa GroupMember:

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

Por favor, consulte este compromiso relevante sobre rieles / rieles.

Juanito Fatas
fuente
16

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 en el que no está buscando.

Asumiendo:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

luego

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

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

Daniel Murphy
fuente
4

Tu puedes 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
dorio
fuente