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:
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 callCustomer.new.greeting
, it will throw an NoMethodError for capitalize only when it comes to greeting (a step too late)
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")
- Stubbing out a instance method: