lib/shallow_attributes/instance_methods.rb
module ShallowAttributes
# Abstract class for value classes. Provides some helper methods for
# working with attributes.
#
# @abstract
#
# @since 0.1.0
module InstanceMethods
# Lambda object for getting attributes hash for a specific
# value object.
#
# @private
#
# @since 0.1.0
TO_H_PROC = ->(value) { value.respond_to?(:to_hash) ? value.to_hash : value }
# Initialize an instance object with specific attributes
#
# @param [Hash] attrs the attributes contained in the class
#
# @example Create new User instance
# class User
# include ShallowAttributes
# attribute :name, String
# end
#
# User.new(name: 'Anton') # => #<User @attributes={:name=>"Anton"}, @name="Anton">
#
# @return the new instance of value class with specific attributes
#
# @since 0.1.0
def initialize(attrs = {})
@attributes = {}
attrs.each_pair do |key, value|
key = key.to_sym
@attributes[key] = value if default_values.key?(key)
end
define_attributes
define_default_attributes
define_mandatory_attributes
end
# Returns hash of object attributes
#
# @example Returns all user attributes
# class User
# include ShallowAttributes
# attribute :name, String
# end
#
# user = User.new(name: 'Anton')
# user.attributes # => { name: "Anton" }
#
# @return [Hash]
#
# @since 0.1.0
def attributes
hash = {}
@attributes.map do |key, value|
hash[key] =
value.is_a?(Array) ? value.map(&TO_H_PROC) : TO_H_PROC.call(value)
end
hash
end
# @since 0.1.0
alias_method :to_h, :attributes
# @since 0.1.0
alias_method :to_hash, :attributes
# Attribute values mass-assignment
#
# @param [Hash] attributes the attributes which will be assignment
#
# @example Assignment new user name
# class User
# include ShallowAttributes
# attribute :name, String
# end
#
# user = User.new(name: 'Anton')
# user.attributes = { name: "Ben" }
# user.attributes # => { name: "Ben" }
#
# @return [Hash] attributes hash
#
# @since 0.1.0
def attributes=(attributes)
attributes.each_pair do |key, value|
@attributes[key.to_sym] = value
end
define_attributes
end
# Reset specific attribute to default value.
#
# @param [Symbol] attribute the attribute which will be reset
#
# @example Reset name value
# class User
# include ShallowAttributes
# attribute :name, String, default: 'Ben'
# end
#
# user = User.new(name: 'Anton')
# user.reset_attribute(:name)
# user.attributes # => { name: "Ben" }
#
# @return the last attribute value
#
# @since 0.1.0
def reset_attribute(attribute)
instance_variable_set("@#{attribute}", default_value_for(attribute))
end
# Sets new values and returns self. Needs for embedded value.
#
# @private
#
# @param [Hash] value the new attributes for current object
# @param [Hash] _options
#
# @example Use embedded values
# class User
# include ShallowAttributes
# attribute :name, String, default: 'Ben'
# end
#
# class Post
# include ShallowAttributes
# attribute :author, User
# end
#
# post = Post.new(author: { name: 'Anton'} )
# post.user.name # => 'Anton'
#
# @return the object
#
# @since 0.1.0
def coerce(value, _options = {})
self.attributes = value
self
end
# Compare values of two objects
#
# @param [Object] object the other object
#
# @example Compare two value objects
# class User
# include ShallowAttributes
# attribute :name, String, default: 'Ben'
# end
#
# user1 = User.new(name: 'Anton')
# user2 = User.new(name: 'Anton')
# user1 == user2 # => true
#
# @return [boolean]
#
# @since 0.1.0
def ==(object)
self.to_h == object.to_h
end
# Inspect instance object
#
# @example Inspect the object
# class User
# include ShallowAttributes
# attribute :name, String, default: 'Ben'
# end
#
# user = User.new(name: 'Anton')
# user.inspect # => "#<User name=\"Anton\">"
#
# @return [String]
#
# @since 0.1.0
def inspect
"#<#{self.class}#{attributes.map{ |k, v| " #{k}=#{v.inspect}" }.join}>"
end
private
# Define default values for attributes.
#
# @private
#
# @return the object
#
# @since 0.1.0
def define_default_attributes
default_values.each do |key, value|
next unless @attributes[key].nil? && !value.nil?
send("#{key}=", default_value_for(key))
end
end
# Define mandatory attributes for object and raise exception
# if they were not provided
#
# @private
#
# @raise [MissingAttributeError] if attribute was not provided
#
# @return the object
#
# @since 0.10.0
def define_mandatory_attributes
mandatory_attributes.each do |key, value|
next unless @attributes[key].nil? && value
raise ShallowAttributes::MissingAttributeError, %(Mandatory attribute "#{key}" was not provided)
end
end
# Define attributes from `@attributes` instance value.
#
# @private
#
# @return the object
#
# @since 0.1.0
def define_attributes
@attributes.each do |key, value|
send("#{key}=", value)
end
end
# Returns default value for specific attribute. Default values hash
# is taken from class getter `default_values`.
#
# @private
#
# @return [nil] if default value not defined
# @return [Object] if default value is defined
#
# @since 0.1.0
def default_value_for(attribute)
value = default_values[attribute]
case value
when Proc
value.call(self, attribute)
when Symbol, String
respond_to?(value, true) ? send(value) : value
else
value
end
end
# Returns hash of default class values
#
# @private
#
# @return [Hash]
#
# @since 0.1.0
def default_values
@default_values ||= self.class.default_values
end
# Returns hash of mandatory class attributes
#
# @private
#
# @return [Hash]
#
# @since 0.10.0
def mandatory_attributes
@mandatory_attributes ||= self.class.mandatory_attributes
end
end
end