Monthly Archives: June 2008

RubyDiff Grocks Metaprogramming

RubyDiff now understands some basic metaprogramming features.  So now if you had some hypothetical waffles…

Waffle A

class Waffle
end

Waffle B

class Waffle
  attr_reader   :type
  attr_accessor :awesome_factor
end

You can see that the waffle has some new accessors

./bin/ruby_diff waffle.rb waffle_plus.rb
c  Waffle
+    Waffle {accessor awesome_factor}
+    Waffle {reader type}

Pretty neat huh? Grab the new 0.1.5 gem and also checkout the changelog for more details.  So a proof of concept is in, next up, figure out how to make it extendable so that we can have our Rails, Merb, ActiveRecord, DataMapper, or LobsterShack metaprogramming.

Leave a comment

Filed under ruby, ruby_diff

Introducing RubyDiff

You’re probably working too hard. Do any of these sound familiar?

  • An entire file shows up in your diff, you think it’s because someone changed the tabbing from 4 spaces to 2 spaces, but you aren’t sure so now you’re pouring over all the code.
  • Your co-worker decided to rearrange all of the methods in awesome.rb, but you didn’t notice they also changed the parameter order on one of the methods.
  • There are a pile of small changes in large methods, and your diff didn’t show you what the method names were.
  • You have no idea what you have been working on for the last 2 hours, and can’t remember what to put in your check in message.

If they don’t, then humor me and pretend that they do, because I’m trying to motivate this tool I just wrote and I want you to be interested. What you secretly have been wanting is not a textual diff, but a logical diff, and that’s exactly what RubyDiff does (neat!). So grab it now:

sudo gem install ruby_diff

RubyDiff scans over your code and builds a logical model which it can use to tell you the really important high level information you need. Lets take a look at two versions of my enterprise pancake code:

Sample Version 1

module Food
  class Pancake
    def is_yummy?
      true
    end

    def << ingredient
      puts "adding #{ingredient}"
    end

    def self.build(ingredients)
      p = Pancake.new
      ingredients.each{|i| p << i}
      p
    end
  end
end

Sample Version 2

module Food
  class Pancake
    def << ingredient
      (@ingredients ||= []) << ingredient
    end

    def self.about
      puts "A delicious morning treat"
    end

    def is_yummy?
      true
    end
  end

  class PancakeFactory
    class << self
      def build(ingredients)
        p = Pancake.new
        ingredients.each{|i| p << i}
        p
      end
    end
  end
end

Now, the important thing here is not that waffles are better, but that a bunch of things have changed, and the question is… what was it?

ruby_diff test_a.rb test_b.rb
c  Food
c    Food::Pancake
-      Food::Pancake.build
c      Food::Pancake#<<
+      Food::Pancake.about
+    Food::PancakeFactory

RubyDiff shows us that the module Food has changed, within it the Pancake class changed. The class method Pancake.build was removed and a new PancakeFactory class was added. We also see that the append operator was modified and there is a new class method about. Great!

Best of all you can run ruby_diff over an entire repository of code, and it even supports git. For instance to see what changes you have made locally you can do something like:

ruby_diff --git HEAD --file ./
(your amazing changes here)

Or just to be meta, compare two revisions of RubyDiff:

ruby_diff --git HEAD~4 --git HEAD~3
c  CodeChange
c    CodeChange#initialize
c    CodeChange#to_s
c  CodeComparison
c    CodeComparison#changed
c  CodeObject
+    CodeObject#child_signatures
c  StructureProcessor
c    StructureProcessor#diff

I’ll post more about RubyDiff shortly, but the source is available now on rubyforge and you can branch it on github.  It’s still in it’s early stages, so I’d love to hear what you think.

8 Comments

Filed under ruby