Class: Telephone::Service

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Model
Defined in:
lib/telephone/service.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) {|_self| ... } ⇒ Service

Primary responsibility of initialize is to instantiate the attributes of the service object with the expected values.

Yields:

  • (_self)

Yield Parameters:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/telephone/service.rb', line 22

def initialize(attributes = {})
  attributes = attributes.transform_keys(&:to_sym)

  attributes.each do |key, value|
    send("#{key}=", value)
  end

  self.class.defaults.each do |key, value|
    next if attributes.key?(key)

    resolved = if value.is_a?(Proc)
      instance_exec(&value)
    elsif value.respond_to?(:call)
      value.call
    else
      value
    end

    send("#{key}=", resolved)
  end

  super
  yield self if block_given?
end

Instance Attribute Details

#resultObject

The primary outcome of the service object. For consistency, all service objects always return self. If you need a value returned, the return value of #call will be available on the attribute @result.

Examples:

Telephone::Service.call(foo: bar).result # "baz"


17
18
19
# File 'lib/telephone/service.rb', line 17

def result
  @result
end

Class Method Details

.argument(arg, default: nil, required: false, type: nil) ⇒ Object

Defines a getter/setter for a service object argument. This also allows you to pass in a default, or set the argument to “required” to add a validation that runs before executing the block.

The default value can be a static value or any callable object (Proc, lambda, method, or any object that responds to #call) that will be evaluated at runtime when the service is instantiated.

Callable defaults are evaluated in the context of the service instance, so they can access other attributes. They are processed in definition order, meaning a callable can depend on any argument defined before it.

To store a Proc as the actual value, wrap it in another lambda:

argument :my_proc, default: -> { -> { puts "hi" } }

You can also pass type: to validate that the argument is an instance of the given class or module. Nil values are allowed unless the argument is also marked as required.

Examples:

class SomeService < Telephone::Service
  argument :first_name, default: "John"
  argument :last_name, default: "Doe"
  argument :full_name, default: -> { "#{first_name} #{last_name}" }
  argument :timestamp, default: -> { DateTime.current }
  argument :user, type: User

  def call
    puts full_name
    puts timestamp
  end
end


98
99
100
101
102
103
104
105
106
107
108
# File 'lib/telephone/service.rb', line 98

def argument(arg, default: nil, required: false, type: nil)
  arg = arg.to_sym
  send(:attr_accessor, arg)
  send(:validates, arg, presence: true) if required

  if type
    send(:validates_with, Telephone::TypeValidator, attributes: arg, with: type, allow_nil: true)
  end

  defaults[arg] = default
end

.call(*args, **kwargs) ⇒ Object

Executes the service object action directly from the class — similar to the way a Proc can be executed with ‘Proc#call`. This allows us to add some common logic around running validations and setting argument defaults.

Examples:

Telephone::Service.call(foo: bar)
Telephone::Service.call({ foo: bar })


125
126
127
# File 'lib/telephone/service.rb', line 125

def call(*args, **kwargs)
  new(args.extract_options!.merge(kwargs)).call
end

.defaultsObject

Used to maintain a list of default values to set prior to initialization based on the options in #argument.



113
114
115
# File 'lib/telephone/service.rb', line 113

def defaults
  @defaults ||= {}
end

.method_added(method_name) ⇒ Object

When the subclass overwrites the #call method, reassign it to #__call. This allows us to still control what happens in the instance level of #call.



132
133
134
135
136
137
138
139
# File 'lib/telephone/service.rb', line 132

def method_added(method_name)
  if method_name == :call
    alias_method :__call, :call
    send(:remove_method, :call)
  else
    super
  end
end

Instance Method Details

#callObject



59
60
61
62
# File 'lib/telephone/service.rb', line 59

def call
  self.result = __call if valid?
  self
end

#success?Boolean

Determines whether or not the action of the service object was successful.

Examples:

Telephone::Service.call(foo: bar).success? # true

Returns:

  • (Boolean)

    whether or not the action succeeded.



55
56
57
# File 'lib/telephone/service.rb', line 55

def success?
  errors.empty?
end