Project

method_man

0.01
No commit activity in last 3 years
No release in over 3 years
Provides a MethodObject class which implements KentBeck's "method object" pattern.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

>= 0
>= 0
 Project Readme

Method Man

Defines a MethodObject class which facilitates basic setup for Kent Beck's "method object".

  • Facilitates basic method object pattern setup. You only need to supply an instance call method.
  • Accepts a list of arguments which are mapped to required keyword arguments.
  • Disallows calling new on the resulting MethodObject class instance.

Bundler usage

gem 'method_man', require: 'method_object'

Requirements

  • Ruby >= 2.1

Example

  require 'method_object'

  class MakeArbitraryArray < MethodObject
    attrs(:first_name, :last_name, :message)

    def call
      [fullname, message, 42]
    end

    def fullname
      "#{first_name} #{last_name}"
    end
  end

  MakeArbitraryArray.call(
    first_name: 'John',
    last_name: 'Smith',
    message: 'Hi',
  )
  => ['John Smith', 'Hi', 42]

Also allows automatic delegation inspired by Go's delegation.

  require 'method_object'

  class MakeArbitraryArray < MethodObject
    attrs(:company)

    def call
      [
        company.name,
        name, # Automatic delegation since company has a `name` method
        company_name, # Automatic delegation with prefix
      ]
    end
  end

  company = Company.new(name: 'Tyrell Corporation')

  MakeArbitraryArray.call(company: company)
  => ['Tyrell Corporation', 'Tyrell Corporation', 'Tyrell Corporation']

Naming

Why is the class method called call? Some people have argued for names like TaskSender.send_task instead of TaskSender.call. My reasoning is.

  1. call is a ubiquitous concept in Ruby, as it's how you invoke Procs.
  2. There's even a syntactic sugar for this, .() instead of .call(), e.g. TaskSender.(task).
  3. The name call clearly calls out to someone reading the code that "this is an invocation of a method object". I would say this is especially so if you see something like TaskSender.(task).
  4. Avoiding redundancy. Any custom name will always just match the module/class name, e.g.
TaskSender.send_task # This is redundant
  1. Minimizing complexity: adding an option to specify the class method would introduce additional complexity.

History

In Refactoring: Ruby Edition, we see this at the top of the section on the method object pattern.

Stolen shamelessly from Kent Beck’s Smalltalk Best Practices.

  1. Create a new class, name it after the method.

In the example, the instance method Account#gamma is refactored to Gamma.compute. Kent Beck's original example refactored Obligation#sendTask to TaskSender.compute.

Noun vs. verb

Beck uses TaskSender. I personally prefer SendTask, essentially just preserving the name of whatever method you're converting to a method object. However I don't think this detail is of much import. I might recommend trying a little of both, and seeing which naming seems least confusing when you're coming back to it weeks later.

How useful is this pattern?

Kent Beck has raved about it, saying:

extract method object is such deep deep magic. it brings clarity to the confused and structure to the chaotic.