Rails создает или обновляет магию?
У меня есть класс под названием CachedObject
, который хранит обобщенные сериализованные объекты, индексированные ключом. Я хочу, чтобы этот класс реализовал метод create_or_update
. Если объект найден, он обновит его, иначе он создаст новый.
Есть ли способ сделать это в Rails или мне нужно написать собственный метод?
Ответы
Ответ 1
Нет, если вы ищете оператор типа "upsert" (где база данных выполняет обновление или оператор вставки в той же операции). Стандартно Rails и ActiveRecord не имеют такой возможности. Вы можете использовать драгоценный камень upsert, однако.
В противном случае вы можете использовать: find_or_initialize_by
или find_or_create_by
, которые предлагают аналогичную функциональность, хотя и за счет дополнительного попадания в базу данных, что в большинстве случаев вряд ли является проблемой вообще. Поэтому, если у вас нет серьезных проблем с производительностью, я бы не стал использовать этот драгоценный камень.
Например, если не найдено ни одного пользователя с именем "Roger", создается экземпляр нового пользователя с его name
установленным в "Roger".
user = User.where(name: "Roger").first_or_initialize
user.email = "[email protected]"
user.save
Кроме того, вы можете использовать find_or_initialize_by
.
user = User.find_or_initialize_by(name: "Roger")
В рельсах 3.
user = User.find_or_initialize_by_name("Roger")
user.email = "[email protected]"
user.save
Вы можете использовать блок, но блок запускается, только если запись новая.
User.where(name: "Roger").first_or_initialize do |user|
# this won't run if a user with name "Roger" is found
user.save
end
User.find_or_initialize_by(name: "Roger") do |user|
# this also won't run if a user with name "Roger" is found
user.save
end
Если вы хотите использовать блок независимо от сохраняемости записи, tap
на результат:
User.where(name: "Roger").first_or_initialize.tap do |user|
user.email = "[email protected]"
user.save
end
Ответ 2
В Rails 4 вы можете добавить к определенной модели:
def self.update_or_create(attributes)
assign_or_new(attributes).save
end
def self.assign_or_new(attributes)
obj = first || new
obj.assign_attributes(attributes)
obj
end
и используйте его как
User.where(email: "[email protected]").update_or_create(name: "Mr A Bbb")
Или, если вы предпочитаете добавлять эти методы ко всем моделям, помещенным в инициализатор:
module ActiveRecordExtras
module Relation
extend ActiveSupport::Concern
module ClassMethods
def update_or_create(attributes)
assign_or_new(attributes).save
end
def update_or_create!(attributes)
assign_or_new(attributes).save!
end
def assign_or_new(attributes)
obj = first || new
obj.assign_attributes(attributes)
obj
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecordExtras::Relation
Ответ 3
Добавьте это в свою модель:
def self.update_or_create_by(args, attributes)
obj = self.find_or_create_by(args)
obj.update(attributes)
return obj
end
С этим вы можете:
User.update_or_create_by({name: 'Joe'}, attributes)
Ответ 4
Вы можете сделать это в одном из следующих утверждений:
CachedObject.where(key: "the given key").first_or_create! do |cached|
cached.attribute1 = 'attribute value'
cached.attribute2 = 'attribute value'
end
Ответ 5
Старый вопрос, но бросание моего решения в кольцо для полноты.
Мне это нужно, когда мне нужна была определенная находка, но другое создание, если оно не существует.
def self.find_by_or_create_with(args, attributes) # READ CAREFULLY! args for finding, attributes for creating!
obj = self.find_or_initialize_by(args)
return obj if obj.persisted?
return obj if obj.update_attributes(attributes)
end
Ответ 6
sequel gem добавляет метод update_or_create который, кажется, делает то, что вы ищете.
Ответ 7
Волшебство, которое вы искали, было добавлено в Rails 6
Теперь вы можете сохранить (обновить или вставить).
Model.upsert(column_name: value)
Примечание: для массового обновления или создания вы можете использовать upsert_all