Jan 09, 2019

Simpler parameter handling for Interactors

At BuyCoins, we use the Interactor gem for business logic. It works like this:

Say you have an Interactor called SendCoins. You call can trigger it’s functionality by doing

 SendCoins.call(amount: 0.5, currency: Ethereum…) 

The has passed into #call will be available within the Interactor as attributes of an object called context.

 def call
 amount = context.amount
end 

As your Interactors become more complex and as such accept more parameters, it can become tedious to do this every time for each Interactor.

 def call
 amount = context.amount
 currency = context.currency
 address = context.address
 … etc etc 

The first step to eliminating this annoying repetitiveness was revealed to me by Alessandro Desantis. He realized that the delegate method was perfect.

Delegate is included in ActiveSupport and as such, in every rails application.

It is a pretty simple method but it’s very useful.

 delegate :amount, :currency, :address, to: :context 

What this does is; whenever amount is called on (or within the interactor), the method call is literally delegated to context and shall return what context.amount returns.

To make it a bit neater, I decided to go a step further.

All our Interactors are subclasses of ApplicationInteractor. There’s some shared logic in that file related to failures and error reporting.

Decided to add this method to it.

 def self.parameters(*params)
 delegate *params, to: : context
end 

This way, in your Interactors, all you need to do is.

 parameters: :amount, :currency, :address 

Which kind of reminds me of attr_reader.

That’s all folks.

Update (June 8, 2019)

The above code only lets you read the parameters but doesn’t let you write them.

The code below adds to the self.parameters method to make that happen.

 def self.parameters(*parameters)
  delegate *parameters, to: :context
  
  parameters.each do |parameter|
    method_name = "update_#{parameter}"
    define_method(method_name) do |new_value|
      context.send("#{parameter}=", new_value)
    end
  end
end

This is a simple bit of metaprogramming that uses define_method to dynamically create update_* methods for every parameter.

Doing this:

 parameters: :amount, :currency, :address 

Lets you update the values of context.amount, context.currency and context.address like this:

 update_amount 0.2
update_currency Bitcoin
update_address "bc1q7tmpy0pqn4mzu02h3ywv7a4t98xdepxgym6tr5" 
Copied to clipboard!