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.
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)
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.
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.
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
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
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