2014年9月30日火曜日

ActiveRecordをRails.cacheに入れてみたときの挙動

ActiveRecordでDBから取得した内容をキャッシュしたかったのでRails.cacheに入れてみました。

開発環境ではcacheが効かないようになっているので有効にする。
config/environments/development.rb
 config.action_controller.perform_caching = true

例としてサービスマスタのModelを作成
mst_service.rb
class MstService < ActiveRecord::Base
end

controllerから以下のようなコードでcacheを利用
    puts "cache_#{Rails.cache.read("mst_service").nil?}"
    mst_service = Rails.cache.fetch("mst_service",expires_in: 1.minutes) do
      mst = MstService.select(:service_id).where(:status => '0')
      mst
    end

4回アクセスした時の標準出力は以下のようになる。
2回目以降はキャッシュから取得できているようです。
cache_true
cache_false
cache_false
cache_false

DBへのアクセスはこんな感じ。
アクセス回数分、DBにselectが投げられている。。。
MstService Load (0.3ms)  SELECT `mst_services`.`service_id` FROM `mst_services`  WHERE `mst_services`.`status` = '0'
MstService Load (0.4ms)  SELECT `mst_services`.`service_id` FROM `mst_services`  WHERE `mst_services`.`status` = '0'
MstService Load (0.2ms)  SELECT `mst_services`.`service_id` FROM `mst_services`  WHERE `mst_services`.`status` = '0'
MstService Load (0.2ms)  SELECT `mst_services`.`service_id` FROM `mst_services`  WHERE `mst_services`.`status` = '0'

cacheには入っているようですが、DBへのアクセスが減っておらず、
やりたいことはDBへのアクセスを減らすことなので、意図したキャッシュの処理にはなっていません。
毎回SQLが発行されていることから、ActiveRecordの状態がキャッシュされていて、
データがキャッシュされる訳ではなさそうです。

DBから取得した内容をhashに入れてからキャッシュするように変えてみました。
    puts "cache_#{Rails.cache.read("mst_service").nil?}"
    mst_service = Rails.cache.fetch("mst_service",expires_in: 1.minutes) do
      mst_data = MstService.select(:service_id).where(:status => '0')
      mst = {}
      mst_data.each do |data|
        mst.store(data.service_id,data.service_id)
      end
      mst
    end

4回アクセスした時の標準出力は以下のようになりました。
上のと内容は同じです。
cache_true
cache_false
cache_false
cache_false

DBへのアクセスを確認したところ、以下のように1回だけに減っていました。
MstService Load (0.2ms)  SELECT `mst_services`.`service_id` FROM `mst_services`  WHERE `mst_services`.`status` = '0'

DBから取得した値をRails.cacheに入れるのであれば、ActiveRecordをそのまま入れるのではなく
hashに移し替えてから格納するのがよさそうですね。

2014年9月22日月曜日

rspecとfactory_girlのテストでトランザクション効かせる

railsのmodelのテストをrspecで作成していて、factory_girlで作ったデータでテストをした際に、
テストが完了したらテスト用データも削除されるようにしたいので、そのときにやった対応です。

テスト対象テーブルはこんな感じです。
mysql> select * from seq_sometest4s;
+----+--------+
| id | seq_id |
+----+--------+
|  1 |      1 |
+----+--------+
1 row in set (0.00 sec)

テストしたいクラスです。
$ cat app/models/concerns/seq_table.rb 
module SeqTable
 
  def seq_incrment(klazz)
    base = Object.const_get(klazz)
    base.transaction do
    tbl = base.lock.find(1)
 
    tbl.increment!(:seq_id)
    tbl.seq_id
  end
  rescue => e
    raise e
  end
end
 
$ cat app/models/seq_sometest4.rb 
class SeqSometest4 < ActiveRecord::Base
end

$controller抜粋
seq_id = seq_incrment("SeqSometest4")

spec/rails_helper.rbに設定
config.use_transactional_fixtures = true

まずテストを書いてみました。
require 'rails_helper'
 
describe "SeqTable" do
  include SeqTable
 
  seq = FactoryGirl.create(:seq_sometest4)
  context "#seq_incrment" do
    it {
      res = seq_incrment("SeqSometest4")
      expect(res).to eq (seq.seq_id + 1)
    }
  end
end

これだと、テスト終了後にデータが残ってしまいました。
mysql> select * from seq_sometest4s;
+----+--------+
| id | seq_id |
+----+--------+
|  1 |      1 |
+----+--------+
1 row in set (0.00 sec)

FactoryGirlの処理をit{}の中に持ってきてみました。
require 'rails_helper'
 
describe "SeqTable" do
  include SeqTable
 
  context "#seq_incrment" do
    it {
      seq = FactoryGirl.create(:seq_sometest4)
      res = seq_incrment("SeqSometest4")
      expect(res).to eq (seq.seq_id + 1)
    }
  end
end

mysql> select * from seq_sometest4s;
Empty set (0.00 sec)

itの中に書くことでテスト内での操作ってことになって、トランザクション内での処理ってことになるのかなと。


参考URL
https://relishapp.com/rspec/rspec-rails/docs/transactions