menu
Recently, I became interested in Rails' Action Mailbox, and I read several articles including the official documentation. One article from Prograils (https://prograils.com/real-life-examples-adding-action-mailbox-to-a-rails-6-app) particularly caught my attention due to its discussion on using the simple_command gem (https://github.com/nebulab/simple_command). This article shows how the gem can help you avoid repeating the way you invoke your services or classes. It allows you to structure classes like this:

# define a command class
class AuthenticateUser
  prepend SimpleCommand

  # optional, initialize the command with some arguments
  def initialize(email, password)
    @email = email
    @password = password
  end

  # mandatory: define a #call method. its return value will be available
  #            through #result
  def call
    if user = User.find_by(email: @email)&.authenticate(@password)
    ...
end

So you can invoke it like this
AuthenticateUser.call(session_params[:email], session_params[:password])

And I wondered how the SimpleCommand module might be structured. Additionally, I considered how it could support keyword arguments in the form of:
def my_method(value:, second_value:)
  puts "value #{value}, second_value #{second_value}"
end
my_method(value: 'value one', second_value: 'value two')

In fact, it is quite simple. Let’s define it in two different ways without using any additional dependencies:

Using class_eval and self for defining a class method

module SimpleCommand
  def self.included(klass)
    klass.class_eval do 
      def self.call(*args, **kwargs)
        puts "args #{args}"
        new(*args, **kwargs).call
      end
    end
  end
end

or

Using extend to define the methods as class methods

module SimpleCommand
  def self.included(klass)
    klass.extend(ClassMethods)
  end
  
  module ClassMethods
    def call(*args, **kwargs)
      new(*args, **kwargs).call
    end
  end
end

And here is how you can use it after that
class NamedArguments
  include SimpleCommand
  def initialize(value: , second:)
    @value = value
    @second = second
  end

  def call
    puts "the method in myClass @value: #{@value}, @second #{@second}"
  end
end

NamedArguments.call(value: 'the value', second: 'from second')

Or using positional arguments

class SimpleArguments
  include SimpleCommand
  def initialize(valueOne, valueTwo)
    @valueOne = valueOne
    @valueTwo = valueTwo
  end

  def call
    puts "the method in myClass @value: #{@valueOne}, @second #{@valueTwo}"
  end
end

SimpleArguments.call('arg one', 'arg two')

There you have it!