base.rb 9.4 KB

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