DEV Community

Cover image for Have you called your method object lately?
Jason Dinsmore for Hint

Posted on • Originally published at hint.io

Have you called your method object lately?

Originally posted on Hint's blog

Allow me to introduce you to another library that has proven itself useful when writing Ruby: the Procto gem.

Procto Overview

Procto is a gem that lets you turn a Ruby class into a method object. This helps clean up code that instantiates a class to perform a single task, calculation, etc.

To better understand what it's trying to facilitate, perhaps a review of method objects in Ruby would help.

Ruby Method Objects

A method object is essentially an object that has a bound context and provides a single method.

Consider this example:

  >> meth_obj = [:foo, :bar, :baz].method(:size)
  => Array#size()
  >> meth_obj.class
  => Method < Object
  >> meth_obj.call
  => 3
Enter fullscreen mode Exit fullscreen mode

Above, meth_obj is a method object that is bound to the array containing [:foo, :bar, :baz]. In this case, when call is executed on the method object, the size message is passed to the context the method object is bound to ([:foo, :bar, :baz]) and the result is 3. Interestingly (though unrelated), Ruby allows you to unbind and rebind in cases like this.

For example:

  >> meth_obj = [:foo, :bar, :baz].method(:size)
  >> meth_obj.call
  => 3
  >> meth_obj = meth_obj.unbind.bind([:waldo, :fred])
  >> meth_obj.call
  => 2
Enter fullscreen mode Exit fullscreen mode

Use

To use Procto, you'll need to ensure you've got the gem installed and that it has been loaded by your program or application. If the gem is available, but hasn't been loaded, you can just use require 'procto' at the top of your Ruby class.

Once the gem is loaded and available for you to use, you'll just need to: include Procto.call right after your class definition, ala:

require 'procto'

class Foo
  include Procto.call

  def initialize(bar)
    @bar = bar
  end

  def call
    # do stuff
  end
end
Enter fullscreen mode Exit fullscreen mode

When invoking your class, you will supply call with the parameters you would normally pass to your class's initializer.

E.g.:

Instead of Foo.new(bar).call, you would do Foo.call(bar).

Blocks

Although Procto is very useful, it's not the answer for everything. One thing you might find yourself wanting to do is pass a block to your call method. Unfortunately, Procto does not support this.

Consider the following code:

class FooWithBlock
  include Procto.call

  def initialize(bar)
    @bar = bar
  end

  def call
    # do something

    yield if block_given?
  end

  protected

  attr_reader :bar
end
Enter fullscreen mode Exit fullscreen mode

You might expect to be able to use the above code like this:

FooWithBlock.call('Hello') do
  puts "I'm in a block!"
end
Enter fullscreen mode Exit fullscreen mode

Sadly, your block will never be executed.

If you need to do the above, you'll have to do so without using Procto, so you'd need to remove include Procto.call from your class, and invoke your class as follows:

FooWithBlock.new('Hello').call do
  puts "I'm in a block!"
end
Enter fullscreen mode Exit fullscreen mode

Bonus

If you haven't used it before, I also highly recommend the Concord gem. When used in conjunction with Procto, it really creates a nice interface for invoking method objects.

Concord abstracts away having to define the initializer and attribute accessors for your class and its attributes. It also happens to play very nicely with Procto.

Using both, you can turn:

class Foo

  def initialize(bar, baz)
    @bar = bar
    @baz = baz
  end

  def call
    # do stuff
  end

  protected

  attr_reader :bar, :baz
end

For.new(bar, baz).call
Enter fullscreen mode Exit fullscreen mode

Into:

class Foo
  include Concord.new(:bar, :baz)
  include Procto.call

  def call
    # do stuff
  end
end

Foo.call(bar, baz)
Enter fullscreen mode Exit fullscreen mode

I should mention that Concord limits your initializer to three parameters. You can work around this by passing a more complex data structure as one (or more) of the parameters, and extracting the parameters from that object, or by using the [Anima gem (https://github.com/mbj/anima) instead, which does not cap the number of parameters you can pass, but takes an initialization hash (keyword arguments) instead of parameters.

Summary

Procto is a fantastic tool for cleaning up your class's interface. When used in conjunction with the Concord gem, you'll find yourself writing significantly less boilerplate code to get your classes up and running. Your code will also end up generating a smaller abstract syntax tree (AST), which is generally a very good thing.

Top comments (1)

Collapse
 
dinjas profile image
Jason Dinsmore

There's a lot of truth in your observations. I think a benefit of using a gem (even if it does introduce a new dependency) is that you're able to DRY up code across multiple projects.

9.5kb, isn't a huge penalty, but I get what you're saying. Adding gems willy nilly is never a recommended approach.

I'd also encourage people to look at the gemspec of any gem they're considering adding. Many of them have lots of dependencies that may not be obvious.