class LunaPark::UseCases::Scenario

The main goal of the use case is a high-level description of the business process. This specific implementation is based on the ideas of Ivar Jacobson from his article Ivar Jacobson: Use Case 2.0.

@example Create new user

module Errors
  # To catch the errors, it's should be the error must
  # be inherited from the class LunaPark::Errors::Business
  class UserAlreadyExists < LunaPark::Errors::Business
    message 'Sorry user with this email already created'
    notify: :info
  end
end

class CreateUser < Scenario
  attr_accessor :email, :password

  def call!
    user          = Entities::User.new
    user.email    = email
    user.password = Service::Encode.call(password)

    DB.transaction do
     raise Errors::UserAlreadyExists if Repo::Users.exists?(user)
     Repo::Users.create(user)
    end
  end
end

Constants

DEFAULT_NOTIFIER
FAIL
INIT
SUCCESS

Attributes

data[R]

The result obtained during the execution of the scenario. It's nil on failure scenario.

@example when work just started

scenario = Scenario.new
scenario.data # => nil

@example on fail

scenario.call  # Something went wrong
scenario.data  # => nil

@example on success

class SuccessScenario < Scenario
  def call!
    :result
  end
end

scenario = SuccessScenario.new
scenario.call
scenario.data # => :result
failure[R]

If a failure occurs during the scenario, then this attribute will contain this error Else it's nil.

@example when work just started

scenario = Scenario.new
scenario.fail # => nil

@example on fail

class Fail < Errors::Business; end
class FailScenario < Scenario
  def call!
    raise Fail
    :result
  end
end

scenario = FailScenario.new
scenario.call  # Something went wrong
scenario.fail  # => #<Fail: Fail>

@example on success

scenario.call
scenario.fail # => nil
locale[R]

Current locale

state[R]

What status is the process of doing the work under the scenario. It can be :initialized, :success, :failure

@example when work just started

scenario = Scenario.new
scenario.state # => :initialized

@example on fail

scenario.call  # Something went wrong
scenario.state # => :failure

@example on success

scenario.call
scenario.state # => :success

Public Class Methods

default_notifier() click to toggle source

@return Default notifier

# File lib/luna_park/use_cases/scenario.rb, line 271
def default_notifier
  @default_notifier ||= DEFAULT_NOTIFIER
end
new(notifier: nil, locale: nil, **attrs) click to toggle source

Initialize new scenario

@param notifier - custom notifier for the current instance of scenario @param locale - custom locale for the current instance of scenario @param attrs - the parameters that are needed to implement the scenario, usually the request model

@example without parameters

class SayHello < Scenario
  attr_accessor :first_name, :last_name

  def call!
    t('hello_my_nme_is', first_name: first_name, last_name: last_name)
  end
end

hello = Scenario.new first_name: 'John', last_name: 'Doe'
hello.notifier    # => Notifiers::Log
hello.locale      # => nil
hello.first_name  # => 'John'
hello.last_name   # => 'Doe'
hello.call!       # => 'Hello my name is John Doe'

@example with custom parameters

hello = Scenario.new first_name: 'John', last_name: 'Doe', notifier: Notifier::Bugsnag, locale: :ru
hello.notifier    # => Notifiers::Bugsnag
hello.locale      # => :ru
hello.first_name  # => 'John'
hello.last_name   # => 'Doe'
hello.call!       # => 'Добрый день, меня зовут John Doe'
# File lib/luna_park/use_cases/scenario.rb, line 153
def initialize(notifier: nil, locale: nil, **attrs)
  set_attributes attrs
  @data     = nil
  @failure  = nil
  @locale   = locale
  @notifier = notifier
  @state    = INIT
end
notify_with(notifier) click to toggle source

Set notifier for this class

@example set notifier

class Foobar < Scenario
  notify_with Notifier::Bugsnag

  def call!
    true
  end
end

Foobar.default_notifier # => Notifier::Bugsnag
Foobar.new.notifier     # => Notifier::Bugsnag
# File lib/luna_park/use_cases/scenario.rb, line 288
def notify_with(notifier)
  @default_notifier = notifier
end

Public Instance Methods

call() click to toggle source

You must define this action and describe all business logic here. When you run this method - it run as is, and does not change scenario instance.

@abstract

@example fail way

class YouDied < Errors::Business; end

class Shot < Scenario
  attr_accessor :lucky_mode

  def call!
    raise YouDied, 'Always something went wrong' unless lucky_mode
    'All good'
  end
end

bad_day = Shot.new lucky_mode: false
bad_day.call         # => #<Shot:0x000055cbee4bc070...>
bad_day.success?     # => false
bad_day.fail?        # => true
bad_day.data         # => nil
bad_day.state        # => :failure
bad_day.fail         # => #<YouDied:0x000055cbee4bc071...>
bad_day.fail_message # => ''

@example main way

good_day = Shot.new lucky_mode: true
good_day.call! # => 'All good'
good_day.state # => :initialized

@example Russian roulette

class RussianRoulette < Scenario
  def call!
    [true, true, true, true, true, false].shuffle do |bullet|
      Shot.call! lucky_mode: bullet
    end
  end
end
# File lib/luna_park/use_cases/scenario.rb, line 239
def call
  catch { @data = call! }
  self
end
call!() click to toggle source

You must define this action and describe all business logic here. When you run this method - it run as is, and does not change scenario instance.

@abstract

@example Fail way

class Shot < Scenario
  attr_accessor :lucky_mode

  def call!
    raise YouDied, 'Always something went wrong' unless lucky_mode
    'All good'
  end
end

bad_day = Shot.new lucky_mode: false
bad_day.call! # it raise - SomethingWentWrong: Always something went wrong
bad_day.state # => :initialized

@example Main way

good_day = Shot.new lucky_mode: true
good_day.call! # => 'All good'
good_day.state # => :initialized

@example Russian roulette

# `.call!` usually use for "scenario in scenario"
class RussianRoulette < Scenario
  def call!
    [true, true, true, true, true, false].shuffle do |bullet|
      Shot.call! lucky_mode: bullet
    end
  end
end
# File lib/luna_park/use_cases/scenario.rb, line 195
def call!
  raise Errors::AbstractMethod
end
fail?() click to toggle source

@return [Boolean] true if the scenario runs unsuccessfully

# File lib/luna_park/use_cases/scenario.rb, line 250
def fail?
  state == FAIL
end
Also aliased as: failure?, failed?
failed?()
Alias for: fail?
failure?()
Alias for: fail?
failure_message(locale: nil) click to toggle source

@return [String] fail message

# File lib/luna_park/use_cases/scenario.rb, line 265
def failure_message(locale: nil)
  failure&.message(locale: locale || self.locale)
end
notifier() click to toggle source

Return notifier

# File lib/luna_park/use_cases/scenario.rb, line 245
def notifier
  @notifier ||= self.class.default_notifier
end
succeed?()
Alias for: success?
success?() click to toggle source

@return [Boolean] true if the scenario runs successfully

# File lib/luna_park/use_cases/scenario.rb, line 258
def success?
  state == SUCCESS
end
Also aliased as: succeed?

Private Instance Methods

catch() { || ... } click to toggle source
# File lib/luna_park/use_cases/scenario.rb, line 295
def catch
  yield
rescue Errors::Base => e
  @state = FAIL
  notify_error e if e.notify?
  handle_error e
else
  @state = SUCCESS
end
handle_error(error) click to toggle source
# File lib/luna_park/use_cases/scenario.rb, line 309
def handle_error(error)
  case error
  when Errors::Business then on_catch(error)
  when Errors::System then on_raise(error)
  else raise ArgumentError, "Unknown error action #{error.class}"
  end
end
notify_error(error) click to toggle source
# File lib/luna_park/use_cases/scenario.rb, line 305
def notify_error(error)
  notifier.post error, lvl: error.notify_lvl
end
on_catch(error) click to toggle source
# File lib/luna_park/use_cases/scenario.rb, line 317
def on_catch(error)
  @failure = error
end
on_raise(error) click to toggle source
# File lib/luna_park/use_cases/scenario.rb, line 321
def on_raise(error)
  raise error
end