Active Record::Type::Valueを継承した独自タイプの作成


Rails 5を少し触っていますが、「Active Record::Type::Value」等を継承した独自クラスを設定する手順で少しハマったのでメモ。基本的には継承元のクラスを見ればなんとなく分かるはず。


🐞 やりたいこと

こんな感じでActive Recordのattributeに独自クラスにキャストして使いたいっす。

# config/initializers/active_record/money_type.rb
class MoneyType < ActiveRecord::Type::Integer
def cast(value)
if !value.kind_of?(Numeric) && value.include?('$')
price_in_dollars = value.gsub(/\$/, '').to_f
super(price_in_dollars * 100)
else
super
end
end
end
ActiveRecord::Type.register(:money, MoneyType)
# /app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
attribute :price_in_cents, :money
end
store_listing = StoreListing.new(price_in_cents: '$10.00')
store_listing.price_in_cents # => 1000

こうすることで共通の処理を一ヵ所にまとめられたり、値がない場合などの処理を一ヵ所に書くことができます。

ドキュメントとしてはこちらが一番参考になりそう。

ActiveRecord::Attributes::ClassMethods

🐝 継承元のクラス

継承元のクラスだけでもこれだけあるので、どれをキャストするか考える。

ActiveModel::Type::BigInteger
ActiveModel::Type::Binary
ActiveModel::Type::Boolean
ActiveModel::Type::Decimal
ActiveModel::Type::DecimalWithoutScale
ActiveModel::Type::Float
ActiveModel::Type::Integer
ActiveModel::Type::String
ActiveModel::Type::Text
ActiveModel::Type::UnsignedInteger
ActiveModel::Type::Value

🐰 Active Model::Type::Valueを継承元に使う

今回は、Active Model::Type::Valueを継承元に使います。

# config/initializers/active_record/money_type.rb
class OriginalJson
def initialize(hash)
@json = hash.try(:symbolize_keys)
end
def hoge
@json['hoge']
end
def to_h
@json
end
# For casting
class Type < ActiveRecord::Type::Value
# 型を書く
def type
end
# DBから値を取り出す時のキャスト処理
def deserialize(value)
cast(value)
end
# ユーザー入力をキャストする処理
def cast(value)
if String === value
decoded = ::ActiveSupport::JSON.decode(value) rescue nil
::OriginalJson.new(decoded)
else
super
end
end
# DBの保存のために、DBが認識できる型に変換
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, +nil+
def serialize(value)
case value
when Hash
::ActiveSupport::JSON.encode(value)
when ::OriginalJson
::ActiveSupport::JSON.encode(value.to_h)
else
super
end
end
end
end
# Adding OriginalJson::Type to ActiveRecord::Type
ActiveRecord::Type.register(:original_json, OriginalJson::Type)

雑な実装ですので、状況に応じて書きなおしてください。

🍣 あとがき

Railsのコード読むのは結構勉強になる。

🏈 参考リンク

📚 おすすめの書籍