Rails 4: .save не обновляет updated_at для существующих записей?

#ruby-on-rails #rspec

#ruby-on-rails #rspec

Вопрос:

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

Сегодня 18 июня:

     Loading development environment (Rails 4.1.1)
    irb(main):001:0>  o = Order.last
      Order Load (0.4ms)  SELECT  `orders`.* FROM `orders`   ORDER BY `orders`.`id` DESC LIMIT 1
    => #<Order id: 24, user_id: 1, order_date: nil, total_cents: 0, total_currency: "USD", shipping_cents: 0, shipping_currency: "USD", tax_cents: 0, tax_currency: "USD", subtotal_cents: 0, subtotal_currency: "USD", created_at: "2014-06-13 21:24:38", updated_at: "2014-06-13 21:24:38", status: "Incomplete", coupon_id: nil>
    irb(main):002:0> o.save!
       (0.4ms)  BEGIN
       (0.3ms)  COMMIT
    => true
    irb(main):003:0> o.updated_at
    => Fri, 13 Jun 2014 21:24:38 UTC  00:00
    irb(main):004:0> o.reload
      Order Load (0.7ms)  SELECT  `orders`.* FROM `orders`  WHERE `orders`.`id` = 24 LIMIT 1
    => #<Order id: 24, user_id: 1, order_date: nil, total_cents: 0, total_currency: "USD", shipping_cents: 0, shipping_currency: "USD", tax_cents: 0, tax_currency: "USD", subtotal_cents: 0, subtotal_currency: "USD", created_at: "2014-06-13 21:24:38", updated_at: "2014-06-13 21:24:38", status: "Incomplete", coupon_id: nil>
    irb(main):005:0> o.updated_at
    => Fri, 13 Jun 2014 21:24:38 UTC  00:00
  

Причина, по которой меня это волнует, заключается в том, что я оборачиваю внутренние компоненты функции, ActiveRecord::Base.transaction поэтому я хочу проверить, что они не обновляются, когда что-то идет не так:

Тест Rspec (который проходит все время, когда он не должен, поскольку updated_at не изменяется независимо от успеха или неудачи):

   it "rollsback the transaction" do
    ...
    order.stub(:save!).and_raise(StandardError)
    expect { payment_info.save }.not_to change { [billing_address.updated_at, 
                                                  shipping_address.updated_at,
                                                  order.user.updated_at,
                                                  order.updated_at] }
  end
  

И мой метод:

 def process_billing_info!
  ActiveRecord::Base.transaction do
    billing_address.save!
    shipping_address.save!
    user.save!
    order.update_all_fees!
  end
rescue Exception => e
  false
end
  

Ответ №1:

Как вы можете видеть в своем журнале, UPDATE SQL-запрос не выполняется. Rails вообще не обновляет вашу запись. Это связано с тем, что .save фактически запись сохраняется только в том случае, если были внесены изменения.

Существует метод .touch (документация), который вы можете вызвать, чтобы обновить updated_at поле без необходимости вносить изменения в вашу запись:

 1.9.3p489 :005 > Intervention.first.touch
  Intervention Load (12.9ms)  SELECT "interventions".* FROM "interventions" LIMIT 1
  SQL (20.5ms)  UPDATE "interventions" SET "updated_at" = '2014-06-18 16:34:03.924648' WHERE "interventions"."id" = 1
 => true 
  

Здесь мы видим UPDATE SQL-запрос.

Комментарии:

1. Хороший момент. Это было потому, что я никак не менялся order , поэтому updated_at не менялся

2. Спасибо, я использовал update_columns в своем фильтре after_save, чтобы избежать бесконечного цикла. Это не было обновлением поля updated_at . Поэтому я явно обновил поле.

3. @SimmiBadhan теоретически, вы не должны использовать us update_columns , потому что он пропускает обратные вызовы. Вы должны использовать before_save и присвоить желаемый атрибут (вычисленному) значению, а затем позволить Rails сохранить запись. Достигается before_save после прохождения проверок. Это означает, что ваша запись уже .valid? существует при выполнении этого кода.

4. @MrYoshiji Я тоже пытался использовать фильтр before_save. Но это приводит к бесконечному циклу при сохранении записи. Решением моей проблемы было использовать только update_columns, поскольку мне не нужны были обратные вызовы, и я просто хотел обновить хэш-поле некоторой информацией.

Ответ №2:

В дополнение к ответу @MrYoshiji, вот еще один способ использовать .touch :

 if o.save!
  o.touch unless o.changed?
end
  

Комментарии:

1. Возникла проблема, из-за которой мне нужно было обновлять временную метку записи каждый раз, когда выполнялось действие обновления. модифицированная версия этого ответа — это то, что мне было нужно: if record.update(params) record.touch unless record.changed? end