Рельсы создают или обновляют магию?
у меня есть класс с именем CachedObject
который хранит общие сериализованные объекты, индексированные по ключу. Я хочу, чтобы этот класс реализовал create_or_update
метод. Если объект найден, он обновит его, в противном случае он создаст новый.
есть ли способ сделать это в Rails или мне нужно написать свой собственный метод?
6 ответов:
нет, если вы ищете "upsert" (где база данных выполняет обновление или инструкцию insert в той же операции) тип инструкции. Из коробки, рельсы и ActiveRecord не имеют такой функции. Вы можете использовать upsert драгоценный камень, однако.
в противном случае, вы можете использовать:
find_or_initialize_by
илиfind_or_create_by
, которые предлагают аналогичную функциональность, хотя и за счет дополнительного хита базы данных, что в большинстве случаев вряд ли является проблемой совсем. Поэтому, если у вас нет серьезных проблем с производительностью, я бы не использовал камень.например, если ни один пользователь не найден с именем "Roger", новый экземпляр пользователя создается с его
name
установите значение "Roger".user = User.where(name: "Roger").first_or_initialize user.email = "email@example.com" user.save
кроме того, вы можете использовать
find_or_initialize_by
.user = User.find_or_initialize_by(name: "Roger")
В Rails 3.
user = User.find_or_initialize_by_name("Roger") user.email = "email@example.com" 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@example.com" user.save end
в 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: "a@b.com").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
добавьте это в свою модель:
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)
вы можете сделать это в одном заявлении, как это:
CachedObject.where(key: "the given key").first_or_create! do |cached| cached.attribute1 = 'attribute value' cached.attribute2 = 'attribute value' end
старый вопрос, но бросая мое решение в кольцо для полноты. Мне это было нужно, когда мне нужна была конкретная находка, но другое создание, если оно не существует.
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
The продолжение gem добавляет update_or_create метод, который, кажется, делает то, что вы ищете.