Rails find_or_create_by більше ніж один атрибут?


202

У активному записі є зручний динамічний атрибут під назвою find_or_create_by:

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

Але що робити, якщо мені потрібно знайти_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.

Відповіді:


464

Кілька атрибутів можуть бути пов'язані з 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)

Можливо, вам також сподобається github.com/seamusabshere/upsert - перший аргумент - це хеш атрибутів, який використовується для пошуку або створення запису
Seamus Abshere

1
Корисно відзначити, що ініціалізувати виклики create () замість new (), які можуть бути викликані у випадку first_or_create
Sumit

Чи може хтось поділитися суттєвим прикладом цього використання? Я не знайомий з його розміщенням та дзвінками.
6 футів Дня

Я скасував видалення коду 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

Порада з оптимізації: незалежно від того, яке рішення ви обрали, подумайте про додавання індексів до атрибутів, які ви запитуєте найчастіше.


8
Одне, що слід зазначити, це те, що це не буде обробляти атрибути, які не можна присвоїти масі, хоча find_or_create_byбуде.
x1a4

1
Марко, спробуй Model.where(attributes).instance_eval{|q| q.first || q.create}.
хіроші

3
find_or_createтакож має перевагу використовувати транзакцію, де where….first || createвводиться умова гонки
мрм

Я б сказав, def self.find_or_create(attributes) self.where(attributes).first || self.create(attributes) endщо вам не потрібно повторюватисьModel
Августин Рідінгер

@AugustinRiedinger Вам також не потрібно selfвсередині методу (оскільки ви хочете видалити слова!).
David Tuite

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" }

створить новий GroupMember з назвою "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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.