module Eco module API module UseCases class UseCase TYPES = [:import, :filter, :transform, :sync, :export] ALL_PARAMS = [:input, :people, :session, :options] TYPE_PARAMS = { import: [:input, :session], filter: [:people, :session, :options], transform: [:people, :session], export: [:people, :session, :options] } MAX_CHAINS = 70 @@num_chains = 0 attr_reader :name, :type, :times_used def initialize(name, type:, root:, options: {}, &block) self.root = root @case = block @name = name @type = type @options = options @chains = [] @resolved_chains = nil @times_used = 0 end def root=(value) raise "You cannot change root UseGroup once the chains have been resolved" if @resolved_chains raise "Root should be a UseGroup. Given: #{value}" if !value.is_a?(UseGroup) @root = value end def use(preserve_chains: false, recursive: false) newcase = UseCase.new(@name, type: @type, root: @root, options: @options, &@case) if preserve_chains chain_use = {preserve_chains: recursive, recursive: recursive} @chains = @chains.map do |usecase| if usecase.respond_to? :call Proc.new do |usegroup| usecase = usecase.call(usegroup) usecase.use(chain_use).chain_to(newcase) usecase end else usecase.use(chain_use).chain_to(newcase) usecase end end end newcase end def process(input: nil, people: nil, session:, options: {}) raise "This case has been already used. To create multiple instances of same use case, use 'use' method" if @done validate_args(input: input, people: people, session: session, options: options) opts = options&.dup opts = @options.merge(opts || {}) case @type when :import out = input = @case.call(input, session, opts) when :filter out = people = @case.call(people, session, opts) when :transform out = jobs = into_a(@case.call(people, session, opts)) when :sync out = jobs = into_a(@case.call(input, people, session, opts)) when :export out = stat = @case.call(people, session, opts) end @times_used += 1 data_model = { self => { data: { input: input, people: people, session: session, options: opts, output: out } } } post_usecase(data_model) end def chain(usecase = nil) @@num_chains += 1 raise "Reached maximum number of chained use cases (#{MAX_CHAINS}). Looks like a recursive cyclic chain 'use'" if @@num_chains >= MAX_CHAINS raise "A UseCase can only be chained with another UseCase" if usecase && !usecase.is_a?(UseCase) raise "Missuse. Please use either parameter or block but not both" if block_given? && usecase usecase = block_given?? Proc.new : usecase @chains.push(usecase) self end def self.valid_type(type) TYPES.include?(type) end def self.type_params(type) raise "Invalid type '#{type.to_s}'" if !valid_type?(type) TYPE_PARAMS[type] end protected def chain_to(usecase) raise "A UseCase can only be chained with another UseCase" if usecase && !usecase.is_a?(UseCase) usecase.chain(self) end def resolved_chains(use_group = nil) return @resolved_chains if @resolved_chains raise "Only UseGroup object can resolve chains. Given: #{use_group} " if use_group && !use_group.is_a?(UseGroup) use_group = use_group || @root @resolved_chains = @chains.map do |usecase| usecase = usecase.call(use_group) if usecase.respond_to? :call raise "A UseCase can only be chained with another UseCase" if usecase && !usecase.is_a?(UseCase) usecase.resolved_chains(use_group) usecase end @resolved_chains end private def post_usecase(data_model) return data_model if resolved_chains.empty? data_model[self][:chains] = {} unless data_model[self][:chains] resolved_chains.each do |usecase| # chained cases use same params as parent case (out of simplicity) params = data_model[self][:data].slice(*ALL_PARAMS) #TYPE_PARAMS[usecase.type]) data_model[self][:chains].merge(usecase.process(params)) end data_model end def validate_args(input:, people:, session:, options:) case when !session.is_a?(Eco::API::Session) raise "A UseCase needs a Session object. Given: #{session}" when input_required? && !input raise "UseCase of type '#{@type.to_s}' requires a valid input. None given" when people_required? && !people.is_a?(Eco::API::Organization::People) raise "UseCase of type '#{@type.to_s}' requires a People object. Given: #{people}" when !options || (options && !options.is_a?(Hash)) raise "To inject dependencies via ':options' it should be a Hash object. Given: #{options}" when options_required? && !options raise "UseCase of type '#{@type.to_s}' requires a Hash ':options' object." end true end def input_required? [:import, :sync].include?(@type) end def people_required? [:filter, :transform, :sync, :export].include?(@type) end def options_required? [:filter].include?(@type) end def into_a(value) value = [].push(value) unless value.is_a?(Array) value end end end end end