Neat trick to Stop Retyping Arguments



This content originally appeared on DEV Community and was authored by Jess Alejo

It’s a quiet Sunday morning, and I’m tinkering with my side project. As I look at the service object I’m writing, I catch myself seeing the same pattern… again.

# This is what I keep doing.
class UserService
  def self.create(name:, email:, permissions:)
    new(name: name, email: email, permissions: permissions).save
  end

  def initialize(name:, email:, permissions:)
    @name = name
    @email = email
    @permissions = permissions
  end

  def save
    puts "Saving user #{@name}..."
  end
end

See that self.create method? All I’m really doing is grabbing arguments and passing them straight to new. It works, but it feels clunky. Every time I add a new argument to initialize, say an age parameter, I have to update it in two places. It’s a small hassle today, but tomorrow it’s a classic recipe for bugs.

Somewhere along the way I read about Ruby’s argument forwarding syntax (...). I need to burn this into my brain because it makes life so much easier.

Here’s the rewrite for my future self:

# This is how I should be doing it.
class UserService
  def self.create(...)
    new(...).save # <-- So much better
  end

  def initialize(name:, email:, permissions:)
    @name = name
    @email = email
    @permissions = permissions
  end

  def save
    puts "Saving user #{@name}..."
  end
end

The self.create(...) says, “Grab whatever comes in,” and new(...) replies, “Pass it all along.” Clean. Simple. Done.

Why this is better

  1. It’s DRY. No more repeating name:, email:, permissions: twice. Less typing, less clutter.
  2. It’s resilient. If I add age: to initialize, self.create doesn’t need a single change. Argument forwarding takes care of it automatically.
  3. It covers everything. Positional args, keyword args, even blocks. The ... operator forwards it all.

And if I ever need to intercept just one argument while passing along the rest, say for logging, that’s easy too:

def self.create(name:, ...) # grab `name`, forward the rest
  puts "About to create a user named #{name}..."
  new(name: name, ...)
end

Memo to self

Use ... for forwarding arguments. It keeps code cleaner, avoids duplication, and saves future-me from unnecessary headaches.


This content originally appeared on DEV Community and was authored by Jess Alejo