Class: TheHelp::Service

Inherits:
Object
  • Object
show all
Includes:
ProvidesCallbacks, ServiceCaller
Defined in:
lib/the_help/service.rb

Overview

Note:

See README section “Running Callbacks”

An Abstract Service Class with Authorization and Logging

Define subclasses of Service to build out the service layer of your application.

Examples:

class CreateNewUserAccount < TheHelp::Service
  input :user
  input :send_welcome_message, default: true

  authorization_policy do
    call_service(Authorize, permission: :admin_users).success?
  end

  main do
    # do something to create the user account
    if send_welcome_message
      call_service(SendWelcomeMessage, user: user) do |result|
        callback(:message_sent) if result.success?
      end
    end
    result.success
  end

  callback(:message_sent) do |message|
    # do something really important with `message`, I'm sure
  end
end

class Authorize < TheHelp::Service
  input :permission

  authorization_policy allow_all: true

  main do
    if user_has_permission?
      result.success
    else
      result.error 'Permission Denied'
    end
  end
end

class SendWelcomeMessage < TheHelp::Service
  input :user

  main do
    message = 'Hello, world!'
    # do something with message...
    result.success message
  end
end

CreateNewUserAccount.(context: current_user, user: new_user_object)

Calling services with a block


# The service result will be yielded to the block if a block is present.

class CanTakeABlock < TheHelp::Service
  authorization_policy allow_all: true

  main do
    result.success :the_service_result
  end
end

service_result = nil

CanTakeABlock.call { |result| service_result = result.value }

service_result
#=> :the_service_result

Defined Under Namespace

Classes: Result

Constant Summary collapse

CB_NOT_AUTHORIZED =

The default :not_authorized callback

It will raise a TheHelp::NotAuthorizedError when the context is not authorized to perform the service.

->(service:, context:) {
  raise TheHelp::NotAuthorizedError,
        "Not authorized to access #{service.name} as #{context.inspect}."
}

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ServiceCaller

#call_service

Methods included from ProvidesCallbacks

#callback, included

Constructor Details

#initialize(context:, logger: Logger.new($stdout), not_authorized: CB_NOT_AUTHORIZED, **inputs) ⇒ Service

Returns a new instance of Service.



250
251
252
253
254
255
256
257
258
259
# File 'lib/the_help/service.rb', line 250

def initialize(context:, logger: Logger.new($stdout),
               not_authorized: CB_NOT_AUTHORIZED, **inputs)
  @result = Result.new

  self.context = context
  self.logger = logger
  self.not_authorized = not_authorized
  self.inputs = inputs
  self.stop_caller = false
end

Class Method Details

.attr_accessor(*names, make_private: false, private_reader: false, private_writer: false) ⇒ Object

Defines attr_accessors with scoping options



97
98
99
100
101
102
103
104
# File 'lib/the_help/service.rb', line 97

def attr_accessor(*names, make_private: false, private_reader: false,
                  private_writer: false)
  super(*names)
  names.each do |name|
    private name if make_private || private_reader
    private "#{name}=" if make_private || private_writer
  end
end

.authorization_policy(allow_all: false, &block) ⇒ Object

Defines the service authorization policy

If allow_all is set to true, or if the provided block (executed in the context of the service object) returns true, then the service will be run when called. Otherwise, the not_authorized callback will be invoked.

Parameters:

  • allow_all (Boolean) (defaults to: false)
  • block (Proc)

    executed in the context of the service instance (and can therefore access all inputs to the service)



143
144
145
146
147
148
149
150
151
# File 'lib/the_help/service.rb', line 143

def authorization_policy(allow_all: false, &block)
  if allow_all
    define_method(:authorized?) { true }
  else
    define_method(:authorized?, &block)
  end
  private :authorized?
  self
end

.call(*args, &block) ⇒ Object

Convenience method to instantiate the service and immediately call it

Any arguments are passed to #initialize



109
110
111
# File 'lib/the_help/service.rb', line 109

def call(*args, &block)
  new(*args).call(&block)
end

.inherited(other) ⇒ Object

:nodoc:



114
115
116
# File 'lib/the_help/service.rb', line 114

def inherited(other)
  other.instance_variable_set(:@required_inputs, required_inputs.dup)
end

.input(name, **options, &block) ⇒ Object

Defines a service input

The specified input becomes a named parameter for the service’s ‘#call` method.

Parameters:

  • name (Symbol)

    This becomes the name of the input parameter

  • block (Proc)

    If a block is provided, the contents of the block will be executed in the scope of the service instance in order to provide the default value of the input. This is different than providing a Proc to the ‘:default` option, which would simply return the Proc itself as the default value rather than calling it.

  • options (Hash)

    a customizable set of options

Options Hash (**options):

  • :default (Object)

    If specified (and no block is given), this becomes the literal default value for the input.



167
168
169
170
171
172
173
174
175
# File 'lib/the_help/service.rb', line 167

def input(name, **options, &block)
  if options.key?(:default) || block
    make_optional_input(name, options[:default], &block)
  else
    attr_accessor name, make_private: true
    required_inputs << name
  end
  self
end

.main(&block) ⇒ Object

Defines the primary routine of the service

The code that will be run when the service is called, assuming it is unauthorized.



128
129
130
131
132
# File 'lib/the_help/service.rb', line 128

def main(&block)
  define_method(:main, &block)
  private :main
  self
end

.required_inputsObject

:nodoc: instances need access to this, otherwise it would be made private



120
121
122
# File 'lib/the_help/service.rb', line 120

def required_inputs
  @required_inputs ||= Set.new
end

Instance Method Details

#callTheHelp::Service::Result

Executes the service and returns the result



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/the_help/service.rb', line 264

def call
  validate_service_definition

  catch(:stop) do
    authorize
    log_service_call
    main
    check_result!
  end

  self.block_result = yield result if block_given?

  throw :stop if stop_caller

  return block_result if block_given?

  result
end