Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
軽量なActiveModel::Attributes相当のもの。どうしても大量のオブジェクトを扱いたいときに使う。
# ActiveModel::Attributesは不要な抽象化が多くてメモリを大量に消費するため、軽量なActiveModel::Attributes相当のmoduleを用意する
# このmoduleは、ActiveModel::Attributesと比べて多くの機能(dirty trackingなど)がないため、readonlyなオブジェクトに利用するとよい
module LightweightActiveModelAttributes
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
included do
class_attribute :_attribute_types, :_default_attributes, instance_accessor: false
self._attribute_types = {}
self._default_attributes = {}
end
class_methods do
# 軽量のActiveModel::Attributesのようなもの
# AttributesSetを使わず、インスタンス変数にデータを格納しているため、生成するオブジェクトが少ない
#
# @param name [Symbol]
# @param type [ActiveModel::Type::Value]
# @param options [Hash]
#
# @return [void]
def attribute(name, type = ActiveModel::Type.default_value, **options)
name = name.to_s
if type.is_a?(Symbol)
type = ActiveModel::Type.lookup(type, **options.except(:default))
end
self._attribute_types = _attribute_types.merge(name => type)
define_default_attribute(name, options.fetch(:default, nil), type)
define_attribute(name)
define_attribute_method(name)
end
private
def generated_attribute_methods
@generated_attribute_methods ||= Module.new.tap { include(_1) }
end
def define_attribute(attr_name)
generated_attribute_methods.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
def #{attr_name} # def id
@attributes.fetch('#{attr_name}') # @attributes.fetch('id')
end # end
def #{attr_name}=(value) # def id=(value)
type = self.class._attribute_types.fetch('#{attr_name}') # type = self.class._attribute_types.fetch('id')
@attributes['#{attr_name}'] = type.cast(value) # @attributes['id'] = type.cast(value)
end # end
RUBY
end
def define_default_attribute(name, value, type)
raise NotImplementedError, 'not supported proc for performance' if value.respond_to?(:call)
self._default_attributes = _default_attributes.merge(name.to_s => type.cast(value))
end
end
attr_reader :attributes
# Initializes a new model with the given +params+.
def initialize(attributes = {})
@attributes = self.class._default_attributes.deep_dup
assign_attributes(attributes) if attributes
super()
end
# #dupを呼び出した時に呼ばれる
# attributesのfreezeを解除してdupを行う
#
# @param other [LightweightActiveModelAttributes]
# @return [LightweightActiveModelAttributes]
def initialize_dup(other)
@attributes = @attributes.deep_dup
super
end
# Hashの値を格納する
#
# @param attributes [Hash]
#
# @return [void]
def assign_attributes(attributes)
attributes.each do |attr_name, value|
public_send(:"#{attr_name}=", value)
end
end
# 誤検知されるので、rubocopをdisableにする
# rubocop:disable Lint/UselessAccessModifier
private
# rubocop:enable Lint/UselessAccessModifier
def attribute(attr_name)
@attributes.fetch(attr_name.to_s)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment