ラベル ActiveRecord の投稿を表示しています。 すべての投稿を表示
ラベル ActiveRecord の投稿を表示しています。 すべての投稿を表示

2017年5月22日月曜日

rails4.1.6からrails5.0.3にアップデートでActiveSupport::TimeWithZoneにはまった

rails4.1.6からrails5.0.3にアップデートをした際にrspecのテストが落ちて、
それを解決するのに調べました。

環境
ruby2.3.1
rails4.1.6からrails5.0.3

rspecはこんな感じ
test_time = Time.now

user.test_at = test_time
user.save!

ごにょごにょ処理

test_atが変わらないよねっていうテスト
expect(user.test_at).to eq(test_time)

4.1.6では通っていたけど5.0.3で落ちたテスト結果
expected: 2017-05-16 16:26:54.647989000 +0900
          got: 2017-05-16 16:26:54.000000000 +0900

test_timeはTimeクラス、user.test_atはActiveSupport::TimeWithZone。
rails4.1.6のときはsave!のタイミングではTimeWithZoneのミリ秒はまるめられてなかったみたい。
4.1.6でもexpectの前にreloadすると、ミリ秒はまるめられたものに変わった。
rails5ではsave!のタイミングでミリ秒をまるめたものを保持するように変わったげ。

2017年1月31日火曜日

railsのActiveRecordでSystemStackErrorが出た件

何気なくmodel作ってenum書いてたらSystemStackError出たので調べました。
railsのバージョンは4.1.6でした。

こんな感じのmodelを作っていました。
class Sample < ActiveRecord::Base
  enum target: {
    all_user: 0,
    parent: 1,
  }
end

そして動かしてみようとすると、エラーが出ました。
irb(main):001:0* aaa = Sample.new
SystemStackError: stack level too deep
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping.rb:68:in `value_for'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activesupport-4.1.6/lib/active_support/per_thread_registry.rb:47:in `public_send'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activesupport-4.1.6/lib/active_support/per_thread_registry.rb:47:in `block in method_missing'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping.rb:14:in `current_scope'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping/named.rb:25:in `all'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping/named.rb:151:in `block in scope'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/model_schema.rb:335:in `compute_table_name'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/model_schema.rb:148:in `reset_table_name'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/model_schema.rb:109:in `table_name'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/core.rb:151:in `arel_table'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/core.rb:167:in `relation'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping/named.rb:33:in `default_scoped'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping/named.rb:28:in `all'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/scoping/named.rb:151:in `block in scope'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/model_schema.rb:335:in `compute_table_name'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activerecord-4.1.6/lib/active_record/model_schema.rb:148:in `reset_table_name'
... 10760 levels...
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activesupport-4.1.6/lib/active_support/dependencies.rb:241:in `load'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activesupport-4.1.6/lib/active_support/dependencies.rb:241:in `block in load'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activesupport-4.1.6/lib/active_support/dependencies.rb:232:in `load_dependency'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/activesupport-4.1.6/lib/active_support/dependencies.rb:241:in `load'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/commands/rails.rb:6:in `call'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/command_wrapper.rb:38:in `call'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application.rb:191:in `block in serve'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application.rb:161:in `fork'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application.rb:161:in `serve'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application.rb:131:in `block in run'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application.rb:125:in `loop'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application.rb:125:in `run'
 from /Users/xxx/Documents/github/test/vendor/bundle/ruby/2.3.0/gems/spring-1.7.2/lib/spring/application/boot.rb:19:in `'
 from /Users/xxx/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
 from /Users/xxx/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
 from -e:1:in `
'



調べてみると、enumには使ってはいけない単語を使ってるけど、
それをチェックしてないから起こっているエラーのようです。
すでに修正されていて、rails4.2からはSystemStackErrorではなく、
conflictしてるとわかるエラーが出るようです


参考URL
https://github.com/rails/rails/issues/16347
https://github.com/rails/rails/commit/94b7328b08fcc55e82bfcaa34a25ae718921ae1c

2015年6月8日月曜日

ActiveRecordにattr_accessorで定義したものがto_jsonで含まれない

何も考えずにActiveRecordでto_jsonやっていたら、
attr_accessorで定義したものがJSONに含まれてなかったので、
そのときに調べたことを書いていきます。

クラスはこんな感じ
# == Schema Information
#
# Table name: items
#
#  id         :integer          not null, primary key
#  name       :string
#  img        :string
#  price      :integer
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
 
class Item < ActiveRecord::Base
  attr_accessor :view_img_url
end

item = Item.new
item.name = 'なまえ'
item.img = 'test.jpg'
item.view_img_url = 'http://aaa.bbb/ccc/ddd/test.jpg'
item.price = 100
 
p item.to_json
JSONにview_img_urlが含まれていませんでした。
view_img_urlを含むためにはto_jsonの引数にmethods: :view_img_urlを与える必要があります。
p item.to_json(methods: :view_img_url)
これでJSONにview_img_urlが含まれるようになりました。

2015年3月9日月曜日

railsでfind_each使った時にはまった件

railsでバッチ処理を書いててfind_eachを使おうとしたらエラーではまった時にやったことです。

まず最初はこんな感じのテーブルにしていました。
mysql> desc mail_send_targets;
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| user_id     | bigint(20) | NO   | MUL | NULL    |       |
| item_id     | bigint(20) | NO   | MUL | NULL    |       |
| expire_date | datetime   | NO   |     | NULL    |       |
+-------------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

このテーブルに対して、こういうコードを書いたところ、実行したらエラーがでました。
targets = MailSendTarget.where(item_id: item_id)
targets.find_each(batch_size: 2000) do |target|
  #処理
end

ERROR -- : Mysql2::Error: Unknown column 'mail_send_targets.' in 'order clause': SELECT  `mail_send_targets`.* FROM `mail_send_targets`  WHERE `mail_send_targets`.`item_id` = 96  ORDER BY `tmp_mail_send_targets`.`` ASC LIMIT 2000 (ActiveRecord::StatementInvalid)

エラーの内容を見ていくと、ORDER BYのカラムが指定されていなくて、
どうやらprimary keyをorder byに指定するようなので、
テーブルにprimary keyを設定する必要がありそうです。
mysql> desc mail_send_targets;
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| id      | bigint(20) | NO   | PRI | 0       |       |
| user_id     | bigint(20) | NO   | MUL | NULL    |       |
| item_id     | bigint(20) | NO   | MUL | NULL    |       |
| expire_date | datetime   | NO   |     | NULL    |       |
+-------------+------------+------+-----+---------+-------+
4 rows in set (0.01 sec)

プライマリーキーを設定したところ、エラーが起こらずに処理が流れるようになりました。

2014年10月27日月曜日

ActiveRecordのvalidate条件に変数を使う

ActiveRecordのvalidateはよく使うのですが、
必ずチェックを行いたいとは限らないので、validate条件を設定したりするときに、
その比較条件に変数を使いたかったので調べました。

まずは普通のvalidate
validates :shop_id,
    presence: true

validate条件を使ったとき
validates :shop_id,
    presence: true, if: "shop_flg == '1'"

マジックナンバー使うのは嫌なので変数(定数)に置き換えてみる
SHOP_FLG = '1'

validates :shop_id,
    presence: true, if: "shop_flg == #{SHOP_FLG}"

判定を文字列にするので、普通に#{}で変数を囲めば実現できるようです。


参考URL
http://www.techscore.com/tech/Ruby/Rails/model/validation/4

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月3日水曜日

rails4のActiveRecordでselect for updateを実行

mysqlでselect for updateを使ったトランザクション処理をしたかったので調べました。

まずはrailsのバージョン
$ bundle exec rails -v
Rails 4.1.4

mysqlのテーブル
mysql> desc seq_sometest3s;
+--------+------------+------+-----+---------+-------+
| Field  | Type       | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| id     | int(10)    | NO   | PRI | 0       |       |
| seq_id | bigint(10) | YES  |     | NULL    |       |
+--------+------------+------+-----+---------+-------+
2 rows in set (0.01 sec)
 
mysql> select * from seq_sometest3s;
+----+--------+
| id | seq_id |
+----+--------+
|  1 | 100049 |
+----+--------+
1 row in set (0.00 sec)

まずネットで調べた以下のやり方だとエラーになりました。
tbl = base.find(1, lock: true)
tbl.increment!(:seq_id)
tbl.seq_id
 
   (0.2ms)  BEGIN
  SeqSometest3 Load (0.7ms)  SELECT `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` IN (1, '---\n:lock: true\n')
   (0.2ms)  ROLLBACK

次に出てきた以下のやり方だとエラーは出ませんでした。
ただ、プライマリーキーで指定しているのでorder byが不要です、
tbl = base.where("id =1").lock(true).first
tbl.increment!(:seq_id)
tbl.seq_id
 
   (0.1ms)  BEGIN
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE (id =1)  ORDER BY `seq_sometest3s`.`id` ASC LIMIT 1 FOR UPDATE
  SQL (0.2ms)  UPDATE `seq_sometest3s` SET `seq_id` = 100046 WHERE `seq_sometest3s`.`id` = 1
   (0.4ms)  COMMIT

次のやり方でもエラーは出ませんでしたが、
order byはつかなくなったけど2回selectが実行されてしまいます。
後半のSQLだけでよいのに。
tbl = base.find(1).lock!
tbl.increment!(:seq_id)
tbl.seq_id

   (0.1ms)  BEGIN
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 1 LIMIT 1
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 1 LIMIT 1 FOR UPDATE
  SQL (0.2ms)  UPDATE `seq_sometest3s` SET `seq_id` = 100047 WHERE `seq_sometest3s`.`id` = 1
   (0.6ms)  COMMIT

このやり方だとselectも1回しか発行されず、order byもつかないSQLが発行できました。
tbl = base.lock.find(1)
tbl.increment!(:seq_id)
tbl.seq_id
 
   (0.1ms)  BEGIN
  SeqSometest3 Load (0.4ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 1 LIMIT 1 FOR UPDATE
  SQL (0.3ms)  UPDATE `seq_sometest3s` SET `seq_id` = 100049 WHERE `seq_sometest3s`.`id` = 1
   (0.3ms)  COMMIT

ちなみに存在しないレコードにロックをかけようとすると、
RecordNotFoundのエラーが出ました。
tbl = base.lock.find(2)
tbl.increment!(:seq_id)
tbl.seq_id

   (0.2ms)  BEGIN
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 2 LIMIT 1 FOR UPDATE
   (0.1ms)  ROLLBACK
ActiveRecord::RecordNotFound (Couldn't find SeqSometest3 with 'id'=2):


参考URL
http://apidock.com/rails/ActiveRecord/Locking/Pessimistic
http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

2014年9月1日月曜日

rails4のActiveRecordで特定のカラムだけ取得する

ActiveRecordで普通にfindしたりすると、すべてのカラムが取得できますが、
DBの負荷を考えると必要なカラムだけ取得したいので、そのやり方を調べました。

まずはrailsのバージョンを確認
$ bundle exec rails -v
Rails 4.1.4

DBの中身はこんな感じです。
mysql> select * from sometest3s where id=2;
+----+------+--------------------------------------------------------------------------------------------+----------+---------------------+
| id | name | content                                                                                    | view_flg | ins_date            |
+----+------+--------------------------------------------------------------------------------------------+----------+---------------------+
|  2 | john | {"shop_id"=>[100, 200, 300], "item_id"=>[500, 600, 700, 800], "name"=>"jon", "user"=>2000} | 0        | 2014-08-20 02:17:17 |
+----+------+--------------------------------------------------------------------------------------------+----------+---------------------+
1 row in set (0.00 sec)

取得の仕方はこうで、pluckを使うと結果は配列に入ります。
mapss = Sometest3.select(:id,:name).where(:id => 2).pluck(:id,:name)
puts mapss # => 2 john
puts mapss[0] # => 2 john
puts mapss[0][0] # => 2
puts mapss[0][1] # => john 

その際に発行されるSQLがこんな感じです。
   (0.3ms)  SELECT `sometest3s`.`id`, `sometest3s`.`name` FROM `sometest3s`  WHERE `sometest3s`.`id` = 2

ちなみにwhereとselectを入れ替えてみても発行されるSQLは同じでした。
mapss = Sometest3.where(:id => 2).select(:id,:name).pluck(:id,:name)

   (0.3ms)  SELECT `sometest3s`.`id`, `sometest3s`.`name` FROM `sometest3s`  WHERE `sometest3s`.`id` = 2


参考URL
http://6rats.blog62.fc2.com/blog-entry-75.html
http://d.hatena.ne.jp/suginoy/20120605/p3

2014年8月29日金曜日

rails4のActiveRecordでid以外をプライマリーキーに設定する

rails使ってる場合、DBのテーブルのプライマリーキーはidにしといた方が楽なのですが、
他のテーブルとの関連とか考えるとすべてのテーブルでidってのはなかなかわかりづらいです。
なので、ActiveRecordでid以外をプライマリーキーに設定したいです。

まずはrailsのバージョンを確認
$ bundle exec rails -v
Rails 4.1.4

DBのテーブルはこんな感じ
create table sometest4s(
  sometest4_id bigint(10),
  sometest3_id bigint(10),
  val varchar(256),
  ins_date datetime default '9999-12-31 23:59:59',
  primary key (sometest4_id)
);

modelでのプライマリーキーはこんな感じで設定します。
コメントアウトしてるのはrails3のときの設定方法のようです。
$ cat app/models/sometest4.rb 
class Sometest4 < ActiveRecord::Base
#  set_primary_key :sometest4_id
   self.primary_key = :sometest4_id
end

参考URL
http://qiita.com/k-shogo/items/884498ad512c0e6eb303
http://www.atmarkit.co.jp/ait/articles/1104/12/news135.html