Ryan Bigg

⟵ Posts

Using Ruby 2.7's new triple-dot syntax to clean up service objects

21 Jul 2021

In big Rails projects there’s been a bit of a push to move things out to service objects. You might recognise these from their appearance in things like controllers:

CreateBook.new.call(book_params)

The CreateBook class itself might look like this:

class CreateBook
  include Dry::Monads[:do, :result]

  def call(params)
    book_params = yield validate(params)
    create_book(book_params)
  end
end

In order to access that instance call method from the controller, we must first create an instance of this CreateBook class. This makes our code slightly messy because we must always new before we call. The call here must be an instance method because we’ve included Dry::Monads methods within instances of this class, as per the best-practices when using that gem.

However, we can tidy things up here by using Ruby 2.7’s new triple-dot syntax. This syntax is another special type of argument, used in the same place you might use positional or keyword arguments. We can use triple-dots to pass arguments from a class method down to an instance method, like in this example:

class CreateBook
  include Dry::Monads[:do, :result]

  def self.call(...)
    new.call(...)
  end

  def call(params)
    book_params = yield validate(params)
    create_book(book_params)
  end
end

Anytime the call method on the class is called, a new CreateBook instance is created, then those arguments from the class-level call are passed to the instance-level call. By defining this new call method on the class itself, we can then change our controller to this:

CreateBook.call(book_params)

This makes our code out to the CreateBook class from wherever we’re using it slightly easier, while still allowing us to create & use instances of CreateBook if we wish. One particular case where that might come in handy is if you wanted to inject a dependency here:

CreateBook.new(book_repository: book_repo).call(book_params)

But that’s a story for another day.