base.rb 8.3 KB

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