DEV Community

Cover image for Neat trick to Stop Retyping Arguments
Jess Alejo
Jess Alejo

Posted on

Neat trick to Stop Retyping Arguments

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Memo to self

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

Top comments (0)