Рельсы создают или обновляют магию?


у меня есть класс с именем CachedObject который хранит общие сериализованные объекты, индексированные по ключу. Я хочу, чтобы этот класс реализовал create_or_update метод. Если объект найден, он обновит его, в противном случае он создаст новый.

есть ли способ сделать это в Rails или мне нужно написать свой собственный метод?

6 73

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 метод, который, кажется, делает то, что вы ищете.