ActiveRecord module ModelSchema def load_schema! @columns_hash = connection.schema_cache .columns_hash(table_name) .except(*ignored_columns) @columns_hash.each do |name, column| define_attribute( name, connection.lookup_cast_type_from_column(column) ) end end end end
"-> Ruby def deserialize(value) cast(value) end # User "-> Ruby def cast(value) cast_value(value) unless value.nil? end # Ruby "-> DB def serialize(value) value end end Что внутри?
наследник в ActiveRecord используется AdapterSpecificRegistry умеет находить класс типа по имени ActiveRecord"::Type.registry.lookup(:integer) # "=> #<ActiveModel"::Type"::Integer: …>
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 * 70) else super end end end
:money, MoneyType ) class User < ApplicationRecord attribute :balance, :money end User.new(balance: '$1').balance # "=> 70 class User < ApplicationRecord attribute :balance, MoneyType.new end User.new(balance: '$1').balance # "=> 70 через реестр напрямую
при чтении ⚠ class IntegerRangeType < ActiveRecord"::Type"::Integer def initialize(range) @range = range end def cast(value) raise ArgumentError unless range.cover?(value) super end end class User include ActiveModel"::Attributes attribute :age, IntegerRangeType.new(0"..100) end user = User.new user.age = 120 puts user.age # "=> ArgumentError
< ActiveRecord"::Type"::Value … class Value attr_reader :settings def initialize(settings) @settings = settings end def subscribe(subscription) @settings "|= (1 "<< subscription) end def receives_email? @settings & (1 "<< EMAIL) > 0 end end end
class StringDateType < ActiveRecord"::Type"::Value LEGACY_FORMAT = "%Y-%m-%d %H:%M" def serialize(value) value.strftime(LEGACY_FORMAT) end def deserialize(value) Time.strptime(value, LEGACY_FORMAT) end end
class PostMetaType < ActiveRecord"::Type"::Value class Meta include ActiveModel"::Model include ActiveModel"::Attributes attribute :published_at, :date end def serialize(value) ActiveSupport"::JSON.encode(value.attributes) end def cast(value) decoded = value.is_a?(String) ? ActiveSupport"::JSON.decode(value) : value Meta.new(decoded) end end
class Meta include ActiveModel"::Model include ActiveModel"::Attributes attribute :published_at, :date end def serialize(value) ActiveSupport"::JSON.encode(value.attributes) end def cast(value) decoded = value.is_a?(String) ? ActiveSupport"::JSON.decode(value) : value Meta.new(decoded) end end
= ♥ class PostMetaType < ActiveRecord"::Type"::Value Meta = Struct.new(:published_at) do def initialize(published_at=nil) self.published_at = if published_at.nil? "|| published_at.is_a?(Date) published_at else Date.parse(published_at) end end end def serialize(value) ActiveSupport"::JSON.encode(value) end def cast(value) decoded = (value.is_a?(String) ? ActiveSupport"::JSON.decode(value) : value).symbolize_keys Meta.new(*Meta.members.map { |name| decoded[name] }) end end
JSON похожи class PostMetaType < ActiveRecord"::Type"::Value class Meta include ActiveModel"::Model include ActiveModel"::Attributes attribute :published_at, :date end def serialize(value) ActiveSupport"::JSON.encode(value.attributes) end def cast(value) decoded = value.is_a?(String) ? ActiveSupport"::JSON.decode(value) : value Meta.new(decoded) end end class UserSyncType < ActiveRecord"::Type"::Value class Sync include ActiveModel"::Model include ActiveModel"::Attributes attribute :last_sync_at, :datetime attribute :success, :boolean end def serialize(value) ActiveSupport"::JSON.encode(value.attributes) end def cast(value) decoded = value.is_a?(String) ? ActiveSupport"::JSON.decode(value) : value Sync.new(decoded) end end
StoreModel module Model def self.included(base) base.include ActiveModel"::Model base.include ActiveModel"::Attributes base.include StoreModel"::NestedAttributes base.extend StoreModel"::Enum base.extend StoreModel"::TypeBuilders end end end
< ActiveModel"::Type"::Value def cast_value(value) case value when String decoded = ActiveSupport"::JSON.decode(value) rescue nil @model_klass.new(decoded) unless decoded.nil? when Hash @model_klass.new(value) when @model_klass, nil value else raise_cast_error(value) end end … end https:"//github.com/DmitryTsepelev/store_model
< ActiveModel"::Type"::Value … def serialize(value) case value when Hash, @model_klass then ActiveSupport"::JSON.encode(value) else super end end end https:"//github.com/DmitryTsepelev/store_model