Rails buscar o crear por más de un atributo?


Hay un atributo dinámico útil en active-record llamado find_or_create_by:

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

Pero, ¿qué pasa si necesito find_or_create por más de un atributo?

Digamos que tengo un modelo para manejar una relación M:M entre Grupo y Miembro llamado 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 maneras de manejar esto, pero me gusta la conveniencia de la idea de find_or_create.

Author: Marlin Pierce, 2010-06-15

5 answers

Múltiples atributos se pueden conectar con un and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

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

Edit: El método anterior está obsoleto 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

Edit 2: No todos estos fueron factorizados de rails 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)

Se convirtió en

GroupMember.find_or_create_by(member_id: 4, group_id: 7)
 451
Author: x1a4,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-17 12:26:06

Para cualquier otra persona que se encuentre con este hilo pero necesite 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.

 30
Author: Marco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-26 21:00:42

En Rails 4 puedes hacer:

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

Y use where es diferente:

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

Esto llamará create en 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á a create el GroupMember:

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

Por favor, vea este[19]} commit relevante en rails/rails.

 26
Author: Juanito Fatas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-04 02:37:51

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

Asumiendo:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

Entonces

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

Creará un nuevo miembro del grupo con el nombre "John Doe" si no encuentra uno con member_id 4 and group_id 7

 13
Author: Daniel Murphy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-19 14:01:27

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 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
 3
Author: Dorian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-11 19:54:15