Class: TheHelp::Service

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

Overview

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
    authorized = false
    call_service(Authorize, permission: :admin_users,
                 allowed: ->() { authorized = true })
    authorized
  end

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

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

class Authorize < TheHelp::Service
  input :permission
  input :allowed

  authorization_policy allow_all: true

  main do
    if user_has_permission?
      allowed.call
    end
  end
end

class SendWelcomeMessage < TheHelp::Service
  input :user
  input :success, default: ->() { }

  main do
    # whatever
    success.call
  end
end

CreateNewUserAccount.(context: current_user, user: new_user_object)

Calling services with a block


# Calling a service with a block when the service is not designed to
# receive one will result in an exception being raised

class DoesNotTakeBlock < TheHelp::Service
  authorization_policy allow_all: true

  main do
    # whatever
  end
end

DoesNotTakeBlock.call { |result| true } # raises TheHelp::NoResultError

# However, if the service *is* designed to receive a block (by explicitly
# assigning to the internal `#result` attribute in the main routine), the
# result will be yielded to the block if a block is present.

class CanTakeABlock < TheHelp::Service
  authorization_policy allow_all: true

  main do
    self.result = :the_service_result
  end
end

service_result = nil

CanTakeABlock.call() # works just fine
service_result
#=> nil              # but obviously the result is just discarded

CanTakeABlock.call { |result| service_result = result }
service_result
#=> :the_service_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

included

Constructor Details

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

Returns a new instance of Service.



187
188
189
190
191
192
193
# File 'lib/the_help/service.rb', line 187

def initialize(context:, logger: Logger.new($stdout),
               not_authorized: CB_NOT_AUTHORIZED, **inputs)
  self.context = context
  self.logger = logger
  self.not_authorized = not_authorized
  self.inputs = inputs
end

Class Method Details

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

Defines attr_accessors with scoping options



114
115
116
117
118
119
120
121
# File 'lib/the_help/service.rb', line 114

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)



163
164
165
166
167
168
169
170
171
# File 'lib/the_help/service.rb', line 163

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) ⇒ Class

Convenience method to instantiate the service and immediately call it

Any arguments are passed to #initialize

Returns:

  • (Class)

    Returns the receiver



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

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

.inherited(other) ⇒ Object

:nodoc:



134
135
136
# File 'lib/the_help/service.rb', line 134

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

.input(name, **options) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/the_help/service.rb', line 173

def input(name, **options)
  attr_accessor name, make_private: true
  if options.key?(:default)
    required_inputs.delete(name)
    define_method(name) do
      instance_variable_get("@#{name}") || options[:default]
    end
  else
    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.



148
149
150
151
152
# File 'lib/the_help/service.rb', line 148

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



140
141
142
# File 'lib/the_help/service.rb', line 140

def required_inputs
  @required_inputs ||= Set.new
end

Instance Method Details

#callObject



195
196
197
198
199
200
201
202
203
204
# File 'lib/the_help/service.rb', line 195

def call
  validate_service_definition
  catch(:stop) do
    authorize
    log_service_call
    main
    yield result if block_given?
  end
  self
end