Rails find_or_create_by более одного атрибута?

203

В active-record есть полезный динамический атрибут с именем find_or_create_by:

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

Но что, если мне нужно найти find_or_create более чем одним атрибутом?

Скажем, у меня есть модель для обработки отношений M: M между Группой и Участником, которая называется GroupMember. У меня может быть много случаев, когда member_id = 4, но я никогда не хочу более одного раза, когда member_id = 4 и group_id = 7. Я пытаюсь выяснить, возможно ли сделать что-то подобное:

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

Я понимаю, что могут быть лучшие способы справиться с этим, но мне нравится удобство идеи find_or_create.

tybro0103
источник

Ответы:

465

Несколько атрибутов могут быть связаны с and:

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

(используйте, find_or_initialize_byесли вы не хотите сохранять запись сразу)

Редактировать: вышеупомянутый метод устарел в Rails 4. Новый способ сделать это будет:

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

и

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

Редактировать 2: Не все из них были сделаны из рельсов, только атрибуты конкретных.

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

пример

GroupMember.find_or_create_by_member_id_and_group_id(4, 7)

стал

GroupMember.find_or_create_by(member_id: 4, group_id: 7)
x1a4
источник
Вам также может понравиться github.com/seamusabshere/upsert - первый аргумент - это хэш атрибутов, который используется для поиска или создания записи
Seamus Abshere
1
Полезно отметить, что initialize вызывает create () вместо new (), который может быть вызван в случае first_or_create
Sumit Bisht
Может кто-нибудь поделиться суть примера этого использования? Я не знаком с его размещением и вызовом.
футов Дан
Я отменил удаление кода Rails 3, так как многие люди еще не используют Rails 4.
Кайл Хейронимус
Просто добавьте: функции find_or_create_by _ * () устарели в Rails 4, однако find_or_create_by () - НЕ. Можно использовать find_or_create_by (). Проверьте коммит, где это было добавлено github.com/rails/rails/commit/… и официальную документацию по Rails 4.2 для использования: guides.rubyonrails.org/v4.2.0/…
CodeExpress
33

Для тех, кто сталкивается с этим потоком, но ему нужно найти или создать объект с атрибутами, которые могут меняться в зависимости от обстоятельств, добавьте в вашу модель следующий метод:

# 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

Совет по оптимизации: независимо от того, какое решение вы выберете, рассмотрите возможность добавления индексов для атрибутов, которые вы запрашиваете чаще всего.

Marco
источник
8
Стоит отметить, что это не будет обрабатывать атрибуты, которые не могут быть назначены по массе, в то время как find_or_create_byбудет.
x1a4
1
Марко, попробуй Model.where(attributes).instance_eval{|q| q.first || q.create}.
Хироши
3
find_or_createтакже имеет преимущество использования транзакции, где where….first || createвводится условие гонки
mrm
Я бы сказал, def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) endчто вам не нужно повторятьModel
Августин Ридингер
@AugustinRiedinger Вам также не нужно selfвнутри тела метода (так как вы хотите удалить слова!).
Дэвид Туит
31

В Rails 4 вы можете сделать:

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

И использование whereотличается:

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

Это будет призывать createк GroupMember.where(member_id: 4, group_id: 7):

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

Наоборот, то find_or_create_by(member_id: 4, group_id: 7)призовет createна GroupMember:

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

Пожалуйста, смотрите этот соответствующий коммит на рельсах / рельсах.

Хуанито Фатас
источник
16

Передав блок в find_or_create, вы можете передать дополнительные параметры, которые будут добавлены к объекту, если он создан новым. Это полезно, если вы проверяете наличие поля, по которому вы не ищете.

Предполагая, что:

class GroupMember < ActiveRecord::Base
    validates_presence_of :name
end

затем

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

создаст новый член группы с именем «Джон Доу», если не найдет его с member_id 4 and group_id 7

Дэниел Мерфи
источник
4

Ты можешь сделать:

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

Или просто инициализировать:

User.find_or_initialize_by(first_name: 'Penélope', last_name: 'Lopez')
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_initialize
Дориан
источник