TakesMacro
attr_extras is a great gem that lets you remove most of the boilerplate needed for creating different types of initializers in Ruby.
This gem contains a reimplementation of pattr_initialize from attr_extras that is much faster. If you're using attr_extras, but the only feature of it you're using is pattr_initialize then this gem is for you.
This gem calls the method takes to avoid confusing, but the API is exactly the same.
Benchmark
Run bundle exec ruby benchmarks/takes_macro_vs_attr_extras.rb to see how much faster this gem is. The output from doing that on my machine is:
$ bundle exec ruby benchmarks/takes_macro_vs_attr_extras.rb
Warming up --------------------------------------
attr_extras 15.793k i/100ms
takes_macro 91.596k i/100ms
hand written initializer
71.351k i/100ms
Calculating -------------------------------------
attr_extras 169.353k (± 3.9%) i/s - 852.822k in 5.043680s
takes_macro 1.209M (± 4.5%) i/s - 6.045M in 5.011814s
hand written initializer
875.721k (± 5.8%) i/s - 4.424M in 5.071249s
Comparison:
takes_macro: 1208748.5 i/s
hand written initializer: 875721.1 i/s - 1.38x slower
attr_extras: 169352.8 i/s - 7.14x slower
The initializer generated by takes is faster than the hand written version because the takes version uses an options hash, and the hand written version uses keyword arguments, which are a bit slower. You can see the exact code for the benchmark here.
How it works
This gem expands this
class A
takes [:foo!]
endInto this
class A
def initialize(options)
@foo = options.fetch(:foo)
end
attr_reader :foo
private :foo
endIt does that by looking at the arguments to takes and from that building a String of Ruby code that it will then class_eval. That means calling takes is literally as fast as writing the initializer by hand. Nothing fancy happens when you call A.new(...).
Possible arguments to takes
You can call takes in many different ways depending on the style of initializer you want. Here are the different styles and what they expand into:
Positional args
takes :foo, :bar
def initialize(foo, bar)
@foo = foo
@bar = bar
endRequired keyword args
takes [:foo!, :bar!]
def initialize(foo:, bar:)
@foo = foo
@bar = bar
endOptional keyword args
takes [:foo, :bar]
def initialize(foo: nil, bar: nil)
@foo = foo
@bar = bar
endMixed positional, required and optional keyword args
takes :foo, [:bar!, :baz]
def initialize(foo, bar:, baz: nil)
@foo = foo
@bar = bar
@baz = baz
endNote: Each instance variable set in the initializer also gets a private attr_reader, but that was left out of the examples for clarity.
Installation
Add this line to your application's Gemfile:
gem "takes_macro"And then execute:
$ bundle
Or install it yourself as:
$ gem install takes_macro
Usage
If you want to use takes in all your classes add this to your app:
require "takes_macro"
TakesMacro.monkey_patch_objectIf you're in a Rails app I recommend adding this to config/application.rb so you're sure to have it in all your classes.
That will include the TakesMacro module, which defines the takes method, on Object.
If you don't like monkey patching Object you can still do this:
require "takes_macro"
class A
include TakesMacro
takes [:foo!, :bar!, :baz!]
endDevelopment
After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.