やりたいこと
Railsで
class Report < ActiveRecord::Base
serialize :query, JSON
end
とかすると(この例では) query という属性を JSON にシリアライズして1カラムに保存してくれる。この JSON と書いている所を値オブジェクトにして、validation 付けたり、メソッドを持たせるなどして色々便利に扱いたい。
やり方
場所はどこでもいいのだが、自分は app/models/values/query.rb
においてる。
こんなかんじのコードを書く。dump
メソッドおよび load
メソッドを独自定義する。
Query = Struct.new(:statement, :bind)
class Query
class << self
# To support `serialize :query, ::Query`
# @param [Hash or ActionController::Parameters or Query or Object] obj
def dump(obj)
obj.to_json if obj
end
# To support `serialize :query, ::Query`
# @param [String] source JSON text
def load(source)
self.new(JSON.parse(source)) if source
end
end
# @param [Hash] params
# @option params [String] statement
# @option params [Hash] bind
def initialize(params = {})
params = params.symbolize_keys
self.statement = params[:statement]
self.bind = params[:bind]
end
end
これで以下のように指定できるようになって、
class Report < ActiveRecord::Base
serialize :query, ::Query
end
report.query
とすると Query オブジェクトが返ってくるようになる。
report = Report.new
report.query.class #=> Query
テクニカルポイント
1回ハマったのでメモ。上のコードでは解消してあるので気にしなくても良い。当初、dump
の実装を obj.to_json
ではなく obj.to_h.to_json
にしていた。しかし、これではうまくいかなかったという話。
def dump(obj)
- obj.to_h.to_json if obj
+ obj.to_json if obj
end
Controller で
Report.create(params[:query])
のようにした場合、Query.dump(obj)
の obj
には params[:query]
つまり、ActionController::Parameters
のオブジェクトが入って来る。そして、obj.to_h.to_json
が実行される。
ActionController::Parameters#to_h は permit していない key を全て削除してしまうので、strong parameter を使って permit しておかないと、
class ReportsController < ApplicationController
def create
Report.create(report_params)
end
private
def report_params
params.permit(query: [:statement, :bind])
end
end
obj.to_h.to_json
が {}
になってしまう。
Strong Parameter を使わずに、直接 Report.create(params[:query])
のように呼んだ場合に {}
になってハマってしまうので、ハマりポイントを避けるために obj.to_json
に変更した。