trabajo-afectivo/lib/sequencer/unit/base.rb

213 lines
7.6 KiB
Ruby
Raw Normal View History

require 'mixin/rails_logger'
class Sequencer
class Unit
class Base
include ::Mixin::RailsLogger
attr_reader :state
# Creates the class macro `uses` that allows a Unit to
# declare the attributes it will use via parameter or block.
# On the other hand it returns the declared attributes if
# called without parameters.
#
# This method can be called multiple times and will add the
# given attributes to the list. It takes care of handling
# duplicates so no uniq check is required. It's safe to use
# for inheritance structures and modules.
#
# It additionally creates a getter instance method for each declared
# attribute like e.g. attr_reader does. This allows direct access
# to an attribute via `attribute_name`. See examples.
#
# @param [Array<Symbol>] attributes an optional list of attributes that the Unit uses
#
# @yield [] A block returning a list of attributes
#
# @example Via regular Array<Symbol> parameter
# uses :instance, :action, :connection
#
# @example Via block
# uses do
# additional = method(parameter)
# [:some, additional]
# end
#
# @example Listing declared attributes
# Unit::Name.uses
# # => [:instance, :action, :connection, :some, :suprise]
#
# @example Using declared attribute in the Unit via state object
# state.use(:instance).id
#
# @example Using declared attribute in the Unit via getter
# instance.id
#
# @return [Array<Symbol>] the list of all declared uses of a Unit.
def self.uses(*attributes, &block)
declaration_accessor(
key: __method__,
attributes: attributes(*attributes, &block)
) do |attribute|
use_getter(attribute)
end
end
# Creates the class macro `provides` that allows a Unit to
# declare the attributes it will provided via parameter or block.
# On the other hand it returns the declared attributes if
# called without parameters.
#
# This method can be called multiple times and will add the
# given attributes to the list. It takes care of handling
# duplicates so no uniq check is required. It's safe to use
# for inheritance structures and modules.
#
# It additionally creates a setter instance method for each declared
# attribute like e.g. attr_writer does. This allows direct access
# to an attribute via `self.attribute_name = `. See examples.
#
# A Unit should usually not provide more than one or two attributes.
# If your Unit provides it's doing to much and should be splitted
# into multiple Units.
#
# @param [Array<Symbol>] attributes an optional list of attributes that the Unit provides
#
# @yield [] A block returning a list of attributes
#
# @example Via regular Array<Symbol> parameter
# provides :instance, :action, :connection
#
# @example Via block
# provides do
# additional = method(parameter)
# [:some, additional]
# end
#
# @example Listing declared attributes
# Unit::Name.provides
# # => [:instance, :action, :connection, :some, :suprise]
#
# @example Providing declared attribute in the Unit via state object parameter
# state.provide(:action, :created)
#
# @example Providing declared attribute in the Unit via state object block
# state.provide(:instance) do
# # ...
# instance
# end
#
# @example Providing declared attribute in the Unit via setter
# self.action = :created
#
# @return [Array<Symbol>] the list of all declared provides of a Unit.
def self.provides(*attributes, &block)
declaration_accessor(
key: __method__,
attributes: attributes(*attributes, &block)
) do |attribute|
provide_setter(attribute)
end
end
def self.attributes(*attributes)
# exectute block if given and add
# the result to the (possibly empty)
# list of given attributes
attributes.concat(yield) if block_given?
attributes
end
# This method is the heart of the #uses and #provides method.
# It takes the declaration key and decides based on the given
# parameters if the given attributes should get stored or
# the stored values returned.
def self.declaration_accessor(key:, attributes:)
# if no attributes were given (storing)
# return the already stored list of attributes
return declarations(key).to_a if attributes.blank?
# loop over all given attributes and
# add them to the list of already stored
# attributes for the given declaration key
attributes.each do |attribute|
next if !declarations(key).add?(attribute)
# yield callback if given to create
# getter or setter or whatever
yield(attribute) if block_given?
end
end
# This method creates the convenience method
# getter for the given attribute.
def self.use_getter(attribute)
define_method(attribute) do
instance_variable_cached(attribute) do
state.use(attribute)
end
end
end
# This method creates the convenience method
# setter for the given attribute.
def self.provide_setter(attribute)
define_method("#{attribute}=") do |value|
state.provide(attribute, value)
end
end
# This method is the attribute store for the given declaration key.
def self.declarations(key)
instance_variable_cached("#{key}_declarations") do
declarations_initial(key)
end
end
# This method initializes the attribute store for the given declaration key.
# It checks if a parent class already has an existing store and duplicates it
# for independent usage. Otherwise it creates a new one.
def self.declarations_initial(key)
return Set.new([]) if !superclass.respond_to?(:declarations)
superclass.send(:declarations, key).dup
end
# This method creates an accessor to a cached instance variable for the given scope.
# It will create a new variable with the result of the given block as an initial value.
# On later calls it will return the already initialized, cached variable state.
# The variable will be created by default as a class variable. If a instance scope is
# passed it will create an instance variable instead.
def self.instance_variable_cached(key, scope: self)
cache = "@#{key}"
value = scope.instance_variable_get(cache)
return value if value
value = yield
scope.instance_variable_set(cache, value)
end
# This method is an instance wrapper around the class method .instance_variable_cached.
# It will behave the same but passed the instance scope to create an
# cached instance variable.
def instance_variable_cached(key, &block)
self.class.instance_variable_cached(key, scope: self, &block)
end
# This method is an convenience wrapper to create an instance
# and then directly processing it.
def self.process(*args)
new(*args).process
end
def initialize(state)
@state = state
end
def process
raise "Missing implementation of '#{__method__}' method for '#{self.class.name}'"
end
end
end
end