Class: MethodObject

Inherits:
Object
  • Object
show all
Defined in:
lib/method_object.rb,
lib/method_object/version.rb,
lib/method_object/parameter.rb

Overview

Allows for the creation of method objects, to ease the extraction of complex methods from other classes and the implementation of service objects.

A method object works similarly to a proc/lambda, exposing a MethodObject.call method and convenience MethodObject.to_proc method to convert it to a Proc.

Major differences in behaviour compared to a ‘lambda`:

* It accepts only named parameters
* It performs type checking on the parameters

Examples:

Defining and invoking a method object

class ComplexCalculation < MethodObject
  parameter :start_number, Integer
  parameter :end_number, Integer, default: 2

  called do
    @magic_number = 42
    perform_complex_calculation
  end

  private

  def perform_complex_calculation
    start_number + second_number + @magic_number
  end
end

ComplexCalculation.call(start_number: 1, end_number: 3) #=> 46
ComplexCalculation.call(start_number: 1) #=> 45
ComplexCalculation.call(end_number: 3) #=> raise MethodObject::UnknownParameter

calculation = ComplexCalculation.new(end_number: 3)
calculation.start_number = 1
calculation.call # => 46

calculation.end_number = 2
calculation.call # => 45

Using the method object as a block

class NameSayer < MethodObject
  parameter :name, String

  called { "You're #{name}" }
end

def say_my_name
  puts "- Say my name."
  puts "- " + yield(name: "Heisenberg")
  puts "- You're goddamn right!"
end

say_my_name(&NameSayer)
# Output:
#
# - Say my name.
# - You're Heisenberg
# - You're goddamn right!

Constant Summary collapse

ParameterError =
Class.new(::ArgumentError)
ParameterRequired =
Class.new(ParameterError)
InvalidParameterValue =
Class.new(ParameterError)
UnknownParameter =
Class.new(ParameterError)
VERSION =
"0.2.0"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**args) ⇒ MethodObject

Returns a new instance of MethodObject.

Parameters:

  • **args (Hash{Symbol => Object})

    arguments to set on the method object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/method_object.rb', line 143

def initialize(**args)
  self.class.send(:parameters).freeze

  @monitor = Monitor.new

  args.each do |k, v|
    method = "#{k}="
    raise UnknownParameter, "Parameter #{k} unknown" unless respond_to?(method)
    public_send(method, v)
  end

  self.class.send(:parameters)
      .reject { |p| args.keys.map(&:to_sym).include?(p.name) }
      .select(&:default?)
      .each { |p| public_send("#{p.name}=", p.default_in(self)) }
end

Class Method Details

.call(**args) ⇒ Object

Calls the MethodObject with the given arguments.

Parameters:

  • **args (Hash{Symbol => Object})

    arguments to pass to the method

Returns:

  • (Object)

    return value of the method object



73
74
75
# File 'lib/method_object.rb', line 73

def call(**args)
  new(**args).call
end

.to_procProc

Returns a proc that calls the MO with the given arguments.

Returns:

  • (Proc)


79
80
81
# File 'lib/method_object.rb', line 79

def to_proc
  proc { |**args| call(**args) }
end

Instance Method Details

#callObject

Calls the method object with the parameters currently set.

Returns:

  • (Object)

    the return value result of the method invokation

Raises:

  • (ArgumentError)

    if any required parameter is missing



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

def call
  unless respond_to?(:do_call, true)
    raise NotImplementedError,
          'Implementation missing. Please use `called { ... }` to define method body'
  end

  @monitor.synchronize do
    assert_required_arguments!
    do_call
  end
end

#parametersHash{Symbol => Object}

Returns a hash with the parameters currently set.

Returns:

  • (Hash{Symbol => Object})


162
163
164
165
166
167
168
# File 'lib/method_object.rb', line 162

def parameters
  Hash[
    self.class.send(:parameters).map(&:name)
        .select { |p| instance_variable_defined?("@#{p}") }
        .map { |p| [p, public_send(p)] }
  ]
end

#to_procProc

Returns a lambda that calls the method object with the parameters currently set.

Returns:

  • (Proc)


187
188
189
# File 'lib/method_object.rb', line 187

def to_proc
  -> { call }
end