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!

lambda = lambda {|value| value ** value }

lambda.(10)
lambda.call(10)
lambda === 10
lambda[10]

proc = Proc.new {|value| value ** value }

proc.(10)
proc.call(10)
proc === 10
proc[10]

You can use a break

(1..10).to_a.each.with_index do |element, index|
  puts "element #{element}, index: #{index}"
  break if index == 3
end

Using catch and throw

Following a syntax like this

catch :label do
  (1..10).to_a.each.with_index do |e, index|
    throw :label if condition
  end
end

Practical example

def my_method
  result = []
  catch :completed do
    (1..10).to_a.each.with_index do |element, index|
      result << element
      throw :completed if index == 3
    end
  end
  result
end
my_method
=> [1, 2, 3, 4]

Using Return

If you want to use return, it will stop the iterations but also the method itself, so be aware of that, and that remember that the return clause only works if it is contained in a method

def return_example
  values = []
  (1..10).to_a.each_with_index do |element, index|
    values << element
    return values if index == 3
  end
end

return_example
=> [1, 2, 3, 4]