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"