I have the following code to represent different Value Objects in Ruby. The only thing that changes between different classes is the INITIALIZATION_ATTRIBUTES array, which represents the list of attributes of the value object. I can't find a way to DRY this code. I tried to use a Module and accessing the included classes' Constants, but I run into the weird Constant lookup behavior described here . Essentially, the Module code is evaluated multiple times and it interprets the constant of the lastly evaluated class and applies its values to all the Value Object classes.
Is there any better alternative? I also tried with a base class, but I couldn't make it work.
module Values
class MaintenanceRegimeSerializer
INITIALIZATION_ATTRIBUTES = [:distance_between_services, :months_between_services]
def self.load(json)
json ||= '{}'
hash = JSON.parse json, symbolize_names: true
self.new(*INITIALIZATION_ATTRIBUTES.map {|key| hash[key]})
end
def self.dump(obj)
unless obj.is_a?(self)
raise ::ActiveRecord::SerializationTypeMismatch,
"Attribute was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
end
obj.to_json
end
attr_reader *INITIALIZATION_ATTRIBUTES
define_method :initialize do |*args|
raise ArgumentError unless INITIALIZATION_ATTRIBUTES.length == args.length
INITIALIZATION_ATTRIBUTES.each_with_index do |attribute, index|
instance_variable_set "@#{attribute}", args[index]
end
end
end
end
This can be done by layering two modules. The outer module will provide the functionality to initialize the inner module. Because class attributes are used, which are unique for every including class, one including class' attributes can not conflict with another including class' attributes.
module Values
module MaintenanceRegimeSerializer
extend ActiveSupport::Concern
class_methods do
def acts_as_maintenance_regime_serializer(attributes)
# include the inner module
# thereby adding the required methods and class attributes
include JsonMethods
# set the class variables made available by including the inner module
self.serializer_attributes = attributes
end
end
module JsonMethods
extend ActiveSupport::Concern
included do
class_attribute :serializer_attributes
def initialize(*args)
raise ArgumentError unless self.class.serializer_attributes.length == args.length
self.class.serializer_attributes.each_with_index do |attribute, index|
instance_variable_set "@#{attribute}", args[index]
end
end
end
class_methods do
def load(json)
json ||= '{}'
hash = JSON.parse json, symbolize_names: true
new(*serializer_attributes.map {|key| hash[key]})
end
def dump(obj)
unless obj.is_a?(self)
raise ::ActiveRecord::SerializationTypeMismatch,
"Attribute was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
end
obj.to_json
end
end
end
end
end
# in the including class
class SomeClass
# This might also be put into an initializer patching ActiveRecord::Base
# to avoid having to call this in every class desiring the regime serializer functionalit
include Values::MaintenanceRegimeSerializer
acts_as_maintenance_regime_serializer([:distance_between_services,
:months_between_services])
end
# in another including class
class SomeOtherClass
include Values::MaintenanceRegimeSerializer
acts_as_maintenance_regime_serializer([:foo,
:bar])
end
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.