Created
May 23, 2021 14:04
-
-
Save alpaca-tc/7d4cc782d37c0dc9421c3038f1a40253 to your computer and use it in GitHub Desktop.
軽量なActiveModel::Attributes相当のもの。どうしても大量のオブジェクトを扱いたいときに使う。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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