ごきげんよう。 MFクラウド給与の開発を担当している、Railsエンジニアの大津です。 朝ドラに影響されているわけではありません。
先日、モデルのdestroy呼び出しの確認をするために、
expect(tested).to receive(:destroy).once
のようにして、テストを書いたら、以下のように失敗してしまうことがありました。
expected: 1 time with any arguments received: 2 times
設計を間違えて、ActiveRecord.destroyを二回やっていた
MFクラウドシリーズには、会計や請求書などの複数のプロダクトがあります。 それらの処理は相互に関わっている部分があり、意図せずモデルのdestroyが2回呼ばれていたことが原因だったのですが、 「ARってdestroyを2回呼んじゃって大丈夫なのか?」という不安が出てきました。
結論としては、「ARが上手いこと判断してくれて大丈夫だった」のですが、サンプルを使って、その挙動を確認してみようかと思います。
サンプル用意
映画「007は二度死ぬ」を題材にサンプルプロジェクトを作ってみます。
~/work$ mkdir you_only_live_twice && cd you_only_live_twice ~/work/you_only_live_twice$ bundle init ~/work/you_only_live_twice$ bundle exec rails new . -BJT ~/work/you_only_live_twice$ bundle exec rails g model Mi6::Agent name:string
こんな感じで、Mi6::Agent
モデルを用意して(手順は大分端折ってます)、以下のようなクラス群をモデル層に定義してみます。
class Assassin def attack(agent) Rails.logger.info("Kill #{agent.name}!") agent.destroy end end class Villain def initialize @minion = Assassin.new end def encounter(agent) # 悪役は、エージェントに遭遇したら手下に殺させる @minion.attack(agent) end end class Mi6::Manager def infiltrate(agent, pretender, villain) # 協力者を使って、エージェントの死亡を偽装し、 pretender.attack(agent) # 敵地に潜りこませようとするが、残念、お約束通り敵にバレる villain.encounter(agent) end end
検証
require 'rails_helper' RSpec.describe Mi6::Manager, type: :model do describe "#infiltrate" do let(:m) { Mi6::Manager.new } let(:bond) { create(:mi6_agent, name: "James Bond") } let(:ling) { Assassin.new } let(:blofeld) { Villain.new } it do expect(bond).to receive(:destroy).twice m.infiltrate(bond, ling, blofeld) end end end
こんなテストを書いて、実行してみると、
~/work/you_only_live_twice$ bundle exec rspec spec/models/mi6/manager_spec.rb . Finished in 0.0164 seconds (files took 1.17 seconds to load) 1 example, 0 failures
成功しました。確かに2回呼ばれているようです。 どんなSQLが発行されているのか、rails consoleから確認してみます。
irb(main):005:0> m.infiltrate(bond, ling, blofeld) Kill James Bond! (0.2ms) BEGIN SQL (0.3ms) DELETE FROM `mi6_agents` WHERE `mi6_agents`.`id` = 13 (2.9ms) COMMIT Kill James Bond! (0.1ms) BEGIN (0.1ms) COMMIT => #<Mi6::Agent id: 13, name: "James Bond", created_at: "2016-01-25 14:45:17", updated_at: "2016-01-25 14:45:17">
2回目のdestroy実行では、何も発行されていないことがわかります。 確かに、映画のボンドと同じようにARは2回死んではいませんでした。
最後に
さて。僕はこのサンプルを作るのに、映画のストーリーやら登場人物やらを調べて、えらい時間がかかりました。具体的な時間は恥ずかしいので内緒です。
マネーフォワードでは、疑問や課題に対して(僕のように)真摯に向き合えるエンジニアは絶賛募集中です。 一緒に厨ニっぽいクラス名とか設定とか考えたりして盛り上がりましょう!( @nyangry 談)
【採用サイト】 ■マネーフォワード採用サイト ■Wantedly | マネーフォワード
【公開カレンダー】 ■マネーフォワード公開カレンダー
【プロダクト一覧】 ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 iPhone,iPad ■家計簿アプリ・クラウド家計簿ソフト『マネーフォワード』 Android ■クラウド型会計ソフト『MFクラウド会計』 ■クラウド型請求書管理ソフト『MFクラウド請求書』 ■クラウド型給与計算ソフト『MFクラウド給与』 ■経費精算システム『MFクラウド経費』 ■消込ソフト・システム『MFクラウド消込』 ■マイナンバー対応『MFクラウドマイナンバー』 ■創業支援トータルサービス『MFクラウド創業支援サービス』 ■お金に関する正しい知識やお得な情報を発信するウェブメディア『マネトク!』