環境情報
- Rails 5.2.6
やりたいこと
ActiveRecord側ではintegerの配列で扱いたいが、DBにはカンマ区切りの文字列として保存したい
やりかた
fooというテーブルのfoo_idsをintegerの配列として利用したい
テーブル作成
create_table :foos do |t|
t.string :foo_ids
end
Coderクラス作成
activemodelのattributeの仕組み上、頻繁にdump,loadが呼び出されるので、バリデーションなども行う場合は要注意
class IntegerArray
def self.dump(object)
return "" if object.blank?
object.join(",")
end
def self.load(string)
return [] if string.blank?
string.split(",").map(&:to_i)
end
end
モデル作成
class Foo < ApplicationRecord
serialize :foo_ids, IntegerArray
end
確認
[10] pry(main)> foo = Foo.new
[12] pry(main)> foo.foo_ids = [1, 2]
=> [1, 2]
[15] pry(main)> foo.save!
(3.2ms) BEGIN
Foo Create (2.7ms) INSERT INTO `foos` (`foo_ids`) VALUES ('1,2')
(7.3ms) COMMIT
=> true
[16] pry(main)> Foo.first
Foo Load (2.8ms) SELECT `foos`.* FROM `foos` ORDER BY `foos`.`id` ASC LIMIT 1
=> #<Foo:0x00007fbfb7be5ae0 id: 3, foo_ids: [1, 33]>
serializeつかっていいの?
基本的に避けたほうが良いのは同意できるが、パフォーマンスなどの都合でどうしても正規化できない状況もあるのでトレードオフを考慮して使うべきだと思いました。(storeはまだ使い所がわかりません)
また、具体的にどの処理が重いのか検証した記事が少なく、調べてみたいと思います。to be continued...
他の方法
- 独自のActiveRecord::Type::Serializedを定義して、serialize毎再実装する(大変だが少しだけ最適化できる)
- モデル側にintegerの配列として扱えるメソッド群を用意する(うまくmodule化すれば一番安定?)
- composed_ofでValueObjectのように扱う