#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