[rspec-users] fluent chaining drying

Justin Ko
Thu, 26 May 2011 04:35:20 -0000

On Wed, May 25, 2011 at 9:30 PM, Gustavo Delfino <gdelfino at umich.edu>
wrote:

> Thank you Justin & David.
>
> I was able to to use Justin solution. I also tried hard to understand and
> use David''s solution but I could not.
>
> Now I am just curious. What is &HaveXXXHelpers.on_column? A reference
to a
> class method?
>
> Regards,
>
> Gustavo Delfino
>
> On May 25, 2011, at 2:04 PM, Justin Ko wrote:
>
> >
> >
> > On Wed, May 25, 2011 at 11:20 AM, David Chelimsky <dchelimsky at
gmail.com>
> wrote:
> > On May 25, 2011, at 10:07 AM, Gustavo Delfino wrote:
> >
> > > Hello all. Thanks to subjects, custom matchers and fluent
chaining I
> was able to greatly simplify my spec. But now I want to DRY my custom
> matchers.
> > >
> > > I have two custom matchers: ''have_text'' and
''have_number'' and both
> contain exactly the same chains ''on_column'' and
''on_columns''.
> > >
> > > Is there a way to DRY this up?
> > >
> > >
> > > module CustomColumnsMatchers
> > >
> > >  RSpec::Matchers.define(:have_text) do |expected_string|
> > >    chain(:on_column) do |colnumber|
> > >      @index = colnumber - 1
> > >      @width = 1
> > >    end
> > >    chain(:on_columns) do |range|
> > >      a = range.to_a
> > >      @index = Range.new( a[0]-1, a[-1]-1 )
> > >      @width = a[-1] - a[0] + 1
> > >    end
> > >    match do |line|
> > >      line[@index] == expected_string.ljust(@width)
> > >    end
> > >  end
> > >
> > >  RSpec::Matchers.define(:have_number) do |expected_number_string|
> > >    chain(:on_column) do |colnumber|
> > >      @index = colnumber - 1
> > >      @width = 1
> > >    end
> > >    chain(:on_columns) do |range|
> > >      a = range.to_a
> > >      @index = Range.new( a[0]-1, a[-1]-1 )
> > >      @width = a[-1] - a[0] + 1
> > >    end
> > >    match do |line|
> > >      line[@index] ==
expected_number_string.rjust(@width,''0'')
> > >    end
> > >  end
> > >
> > > end
> > >
> > >
> > > Regards,
> > >
> > > Gustavo Delfino
> >
> > I''d probably do something like:
> >
> >  RSpec::Matchers.define(:have_text) do |expected_string|
> >   chain(:on_column, &HaveXXXHelpers.on_column)
> >   chain(:on_columns, &HaveXXXHelpers.on_columns)
> >   match do |line|
> >     line[@index] == expected_string.ljust(@width)
> >   end
> >  end
> >
> > HTH,
> > David
> > _______________________________________________
> > rspec-users mailing list
> > rspec-users at rubyforge.org
> > http://rubyforge.org/mailman/listinfo/rspec-users
> >
> > Ahh David beat me to the lambda approach! Here is another way:
> >
> >
> > module CustomColumnsMatchers
> >
> >  RSpec::Matchers.define(:have_text) do |expected_string|
> >    extend ChainMethods
> >
> >    match do |line|
> >      line[@index] == expected_string.ljust(@width)
> >    end
> >  end
> >
> >  RSpec::Matchers.define(:have_number) do |expected_number_string|
> >    extend ChainMethods
> >
> >    match do |line|
> >      line[@index] ==
expected_number_string.rjust(@width,''0'')
> >    end
> >  end
> >
> >  module ChainMethods
> >    def self.extended(matcher)
> >      matcher.instance_eval do
> >       chain(:on_column) do |colnumber|
> >         @index = colnumber - 1
> >         @width = 1
> >       end
> >       chain(:on_columns) do |range|
> >          a = range.to_a
> >          @index = Range.new( a[0]-1, a[-1]-1 )
> >          @width = a[-1] - a[0] + 1
> >       end
> >     end
> >   end
> > end
> >
> > end
> > _______________________________________________
> > rspec-users mailing list
> > rspec-users at rubyforge.org
> > http://rubyforge.org/mailman/listinfo/rspec-users
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>

Actually, the lambda approach won''t work, because you''re
carrying state
(@index & @width) that is outside of scope in the matcher. This is what it
would look like though:

module CustomColumnsMatchers

 RSpec::Matchers.define(:have_text) do |expected_string|
   chain(:on_column, &ChainHelpers.on_column)
   chain(:on_columns, &ChainHelpers.on_columns)

   match do |line|
     line[@index] == expected_string.ljust(@width)
   end
 end

 RSpec::Matchers.define(:have_number) do |expected_number_string|
   chain(:on_column, &ChainHelpers.on_column)
   chain(:on_columns, &ChainHelpers.on_columns)

   match do |line|
     line[@index] == expected_number_string.rjust(@width,''0'')
   end
 end

 module ChainHelpers
   def self.on_column
     lambda do |colnumber|
        @index = colnumber - 1
        @width = 1
      end
    end

    def self.on_columns
      lambda do |range|
         a = range.to_a
         @index = Range.new( a[0]-1, a[-1]-1 )
         @width = a[-1] - a[0] + 1
      end
    end
  end

end


You could keep all of the state in the ChainHelpers module:

module CustomColumnsMatchers

 RSpec::Matchers.define(:have_text) do |expected_string|
   chain(:on_column, &ChainHelpers.on_column)
   chain(:on_columns, &ChainHelpers.on_columns)

   match do |line|
     line[ChainHelpers.index] == expected_string.ljust(ChainHelpers.width)
   end
 end

 RSpec::Matchers.define(:have_number) do |expected_number_string|
   chain(:on_column, &ChainHelpers.on_column)
   chain(:on_columns, &ChainHelpers.on_columns)

   match do |line|
     line[ChainHelpers.index]
=expected_number_string.rjust(ChainHelpers.width,''0'')
   end
 end

 module ChainHelpers
   class << self
     attr_accessor :index, :width
   end

   def self.on_column
     lambda do |colnumber|
        self.index = colnumber - 1
        self.width = 1
      end
    end

    def self.on_columns
      lambda do |range|
         a = range.to_a
         self.index = Range.new( a[0]-1, a[-1]-1 )
         self.width = a[-1] - a[0] + 1
      end
    end
  end

end


That is kinda dirty and ugly - I''d stick with the "extend"
solution.
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
<http://rubyforge.org/pipermail/rspec-users/attachments/20110525/61806a4f/attachment-0001.html>