Upsert в Rails ActiveRecord

Есть ли в ActiveRecord встроенная функция upsert? Я знаю, что мог бы написать это сам, но я, очевидно, не хочу, если такая вещь уже существует.


person Jason Swett    schedule 14.01.2011    source источник


Ответы (7)


Model.find_or_initialize скорее всего делает то, что вы хотите. Вы можете связать его с save или update_attributes, если это имеет смысл.

Дополнительные сведения см. в руководствах по Rails.

person Andy Lindeman    schedule 14.01.2011
comment
см. комментарий к ответу пасты - person tybro0103; 16.09.2013
comment
Кто-нибудь видел, как это вызывает upsert? В руководстве по рельсам указано, что новый объект еще не будет сохранен в БД, поэтому я не понимаю, как это является истинным обновлением БД. То есть не будет надежно работать в многопоточной среде. - person stuckj; 23.12.2013
comment
Это решение имеет проблемы с параллелизмом. Это не удастся, если другой поток обновит таблицу между find_or_initialize и save. - person Barry Fruitman; 07.01.2017
comment
Это не апсерт. С точки зрения результата это эквивалентно (пока не возникают проблемы параллелизма, такие как упоминания @BarryFruitman), но с точки зрения производительности это не так. - person YWCA Hello; 03.11.2017

В рельсах 6 есть потрясающая новая функция: они добавили upsert и upsert_all к ActiveRecord.

Дополнительную информацию можно найти здесь https://edgeapi.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html#method-i-upsert_all

person Chris    schedule 25.06.2019
comment
Это следует использовать с осторожностью, так как upsert и upsert_all обходят все проверки и обратные вызовы. - person bigtunacan; 22.07.2021

Я только что наткнулся на эту библиотеку: https://github.com/seamusabshere/upsert

Я еще не проверял это, но выглядит многообещающе

person jacklin    schedule 21.06.2012

Механизм IMO Upsert требует индивидуальной настройки для каждой модели.

Таким образом, лучшим решением было бы реализовать собственный SQL-запрос для модели, например.

insert into <table> (<field_1>, ..., <field_n>) 
  values (<field_1_value>, ..., <field_n_value>)
on duplicate key update
  field_x = field_x_value,
  ...
  field_z = field_z_value;
person Serge Seletskyy    schedule 22.04.2016

Также есть Model.find_or_create

person Pasta    schedule 14.01.2011
comment
Это не делает upsert. Он делает выбор, а затем (необязательно) вставку. В то время как вы получаете тот же эффект в однопоточном мире, в многопоточном мире он вам понадобится для фактического upsert. - person tybro0103; 16.09.2013

Rails 6 вводит create_or_find_by для этого случая https://github.com/rails/rails/pull/31989

Также для большого количества записей можно использовать https://github.com/zdennis/activerecord-import< /а>

Пример:

Book.import [book], on_duplicate_key_update: [:title]
person gayavat    schedule 20.12.2019

Я написал сообщение в блоге о том, как мы можем достичь этого. Ознакомьтесь с ним здесь.

Вам нужно будет написать активное расширение записи. Это будет выглядеть примерно так.

module ActiveRecordExtension
  extend ActiveSupport::Concern

  def self.upsert(attributes)
    begin
        create(attributes)
    rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation => e
        find_by_primary_key(attributes['primary_key']).
        update(attributes)
    end
  end
end

ActiveRecord::Base.send(:include, ActiveRecordExtension)
person Nithin Krishna    schedule 17.08.2014
comment
Не сторонник использования ошибок для управления ожидаемым логическим потоком, особенно когда вы можете предсказать, когда эта ошибка произойдет. - person kmanzana; 08.01.2016
comment
Для тех, кто проголосовал против, это стандартный подход в Rails 6 хотя завернутый в транзакцию. - person mlt; 09.10.2019