lib/koine/attributes/attributes.rb in koine-attributes-0.1.4 vs lib/koine/attributes/attributes.rb in koine-attributes-0.2.0
- old
+ new
@@ -1,18 +1,121 @@
module Koine
module Attributes
class Attributes
- def initialize(object, attributes:)
+ def initialize(object, adapters:, options: {})
@object = object
- @attributes = attributes
+ @adapters = adapters
+ @values = {}
+ @initializer = { strict: true, freeze: false, initialize: !options[:initializer].nil? }
+
+ if options[:initializer].is_a?(Hash)
+ @initializer = @initializer.merge(options[:initializer])
+ end
end
+ def initialize_values(values = {})
+ if !@initializer[:initialize] && !values.empty?
+ raise ArgumentError, "wrong number of arguments (given #{values.length}, expected 0)"
+ end
+
+ return unless @initializer[:initialize]
+ set_values(values) && @initializer[:freeze] && freeze
+ end
+
+ def set_values(values)
+ invalid_attributes = []
+
+ if @initializer[:strict]
+ attributes = values.keys.map(&:to_sym)
+ invalid_attributes = attributes - valid_attributes
+ end
+
+ unless invalid_attributes.empty?
+ raise ArgumentError, "Invalid attributes (#{invalid_attributes.join(', ')})"
+ end
+
+ values.each do |attribute, value|
+ set(attribute, value) if has_attribute?(attribute)
+ end
+ end
+
+ def set(attribute, value)
+ @values[attribute.to_sym] = adapter_for(attribute).coerce(value)
+ end
+
+ def with_attribute(attribute, value)
+ new_attributes = to_h.merge(attribute => value)
+ @object.class.new(new_attributes)
+ end
+
+ def get(attribute)
+ @values[attribute.to_sym] || adapter_for(attribute).default_value
+ end
+
+ def ==(other)
+ other.to_h == to_h
+ end
+
def to_h
- {}.tap do |hash|
- @attributes.each do |name|
- hash[name.to_sym] = @object.send(name)
- end
+ valid_attributes.map do |name|
+ [name.to_sym, @object.send(name)]
+ end.to_h
+ end
+
+ def respond_to?(method, _include_private = nil)
+ method = method.to_s
+
+ # getter
+ return true if has_attribute?(method)
+
+ # {attribute_name}=value
+ matches = method.match(/^(.*)=$/)
+ return has_attribute?(matches[1]) if matches
+
+ # with_{attribute}(value)
+ matches = method.match(/^with_(.*)$/)
+ return has_attribute?(matches[1]) if matches
+
+ false
+ end
+
+ def method_missing(method_name, *args)
+ unless respond_to?(method_name)
+ raise NoMethodError, "Undefined method #{method_name} for attributed object #{@object}"
end
+
+ method_name = method_name.to_s
+
+ if method_name.to_s =~ /=$/
+ attribute = method_name.to_s.delete('=')
+ return set(attribute, *args)
+ end
+
+ matches = method_name.match(/^with_(.*)$/)
+ return with_attribute(matches[1], *args) if matches
+
+ get(method_name)
+ end
+
+ private
+
+ def valid_attributes
+ @adapters.keys
+ end
+
+ def has_attribute?(attribute)
+ @adapters.key?(attribute.to_sym)
+ end
+
+ def adapter_for(attribute)
+ @adapters.fetch(attribute.to_sym)
+ end
+
+ def freeze
+ @object.freeze
+ @adapters.freeze
+ @values.freeze
+ super
end
end
end
end