Implementing Ruby’s inject method
I spent a little time tinkering with some of the “newer” RSpec 2 features while re-implementing the inject method that belongs to the Enumerable module. It is a fun and simple exercise because it reinforces the understanding of blocks and the sometimes confusing nature of the inject method. Here it is.
require 'rubygems' require 'rspec' module Enumerable def inject(*args, &block) if(args.size == 2) inject_binary_operation(args[0], args[1], self) elsif(args.size == 1 && !block_given?) inject_binary_operation(self.first, args[0], self.drop(1)) else the_memo = args[0] || self.first self.each { |item| the_memo = yield(the_memo, item) if block_given? } the_memo end end alias :reduce :inject private def inject_binary_operation(the_memo, operator, enum) raise TypeError.new("#{operator} is not a symbol") unless operator.is_a?(Symbol) enum.each { |item| the_memo = the_memo.send(operator, item) } the_memo end end shared_examples_for "an Enumerable object" do describe "inject" do describe "given there is a specified memo value" do it('should return the newly injected "memo" value if using a block') do enumerable.inject([3,4,5]) { |memo, enum_item| memo.include?(enum_item) ? memo : memo << enum_item }.should == [3, 4, 5, 1, 2] end describe "when it is a binary operation" do it('should return a TypeError because the single argument is not a Symbol') do lambda { enumerable.inject(2) }.should raise_error(TypeError) end it('should add the values') do enumerable.inject(2, :+).should == 8 end it('should multiply the values') do enumerable.inject(2, :*).should == 12 end it('should subtract the values') do enumerable.inject(2, :-).should == -4 end end end describe "given there isn't a specified memo value" do it('should return the newly injected "memo" value if using a block') do enumerable.inject { |memo, enum_item| memo > enum_item ? memo : enum_item }.should == 3 end describe "when it is a binary operation" do it('should add the values') do enumerable.inject(:+).should == 6 end it('should multiply the values') do enumerable.inject(:*).should == 6 end it('should subtract the values') do enumerable.inject(:-).should == -4 end end end end end describe Range do it_should_behave_like "an Enumerable object" do let(:enumerable) { (1..3) } end end describe Array do it_should_behave_like "an Enumerable object" do let(:enumerable) { [1,2,3] } end end
I especially like the RSpec documentation format because it provides such great readability. When appropriate the “shared_examples_for” is also nice to keep your test suite DRY.
$ rspec injectable.rb --format documentation Range it should behave like an Enumerable object inject given there is a specified memo value should return the newly injected "memo" value if using a block when it is a binary operation should return a TypeError because the single argument is not a Symbol should add the values should multiply the values should subtract the values given there isn't a specified memo value should return the newly injected "memo" value if using a block when it is a binary operation should add the values should multiply the values should subtract the values Array it should behave like an Enumerable object inject given there is a specified memo value should return the newly injected "memo" value if using a block when it is a binary operation should return a TypeError because the single argument is not a Symbol should add the values should multiply the values should subtract the values given there isn't a specified memo value should return the newly injected "memo" value if using a block when it is a binary operation should add the values should multiply the values should subtract the values Finished in 0.007 seconds 18 examples, 0 failures
And of course the gist…
https://gist.github.com/1096650
Follow me on Twitter
My github repository
My LinkedIn Profile