Skip to content

Instantly share code, notes, and snippets.

@DamirSvrtan
Last active January 19, 2017 09:22
Show Gist options
  • Save DamirSvrtan/57a5004b686caeca617ecd5209539931 to your computer and use it in GitHub Desktop.
Save DamirSvrtan/57a5004b686caeca617ecd5209539931 to your computer and use it in GitHub Desktop.

Hey everyone,

For a long time I've been writing boilerplate classes that have basically the same structure of initialization:

class UserRegistrationForm  
  def initialize(access_code)
    @access_code = access_code
  end
  
  private
  
  attr_reader :access_code
end

Since I'm generating a whole bunch of similar classes like this, I've gotten tired of writing the same boilerplate code over and over again.

A year or two ago I found about a library called attr_extras that at first I didn't want to use since I didn't feel the real benefit of using it, but recently I said to myself that I should probably look it up again, and boy was I right.

The gem provides a couple of cool boilerplate cutdowns:

pattr_initialize

The same class that I've wrote above can be written like this:

class UserRegistrationForm
  pattr_initialize :user
end

The pattr_initialize method does two things:

  • creates an initializer that takes in the argument you've passed it, in this case, the user, and assigns it to an instance variable @user
  • creates a private reader for that argument.

Some of you know about Structs in Ruby, and that most of this can be implemented with Ruby's standard library:

Customer = Struct.new(:name, :address) do
  def greeting
    "Hello, my name is #{name.capitalize}!"
  end
end

Customer.new('John', 'Lemon Ave')

This is also a shorter way of creating an initializer and accessors, however I've got a couple of problems with the struct class:

  • it creates public readers and writers, while I only want readers. I'm a firm believer that accessors are a bad thing in almost any case, especially command/service classes, since you shouldn't alter the inner variables after initialization.
  • you can initialize the Customer class without any attributes -> this is valid: Customer.new. This leads to unwanted behavior that propagates errors down the line when you call a method on that class. So when you call Customer.new.greeting, it will throw an NoMethodError for capitalize only when it comes to greeting (a step too late)

method_object

I've also been writing most of my clases with one public method, named call, and sometimes I create a class method to easily stub it out in tests:

class UserRegistrationForm

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

  def initialize(access_code)
    @access_code = access_code
  end
  
  def call
    ...do stuf...
  end
  
  private
  
  attr_reader :access_code
end

I usually write the class methods for two reasons:

  • it's shorter to call the class method and it's easier to stub out during tests and looks nicer.
    • Stubbing out a instance method: allow_any_instance_of(Widget).to receive(:name).and_return("Wibble")
    • Stubbing out a class method allow(Widget).to receive(:name).and_return("Wibble")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment