Лучший способ найти find_or_create_by_id, но обновить атрибуты, если запись найдена - PullRequest
4 голосов
/ 01 марта 2011

Я ищу чистый способ создать запись с набором атрибутов, если запись не существует, и - если запись существует - обновить ее атрибуты.Мне нравится синтаксис блока в вызове find_or_create_by_id.Вот мой код:

@categories = Highrise::DealCategory.find(:all)

@categories.each do |category|
  puts "Category: #{category.name}"

  Category.find_or_create_by_id(category.id) do |c|
    c.name = category.name
  end
end

Проблема здесь в том, что если запись существует, но имя изменилось, она не обновляется.

Ищите чистое решение этой проблемы ...

Ответы [ 6 ]

4 голосов
/ 01 марта 2011

Вы можете написать свой собственный метод:

class ActiveRecord::Base
  def self.find_by_id_or_create(id, &block)
    obj = self.find_by_id( id ) || self.new
    yield obj
    obj.save
  end
end

использование

 Category.find_by_id_or_create(10) do |c|
   c.name = "My new name"
 end

Конечно, таким образом, вы должны расширить method missing метод и реализовать этот метод таким же образомкак другие find_by_something методы.Но для краткости этого будет достаточно.

1 голос
/ 11 июля 2016

Я использовал этот шаблон для семян:

Category.find_or_initialize_by(id: category.id).update |c|
  c.name = category.name
end

Он работает так же, как ответы Дейла Вийнанда и Теуласа (сохраняет экземпляр только один раз), но использует блок, как в вашем вопросе.

1 голос
/ 26 февраля 2015

Я кодировал этот искатель, который можно использовать для разных сценариев.

Самое важное, что при создании и обновлении он удаляет параметр :id.

Создание модели с :id может вызвать проблемы с MySql или PostgreSQL, поскольку Rails использует автоматический порядковый номер базы данных. Если вы создаете новые экземпляры модели с :id, вы можете получить UniqueViolation: ERROR: duplicate key value violates unique constraint.

# config/initializers/model_finders.rb
class ActiveRecord::Base

  def self.find_by_or_create(attributes, &block)
    self.find_by(attributes) || self.create(attributes.except(:id), &block)
  end


  def self.find_by_or_create!(attributes, &block)
    self.find_by(attributes) || self.create!(attributes.except(:id), &block)
  end


  def self.find_or_create_update_by(attributes, &block)
    self.find_by(attributes).try(:update, attributes.except(:id), &block) || self.create(attributes.except(:id), &block)
  end


  def self.find_or_create_update_by!(attributes, &block)
    self.find_by(attributes).try(:update!, attributes.except(:id), &block) || self.create!(attributes.except(:id), &block)
  end


  def self.find_by_or_initialize(attributes, &block)
    self.find_by(attributes) || new(attributes.except(:id), &block)
  end

end
0 голосов
/ 19 сентября 2013

Мне понравился ответ fl00r. Но почему мы должны каждый раз сохранять объект? Мы можем проверить, есть ли оно уже в записях, или же сохранить его

def self.find_or_create_by_id(id, &block)
    obj = self.find_by_id(id) 
    unless obj
      obj = self.create(id: id)
    end
    obj
end
0 голосов
/ 02 марта 2011

Попробуйте:

c = Category.find_or_initialize_by_id(category.id)
c.name = category.name
c.save!

Таким образом, вы сохраняете экземпляр только один раз, а не дважды, если вы вызвали find_or_create_by_id (при условии, что это новая запись).

0 голосов
/ 01 марта 2011

Я сделал это вчера, желая, чтобы был способ сделать это в однострочнике.

Закончено (с вашим кодом):

c = Category.find_or_initialize_by_id(category.id)
c.name = category.name
c.save

Возможно, естьболее хороший способ, но это то, что я использовал.

[Правка: используйте инициализацию вместо создания, чтобы избежать двойного попадания в БД)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...