lib/active_interactor/context/attributes.rb
# frozen_string_literal: true
module ActiveInteractor
module Context
# Context attribute methods. Because {Attributes} is a module classes should include {Attributes} rather than
# inherit from it.
#
# @author Aaron Allen <hello@aaronmallen.me>
# @since 0.1.4
module Attributes
# Context attribute class methods. Because {ClassMethods} is a module classes should extend {ClassMethods} rather
# than inherit from it.
#
# @author Aaron Allen <hello@aaronmallen.me>
# @since 0.1.4
module ClassMethods
# Get or set attributes on a {Base context} class
#
# @example Set attributes on a {Base context} class
# class MyContext < ActiveInteractor::Context::Base
# attributes :first_name, :last_name
# end
#
# @example Get attributes defined on a {Base context} class
# MyContext.attributes
# #=> [:first_name, :last_name]
#
# @example Set defaults for attributes on a {Base context} class
# class MyContext < ActiveInteractor::Context::Base
# attributes first_name: { default: -> { 'Aaron' } }, last_name: { default: -> { 'Allen' } }
# end
#
# @return [Array<Symbol>] the defined attributes
def attributes(*attributes)
attributes.compact.uniq.each { |attr| attribute(attr) }
attribute_names.sort.collect(&:to_sym)
end
private
def attribute?(attr_name)
attribute_types.key?(attr_name.to_s)
end
alias has_attribute? attribute?
end
# Initialize a new instance of {Base}
#
# @param context [Hash, Base, Class] attributes to assign to the {Base context}
# @return [Base] a new instance of {Base}
def initialize(context = {})
merge_errors!(context) if context.respond_to?(:errors)
copy_flags!(context)
copy_called!(context)
context = context_attributes_as_hash(context) || {}
super
merge_attribute_values(context)
end
# Returns the value of an attribute
#
# @since 1.0.5
#
# @param name [String, Symbol] the key of the value to be returned
# @returns [*] the attribute value
def [](name)
@table[name.to_sym] || attributes[name.to_sym]
end
# Sets value of a Hash attribute in context.attributes
#
# @since 1.1.0
#
# @param name [String, Symbol] the key name of the attribute
# @param value [*] the value to be given attribute name
# @returns [*] the attribute value
def []=(name, value)
public_send("#{name}=", value)
super unless @table.nil?
end
# Get values defined on the instance of {Base context} whose keys are defined on the {Base context} class'
# {ClassMethods#attributes .attributes}
#
# @example Get attributes defined on an instance of {Base context}
# class MyContext < ActiveInteractor::Context::Base
# attributes :first_name, :last_name
# end
#
# context = MyContext.new(first_name: 'Aaron', last_name: 'Allen', occupation: 'Ruby Nerd')
# #=> <#MyContext first_name='Aaron' last_name='Allen' occupation='Ruby Nerd')
#
# context.attributes
# #=> { first_name: 'Aaron', last_name: 'Allen' }
#
# context.occupation
# #=> 'Ruby Nerd'
#
# @return [Hash{Symbol => *}] the defined attributes and values
def attributes
super.symbolize_keys
end
# Check if the {Base context} instance has an attribute
#
# @since 1.0.1
#
# @param attr_name [Symbol, String] the name of the attribute to check
# @return [Boolean] whether or not the {Base context} instance has the attribute
def attribute?(attr_name)
@attributes.key?(attr_name.to_s)
end
alias has_attribute? attribute?
# Merge an instance of {Base context} into the calling {Base context} instance
#
# @since 1.0.0
#
# @example
# context = MyContext.new(first_name: 'Aaron', last_name: 'Allen')
# other_context = MyContext.new(last_name: 'Awesome')
# context.merge!(other_context)
# #=> <#MyContext first_name='Aaron' last_name='Awesome'>
#
# @param context [Class] a {Base context} instance to be merged
# @return [self] the {Base context} instance
def merge!(context)
merge_errors!(context) if context.respond_to?(:errors)
copy_flags!(context)
merged_context_attributes(context).each_pair do |key, value|
self[key] = value unless value.nil?
end
self
end
private
def _called
@_called ||= []
end
def merged_context_attributes(context)
attrs = {}
attrs.merge!(context.to_h) if context.respond_to?(:to_h)
attrs.merge!(context.attributes.to_h) if context.respond_to?(:attributes)
attrs
end
def context_attributes_as_hash(context)
return context.to_h if context&.respond_to?(:to_h)
return context.attributes.to_h if context.respond_to?(:attributes)
end
def copy_called!(context)
value = context.instance_variable_get('@_called') || []
instance_variable_set('@_called', value)
end
def copy_flags!(context)
%w[_failed _rolled_back].each do |flag|
value = context.instance_variable_get("@#{flag}")
instance_variable_set("@#{flag}", value)
end
end
def merge_attribute_values(context)
return unless context
attributes.compact.merge(context).each_pair do |key, value|
self[key] = value
end
end
end
end
end