base.rb 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. require 'mixin/rails_logger'
  2. class Sequencer
  3. class Unit
  4. class Base
  5. include ::Mixin::RailsLogger
  6. attr_reader :state
  7. # Creates the class macro `uses` that allows a Unit to
  8. # declare the attributes it will use via parameter or block.
  9. # On the other hand it returns the declared attributes if
  10. # called without parameters.
  11. #
  12. # This method can be called multiple times and will add the
  13. # given attributes to the list. It takes care of handling
  14. # duplicates so no uniq check is required. It's safe to use
  15. # for inheritance structures and modules.
  16. #
  17. # It additionally creates a getter instance method for each declared
  18. # attribute like e.g. attr_reader does. This allows direct access
  19. # to an attribute via `attribute_name`. See examples.
  20. #
  21. # @param [Array<Symbol>] attributes an optional list of attributes that the Unit uses
  22. #
  23. # @yield [] A block returning a list of attributes
  24. #
  25. # @example Via regular Array<Symbol> parameter
  26. # uses :instance, :action, :connection
  27. #
  28. # @example Via block
  29. # uses do
  30. # additional = method(parameter)
  31. # [:some, additional]
  32. # end
  33. #
  34. # @example Listing declared attributes
  35. # Unit::Name.uses
  36. # # => [:instance, :action, :connection, :some, :suprise]
  37. #
  38. # @example Using declared attribute in the Unit via state object
  39. # state.use(:instance).id
  40. #
  41. # @example Using declared attribute in the Unit via getter
  42. # instance.id
  43. #
  44. # @return [Array<Symbol>] the list of all declared uses of a Unit.
  45. def self.uses(*attributes, &block)
  46. declaration_accessor(
  47. key: __method__,
  48. attributes: attributes(*attributes, &block)
  49. ) do |attribute|
  50. use_getter(attribute)
  51. end
  52. end
  53. # Creates the class macro `provides` that allows a Unit to
  54. # declare the attributes it will provided via parameter or block.
  55. # On the other hand it returns the declared attributes if
  56. # called without parameters.
  57. #
  58. # This method can be called multiple times and will add the
  59. # given attributes to the list. It takes care of handling
  60. # duplicates so no uniq check is required. It's safe to use
  61. # for inheritance structures and modules.
  62. #
  63. # It additionally creates a setter instance method for each declared
  64. # attribute like e.g. attr_writer does. This allows direct access
  65. # to an attribute via `self.attribute_name = `. See examples.
  66. #
  67. # A Unit should usually not provide more than one or two attributes.
  68. # If your Unit provides it's doing to much and should be splitted
  69. # into multiple Units.
  70. #
  71. # @param [Array<Symbol>] attributes an optional list of attributes that the Unit provides
  72. #
  73. # @yield [] A block returning a list of attributes
  74. #
  75. # @example Via regular Array<Symbol> parameter
  76. # provides :instance, :action, :connection
  77. #
  78. # @example Via block
  79. # provides do
  80. # additional = method(parameter)
  81. # [:some, additional]
  82. # end
  83. #
  84. # @example Listing declared attributes
  85. # Unit::Name.provides
  86. # # => [:instance, :action, :connection, :some, :suprise]
  87. #
  88. # @example Providing declared attribute in the Unit via state object parameter
  89. # state.provide(:action, :created)
  90. #
  91. # @example Providing declared attribute in the Unit via state object block
  92. # state.provide(:instance) do
  93. # # ...
  94. # instance
  95. # end
  96. #
  97. # @example Providing declared attribute in the Unit via setter
  98. # self.action = :created
  99. #
  100. # @return [Array<Symbol>] the list of all declared provides of a Unit.
  101. def self.provides(*attributes, &block)
  102. declaration_accessor(
  103. key: __method__,
  104. attributes: attributes(*attributes, &block)
  105. ) do |attribute|
  106. provide_setter(attribute)
  107. end
  108. end
  109. def self.attributes(*attributes)
  110. # exectute block if given and add
  111. # the result to the (possibly empty)
  112. # list of given attributes
  113. attributes.concat(yield) if block_given?
  114. attributes
  115. end
  116. # This method is the heart of the #uses and #provides method.
  117. # It takes the declaration key and decides based on the given
  118. # parameters if the given attributes should get stored or
  119. # the stored values returned.
  120. def self.declaration_accessor(key:, attributes:)
  121. # if no attributes were given (storing)
  122. # return the already stored list of attributes
  123. return declarations(key).to_a if attributes.blank?
  124. # loop over all given attributes and
  125. # add them to the list of already stored
  126. # attributes for the given declaration key
  127. attributes.each do |attribute|
  128. next if !declarations(key).add?(attribute)
  129. # yield callback if given to create
  130. # getter or setter or whatever
  131. yield(attribute) if block_given?
  132. end
  133. end
  134. # This method creates the convenience method
  135. # getter for the given attribute.
  136. def self.use_getter(attribute)
  137. define_method(attribute) do
  138. instance_variable_cached(attribute) do
  139. state.use(attribute)
  140. end
  141. end
  142. end
  143. # This method creates the convenience method
  144. # setter for the given attribute.
  145. def self.provide_setter(attribute)
  146. define_method("#{attribute}=") do |value|
  147. state.provide(attribute, value)
  148. end
  149. end
  150. # This method is the attribute store for the given declaration key.
  151. def self.declarations(key)
  152. instance_variable_cached("#{key}_declarations") do
  153. declarations_initial(key)
  154. end
  155. end
  156. # This method initializes the attribute store for the given declaration key.
  157. # It checks if a parent class already has an existing store and duplicates it
  158. # for independent usage. Otherwise it creates a new one.
  159. def self.declarations_initial(key)
  160. return Set.new([]) if !superclass.respond_to?(:declarations)
  161. superclass.send(:declarations, key).dup
  162. end
  163. # This method creates an accessor to a cached instance variable for the given scope.
  164. # It will create a new variable with the result of the given block as an initial value.
  165. # On later calls it will return the already initialized, cached variable state.
  166. # The variable will be created by default as a class variable. If a instance scope is
  167. # passed it will create an instance variable instead.
  168. def self.instance_variable_cached(key, scope: self)
  169. cache = "@#{key}"
  170. value = scope.instance_variable_get(cache)
  171. return value if value
  172. value = yield
  173. scope.instance_variable_set(cache, value)
  174. end
  175. # This method is an instance wrapper around the class method .instance_variable_cached.
  176. # It will behave the same but passed the instance scope to create an
  177. # cached instance variable.
  178. def instance_variable_cached(key, &block)
  179. self.class.instance_variable_cached(key, scope: self, &block)
  180. end
  181. # This method is an convenience wrapper to create an instance
  182. # and then directly processing it.
  183. def self.process(*args)
  184. new(*args).process
  185. end
  186. def initialize(state)
  187. @state = state
  188. end
  189. def process
  190. raise "Missing implementation of '#{__method__}' method for '#{self.class.name}'"
  191. end
  192. end
  193. end
  194. end