• Playing with Processing, Making Snow

    by Dan

    What is Processing?

    "Processing is an open source programming language and environment for people who want to program images, animation, and interactions."
    -Processing.org

    I wanted to play around with doing some visual programming and had played with Processing in the past. I recently had been reading about Ruby-Processing and wanted to give it a shot. First, I went to look for some Ruby-Processing tutorials, and I had recently heard about the presentation by Jeff Casimir about the 'Art of Code' (slides and code), using Ruby-Processing. I went through those examples and decided I wanted to modify it to display snowflakes in the spirit of winter. After a bit of searching I found a project that generated a Penrose snow flake using Ruby-Processing. I figured I could modify the programs to get a nice snow flake screen saver type app. The result is my app Processing-Snow, and is shown in the screen shot below.

    Processing-Snow

    Processing-Snow

    Playing around with Ruby-Processing is a lot of fun, I highly recommend spending a couple hours to make a tiny app. I built my Snow app in about an hour and a half. Then I spent a bit of time using Caliper to improve the metrics. For such a small project there wasn't a lot to improve, but it still helped me to do some refactoring. To get an idea of the code you can view Processing-Snow's Metrics.

    Feel free to fork Processing-Snow on GitHub and read about how to run it with in the projectss README.

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on December 23rd, 2009 by Dan in Hacking, Misc, Ruby.

  • Making Rack::Reloader work with Sinatra

    by Ben

    According to the Sinatra FAQ, source reloading was taken out of Sinatra in version 0.9.2 due to "excess complexity" (in my opinion, that's a great idea, because it's not a feature that needs to be in minimal a web framework like Sinatra). Also, according to the FAQ, Rack::Reloader (included in Rack) can be added to a Sinatra application to do source reloading, so I decided to try it out.

    Setting up Rack::Reloader is easy:

    require 'sinatra'
    require 'rack'
    
    configure :development do
      use Rack::Reloader
    end
    
    get "/hello" do
      "hi!"
    end
    
    $ ruby hello.rb
    == Sinatra/0.9.4 has taken the stage on 4567 for development with backup from Thin
    >> Thin web server (v1.2.4 codename Flaming Astroboy)
    >> Maximum connections set to 1024
    >> Listening on 0.0.0.0:4567, CTRL+C to stop
    [on another terminal]
    $ curl http://localhost:4567/hello
    hi!
    

    If you add another route, you can access it without restarting Sinatra:

    get "/goodbye" do
      "bye!"
    end
    
    $ curl http://localhost:4567/goodbye
    bye!
    

    But what happens when you change the contents of a route?

    get "/hello" do
      "greetings!"
    end
    
    $ curl http://localhost:4567/hello
    hi!
    

    You still get the old value! What is going on here?

    Rack::Reloader simply looks at all files that have been required and, if they have changed on disk, re-requires them. So each Sinatra route is re-evaluated when a reload happens.

    However, identical Sinatra routes do NOT override each other. Rather, the first route that is evaluated is used (more precisely, all routes appended to a list and the first matching one is used, so additional identical routes are never run).

    We can see this with a simple example:

    require 'sinatra'
    
    get "/foo" do
     "foo"
    end
    
    get "/foo" do
     "bar"
    end
    
    $ curl http://localhost:4567/foo
    foo   # The result is 'foo', not 'bar'
    

    Clearly, Rack::Reloader is not very useful if you can't change the contents of any route. The solution is to throw away the old routes when the file is reloaded using Sinatra::Application.reset!, like so:

    configure :development do
      Sinatra::Application.reset!
      use Rack::Reloader
    end
    
    $ curl http://localhost:4567/hello
    greetings!
    

    Success!

    A word of caution: you MUST call reset! very early in your file - before you add any middleware, do any other configuration, or add any routes.

    This method has worked well enough for our Sinatra application. However, code reloading is always tricky and is bound to occasionally produce some weird results. If you want to significantly reduce the chances for strange bugs (at the expense of code loading time), try Shotgun or Rerun. Happy reloading!

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on December 21st, 2009 by Ben in Hacking, Tips & Tricks, Tools and tagged , , .

  • Improving Code using Metric_fu

    by Dan

    Often, when people see code metrics they think, "that is interesting, I don't know what to do with it." I think metrics are great, but when you can really use them to improve your project's code, that makes them even more valuable. metric_fu provides a bunch of great metric information, which can be very useful. But if you don't know what parts of it are actionable it's merely interesting instead of useful.

    One thing when looking at code metrics to keep in mind is that a single metric may not be as interesting. If you look at a metric trends over time it might help give you more meaningful information. Showing this trending information is one of our goals with Caliper. Metrics can be your friend watching over the project and like having a second set of eyes on how the code is progressing, alerting you to problem areas before they get out of control. Working with code over time, it can be hard to keep everything in your head (I know I can't). As the size of the code base increases it can be difficult to keep track of all the places where duplication or complexity is building up in the code. Addressing the problem areas as they are revealed by code metrics can keep them from getting out of hand, making future additions to the code easier.

    I want to show how metrics can drive changes and improve the code base by working on a real project. I figured there was no better place to look than pointing metric_fu at our own devver.net website source and fixing up some of the most notable problem areas. We have had our backend code under metric_fu for awhile, but hadn't been following the metrics on our Merb code. This, along with some spiked features that ended up turning into Caliper, led to some areas getting a little out of control.

    Flay Score before cleanup

    When going through metric_fu the first thing I wanted to start to work on was making the code a bit more DRY. The team and I were starting to notice a bit more duplication in the code than we liked. I brought up the Flay results for code duplication and found that four databases models shared some of the same methods.

    Flay highlighted the duplication. Since we are planning on making some changes to how we handle timestamps soon, it seemed like a good place to start cleaning up. Below are the methods that existed in all four models. A third method 'update_time' existed in two of the four models.

     def self.pad_num(number, max_digits = 15)
        "%%0%di" % max_digits % number.to_i
      end
    
      def get_time
          Time.at(self.time.to_i)
      end
    

    Nearly all of our DB tables store time in a way that can be sorted with SimpleDB queries. We wanted to change our time to be stored as UTC in the ISO 8601 format. Before changing to the ISO format, it was easy to pull these methods into a helper module and include it in all the database models.

    module TimeHelper
    
      module ClassMethods
        def pad_num(number, max_digits = 15)
          "%%0%di" % max_digits % number.to_i
        end
      end
    
      def get_time
          Time.at(self.time.to_i)
      end
    
      def update_time
        self.time = self.class.pad_num(Time.now.to_i)
      end
    
    end
    

    Besides reducing the duplication across the DB models, it also made it much easier to include another time method update_time, which was in two of the DB models. This consolidated all the DB time logic into one file, so changing the time format to UTC ISO 8601 will be a snap. While this is a trivial example of a obvious refactoring it is easy to see how helper methods can often end up duplicated across classes. Flay can come in really handy at pointing out duplication that over time that can occur.

    Flog gives a score showing how complex the measured code is. The higher the score the greater the complexity. The more complex code is the harder it is to read and it likely contains higher defect density. After removing some duplication from the DB models I found our worst database model based on Flog scores was our MetricsData model. It included an incredibly bad high flog score of 149 for a single method.

    File Total score Methods Average score Highest score
    /lib/sdb/metrics_data.rb 327 12 27 149

    The method in question was extract_data_from_yaml, and after a little refactoring it was easy to make extract_data_from_yaml drop from a score of 149 to a series of smaller methods with the largest score being extract_flog_data! (33.6). The method was doing too much work and was frequently being changed. The method was extracting the data from 6 different metric tools and creating summary of the data.

    The method went from a sprawling 42 lines of code to a cleaner and smaller method of 10 lines and a collection of helper methods that look something like the below code:

      def self.extract_data_from_yaml(yml_metrics_data)
        metrics_data = Hash.new {|hash, key| hash[key] = {}}
        extract_flog_data!(metrics_data, yml_metrics_data)
        extract_flay_data!(metrics_data, yml_metrics_data)
        extract_reek_data!(metrics_data, yml_metrics_data)
        extract_roodi_data!(metrics_data, yml_metrics_data)
        extract_saikuro_data!(metrics_data, yml_metrics_data)
        extract_churn_data!(metrics_data, yml_metrics_data)
        metrics_data
      end
    
      def self.extract_flog_data!(metrics_data, yml_metrics_data)
        metrics_data[:flog][:description] = 'measures code complexity'
        metrics_data[:flog]["average method score"] = Devver::Maybe(yml_metrics_data)[:flog][:average].value(N_A)
        metrics_data[:flog]["total score"]   = Devver::Maybe(yml_metrics_data)[:flog][:total].value(N_A)
        metrics_data[:flog]["worst file"] = Devver::Maybe(yml_metrics_data)[:flog][:pages].first[:path].fmap {|x| Pathname.new(x)}.value(N_A)
      end
    

    Churn gives you an idea of files that might be in need of a refactoring. Often if a file is changing a lot it means that the code is doing too much, and would be more stable and reliable if broken up into smaller components. Looking through our churn results, it looks like we might need another layout to accommodate some of the different styles on the site. Another thing that jumps out is that both the TestStats and Caliper controller have fairly high churn. The Caliper controller has been growing fairly large as it has been doing double duty for user facing features and admin features, which should be split up. TestStats is admin controller code that also has been growing in size and should be split up into more isolated cases.

    churn results

    Churn gave me an idea of where might be worth focusing my effort. Diving in to the other metrics made it clear that the Caliper controller needed some attention.

    The Flog, Reek, and Roodi Scores for Caliper Controller:

    File Total score Methods Average score Highest score
    /app/controllers/caliper.rb 214 14 15 42

    reek before cleanup

    Roodi Report
    app/controllers/caliper.rb:34 - Method name "index" has a cyclomatic complexity is 14.  It should be 8 or less.
    app/controllers/caliper.rb:38 - Rescue block should not be empty.
    app/controllers/caliper.rb:51 - Rescue block should not be empty.
    app/controllers/caliper.rb:77 - Rescue block should not be empty.
    app/controllers/caliper.rb:113 - Rescue block should not be empty.
    app/controllers/caliper.rb:149 - Rescue block should not be empty.
    app/controllers/caliper.rb:34 - Method name "index" has 36 lines.  It should have 20 or less.
    
    Found 7 errors.
    

    Roodi and Reek both tell you about design and readability problems in your code. The screenshot of our Reek 'code smells' in the Caliper controller should show how it had gotten out of hand. The code smells filled an entire browser page! Roodi similarly had many complaints about the Caliper controller. Flog was also showing the file was getting a bit more complex than it should be. After picking off some of the worst Roodi and Reek complaints and splitting up methods with high Flog scores, the code had become easily readable and understandable at a glance. In fact I nearly cut the Reek complaints in half for the controller.

    Reek after cleanup

    Refactoring one controller, which had been quickly hacked together and growing out of control, brought it from a dizzying 203 LOC to 138 LOC. The metrics drove me to refactor long methods (52 LOC => 3 methods the largest being 23 LOC), rename unclear variable names (s => stat, p => project), move some helpers methods out of the controller into the helper class where they belong. Yes, all these refactorings and good code designs can be done without metrics, but it can be easy to overlook bad code smells when they start small, metrics can give you an early warning that a section of code is becoming unmanageable and likely prone to higher defect rates. The smaller file was a huge improvement in terms of cyclomatic complexity, LOC, code duplication, and more importantly, readability.

    Obviously I think code metrics are cool, and that your projects can be improved by paying attention to them as part of the development lifecycle. I wrote about metric_fu so that anyone can try these metrics out on their projects. I think metric_fu is awesome, and my interest in Ruby tools is part of what drove us to build Caliper, which is really the easiest way try out metrics for your project. Currently, you can think of it as hosted metric_fu, but we are hoping to go even further and make the metrics clearly actionable to users.

    In the end, yep, this is a bit of a plug for a product I helped build, but it is really because I think code metrics can be a great tool to help anyone with their development. So submit your repo in and give Caliper hosted Ruby metrics a shot. We are trying to make metrics more actionable and useful for all Ruby developers out, so we would love to here from you with any ideas about how to improve Caliper, please contact us.

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on October 27th, 2009 by Dan in Development, Devver, Hacking, Misc, Ruby, Testing, Tools and tagged , , , , , .

  • Lone Star Ruby Conf 2009 Wrapup Review

    by Dan

    I recently went to the Lone Star Ruby Conference (LSRC), in Austin TX. It was great to be able to put faces to many people I had interacted with in the Ruby community via blogs and twitter. I also got to meet Matz and briefly talk with him, which was cool. Meeting someone who created a language which is such a large part of your day to day life is just an interesting experience. I enjoyed LSRC, and just wanted to give a quick summary of some of the talks that I saw and enjoyed. This is by no means full coverage of the event, but hopefully sharing my experience with others is worth something. If you are interested in seeing any of the talks keep an eye out for Confreaks, they taped the event and many of the talks should be coming online soon.

    Dave Thomas
    Dave was the first speaker for LSRC, and it was a great way to kick off the event. Dave gave a talk about Ruby not being perfect and that is why he likes it. I have heard Dave speak before, and I always enjoy his talks. It isn't like you learn anything specific about Ruby development, but you learn about the Ruby community. Actually, Dave would say we are a collection of Ruby communities, and that having a collection of communities is a good thing. It was also interesting to hear Dave speak about the entire Zed, "Rails is a Ghetto" incident. Sometimes when you are angrily ranting around online, it is easy to forget that there are real people attached to things. Feelings can get hurt, and while Dave agrees there is some valid points in the post, I think it shows that it probably isn't a good way to go about fixing them. Dave really loves Ruby and the weird things you can do with the language and it shows.

    Glenn Vanderburg, Programming Intuition
    Glenn talked about phyical emotions tied to code, such as a sense of touch or smell. The talk generally just evoked memories of Paul Graham's "Hackers and Painters" in my head, in fact Glenn talked about PG during his talk. The best programmers talk about code as if they can see it. The talk explored ways to feel the code and react to it. It tried to promote the idea that it is OK to just have a gut reaction that some code is a bad way to do things, because we should sense the code. Glenn also played a video showing Bobby McFerrin teaching the audience the Pentatonic scale, which I really enjoyed.

    James Edward Gray II, Module Magic
    James visited Japan recently and went to a Ruby conference, and he really enjoyed it. About half his talk was why Japan is awesome... He then found little ways to tie this back into his talk about Ruby and Modules. It covered some interesting topics like load order that many people just don't know enough about, but use every day. Examples of the differences between include and extend. Modules are terrific at limiting scope, limit the scope of scary magic and keep it hidden away. I enjoyed talking with James a good amount through out the conference. I had never met him before LSRC, but I used to practice Ruby working on Ruby Quiz which he ran for a long time.

    James has his slides are up, Module Magic

    Fernand Galiana, R-House
    Fernand gave a really cool and demo heavy talk about home automation. He has a web front end that lets him interact with all his technology. His house tweets most of the events that it runs. The web interface has a iPhone front in, so he can, on the go, change the temperature or turn off lights. I have always been a real home automation geek. When I was growing up, my dad loved playing with an X-10 system that we had in the house. I am really interested in playing with some of this stuff when I have my own place, mostly looking at ways I could use it to cut waste on my energy usage.

    Mike Subelsky, Ruby for Startups: Battle Scars and Lessons Learned
    * You Ain't Gonna Need It (YAGNI), don't worry about being super scaling at the beginning...
    * Early days focus on learning more data about what your building and what your customers want concentrate on the first 80% solution.
    * Don't over build, over design, or over engineer.
    * Eventually plan to move everything out of your web request, build it so that it will be easy to do in the future, but it isn't worth doing at first. (delayed job, EM, etc)
    * Make careful use of concurrency, prefer processes communicating via messages (SQS etc...) If you are doing threading in your software EM is your friend.
    * Avoid touching your RDBMS when you are storing not critical data:
    - Storing large text blogs in S3, message across SQS, tons of logging SDB
    * Don't test all the time at the beginning, it gets in the way of exploration... Things that is mission critical maybe should be BDD as it will be the most stable and least likely to change part of your code

    Mike posted his slides on his blog, Ruby for Startups.

    Jeremy Hinegardner, Playing nice with others. -- Tools for mixed language environments

    Jeremy wanted to show how easy it is to use some code to make it easy to work with a system that uses multiple languages. He brought up that most projects in the room utilize more than one language. That it will be more common as systems grow in complexity. He looked at a lot of queues, key value stores, and cache-like layers that can be talked to by a variety of language. He then showed some code that would quickly demonstrate how easy it was to work with some of these tools. Extra points because he talked about Beanstalkd, which I personally think is really cool. I think nearly everyone is starting to look at work queues, messaging systems, and non standard databases for their project and this was a good overview of options that are out there.

    Yukihiro Matsumoto (Matz), Keynote and Q&A
    Matz gave a talk about why we, as a community, love Ruby. In this talk there weren't really any takeaways that were specifically about Ruby code but more about the community and why Ruby is fun. He spent a good amount of time talking about Quality Without A Name, QWAN. More interesting than the talk was the Q&A session. I thought the most interesting question was why Ruby isn't on Git yet. He said the teams doesn't have time to convert all the tools they use from SVN to git. He also mentioned that the git project tracking SVN is very close to the SVN master and is a good way to work on the Ruby code.

    Evan Light, TDD: More than just "testing"
    Evan first covered that the tools we as a community keep getting excited about aren't really what matters. What matters is TDD technique. After discussing why tools aren't as important for awhile, Evan began live coding with the audience. Something I thought was pretty impressive as it would be difficult to do. It made for a weird pair programming exercise with the entire audience trying to drive. Which sometimes worked well and sometimes lead to conflicting ideas / discussion (which made for interesting debate). It was over all a really interesting session, but it is hard to pull out specific tidbits of wisdom from the talk.

    Jake Scruggs, What's the Right Level of Testing?
    I have known of Jake for awhile from his work on the excellent Metric Fu gem. Jake explored what the right level of testing for a project is, from his experience on his last nine projects over the years. He explored what worked, what didn't and what sometimes works but only depending on the people and the project. I think it comes to this conclusion: what works for one project won't work for all projects. Having some testing and getting the team on a similar testing goal will make things much better. He also stressed the importance of metrics along with testing (really? From the metric-fu guy? Haha). If testing is old and broken, causing team backlash, low morale, and gridlock, it might be better to lessen the testing burden or throw away difficult to maintain tests. Getting rid of them and getting them out of the way, might be worth more than the value the tests were providing. In general he isn't big into view testing, he likes to avoid slow testing. He likes to have a small 'smoke screen' of integration tests, to help verify the system is all working together. In the end, what is the right level of testing for a project? The answer: what level of assurance does the given project really need? In a start-up you probably don't need a huge level of assurance, speed matters and market feedback matter more. If your building parts for a rocket or medical devices it is entirely different.

    I enjoyed this talk quite a bit, and it inspired me to fix our broken metric_fu setup and start tracking on projects metrics again. Jake also wrote a good roundup of LSRC

    Corey Donohoe @atmos, think simple
    Corey gave interesting quick little thoughts and ideas about how to stay productive, happy, learn more, do more, fail less, and keep things simple and interesting... Honestly with something like 120+ slides, I can't even begin to summarize this talk. I checked around and couldn't find his slides online, but they couldn't really do the talk justice anyways. Keep your eyes peeled for the video as it was a fun talk, which I enjoyed. Until then here is a post he made about heading to LSRC.

    Joseph Wilk, Outside-in development with Cucumber
    Cucumber is something I keep hearing and reading about, but haven't really gotten a chance to play with it myself. Joseph's talk was a good split between a quick intro to Cucumber, and diving in deeper to actually show testing examples and how it worked. From the talk it sounded to me like Cucumber was mostly a DSL to talk between the customer and the developer/tester. I don't know if that is how others would describe it. I thought Cucumber was an odd mix of English and and Ruby, but it helps effectively tell a story. Since returning form LSRC, I have started working on my first Cucumber test.

    Yehuda Katz, Bundler
    This was just a lightening talk about Bundler, which I had read about briefly online. Seeing the work that was done for this blew me away. I can honestly say I hope this takes over the Ruby world. We have been dealing with so many problems related to gems at Devver, and if Bundler becomes a standard, it would make the Ruby community a better place. I am really excited about this so go check out the Bundler project now.

    Rich Kilmer, Encoding Domains
    The final keynote of the event was about encoding domains. I didn't really know what to expect going into this talk, but I was happily surprised. Rich talked about really encapsulating a domain in Ruby and then being able to make the entire programming logic much simpler. He gave compelling examples of working with knowledge workers in the field and just writing code with them to express their domain of knowledge in Ruby code. Live coding with the domain with experts he jokingly called "syntax driven development" - you write code with them until it doesn't raise syntax errors. Rich spoke energetically and keep a tiring audience paying attention to his stories about projects he has worked on through out the years. Just hearing about people who have created successful projects who have been working with Ruby in the industry this long is interesting. I thought it had great little pieces of knowledge that were shared during the talk, but again this was a talk where it was to hard to pull out tiny bits of information, so I recommend looking for the video when it is released.

    Final Thoughts
    LSRC was a good time besides hearing all the speakers. In fact like most conferences some of the best knowledge sharing happened during breaks, at dinner, and in the evenings. It also gave me a chance to get to know some of the community better than just faceless Twitter avatars. It was fun to talk with Ruby people about things that had nothing to do with Ruby. I also am interested in possibly living in Austin at some point in my life so it was great to check it out a bit. Friday night after the conference I went out with a large group of Rubyists to Ruby's BBQ, which was delicious. We ate outside with good food, good conversation, and live music playing next door. As we were leaving someone pointed out that the guitarist playing next door was Jimmy Vaughn, brother of the even more famous Stevie Ray Vaughan. We went over to listen to the show and have a beer, which quickly changed into political speeches and cheers. Suddenly I realized we were at a libertarian political rally. I never expected to end up at a Texan political rally with a bunch of Rubyists, but I had a good time.

    Hopefully the next Ruby conference I attend with be as enjoyable as LSRC was, congrats to everyone who helped put the conference together and all those that attended the event and made it worth while.

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on September 3rd, 2009 by Dan in Hacking, Misc, Ruby, Tips & Tricks.

  • Unit Testing Filesystem Interaction

    by Ben

    Like most Rubyists, I write unit tests to verify the non-trivial parts of my code. I also try to use mocks and stubs to stub out interactions with systems external to my code, like network services.

    For the most part, this works fine. But I've always struggled to find a good way to test interaction with the filesystem (which can often be non-trivial and therefore should be tested). On the one hand, the filesystem could be considered "external" and mocked out. But on the other hand, the filesystem is accessible when the tests run. In this way, the filesystem is sort of like a local database - it could be mocked out, but it doesn't have to be, and there are tradeoffs to both approaches.

    Over the past year or so, I've tried out a few approaches for testing interactions with the filesystem, each of which I'll explain below. Since none of the approaches met my needs, Avdi and I built a new testing library, which I'll introduce below.

    Mocking the file system.

    Sometimes, it is simplest to just mock the interaction with the filesystem. This works well for single calls to methods like File#read or File#exist? (these examples use Mocha):

    File.stubs(:read).returns("file contents")
    File.stubs(:exist?).returns(true)
    

    However, this approach breaks down when you want to test more complex code, which, of course, is the code you're more likely to want to test thoroughly. For instance, imagine trying to set up mocks/stubs for the following method (which atomically rewrites the contents of a file):

    require 'tempfile'
    
    class Rewriter
    
      def rewrite_file!(target_path)
        backup_path = target_path + '.bak'
        FileUtils.mv(target_path, backup_path)
        Tempfile.open(File.basename(target_path)) do |outfile|
          File.open(backup_path) do |infile|
            infile.each_line do |line|
              outfile.write(yield(line))
            end
          end
          outfile.close
          FileUtils.cp(outfile.path, target_path)
        end
      rescue Exception
        if File.exist?(backup_path)
          FileUtils.mv(backup_path, target_path)
        end
        raise
      end
    
    end
    

    Now imagine setting up those same mocks/stubs for each of the five or so tests you'd want to test that method. It gets messy.

    Even more importantly, mocking/stubbing out methods ties your tests to a specific implementation. For instance, if you use the above stub (File.stubs(:read).returns("file contents")) in your test and then refactor your implementation to use, say, File.readlines, you'll have to update your tests. No good.

    MockFS

    MockFS is a library that mocks out the entire filesystem. It allows you write test code like this:

    require 'test/unit'
    require 'mockfs'
    
    class TestMoveLog < Test::Unit::TestCase
    
      def test_move_log
        # Set MockFS to use the mock file system
        MockFS.mock = true
    
        # Fill certain directories
        MockFS.fill_path '/var/log/httpd/'
        MockFS.fill_path '/home/francis/logs/'
    
        # Create the access log
        MockFS.file.open( '/var/log/httpd/access_log', File::CREAT ) do |f|
          f.puts "line 1 of the access log"
        end
    
        # Run the method under test
        move_log
    
        # Test that it was moved, along with its contents
        assert( MockFS.file.exist?( '/home/francis/logs/access_log' ) )
        assert( !MockFS.file.exist?( '/var/log/httpd/access_log' ) )
        contents = MockFS.file.open( '/home/francis/logs/access_log' ) do |f|
          f.gets( nil )
        end
        assert_equal( "line 1 of the access log\n", contents )
      end
    end
    

    Although I suspect MockFS would be a great fit for some projects, I ended up running into issues.

    First of all, it depends on a library (extensions) that can have strange monkey-patching conflicts with other libraries. For example, compare this:

    require 'faker'
    puts [].respond_to?(:shuffle) # true
    

    to this:

    require 'extensions/all'
    require 'faker'
    puts [].respond_to?(:shuffle) # false
    

    Secondly, as you'll notice in the above example, using MockFS requires you to use methods like MockFS.file.exist? instead of just File.exist?. This works fine if you're only testing your own code. However, if your code calls any libraries that use filesystem methods, MockFS won't work.

    (Note: There is a way to mock out the default filesystem methods, but it's experimental. From the MockFS documentation:

    "Reading the testing example above, you may be struck by one thing: Using MockFS requires you to remember to reference it everywhere, making calls such as MockFS.file_utils.mv instead of just FileUtils.mv. As another option, you can use File, FileUtils, and Dir directly, and then in your tests, substitute them by including mockfs/override.rb. I'd recommend using these with caution; substituting these low-level classes can have unpredictable results. ")

    All that said, MockFS is probably your best option if you're only testing your code and you want to mock out files that you can't actually interact with - for instance, if you need to test that a method reads/writes a file in /etc (although for the sake of testability, it's generally good to avoid hardcoding fully-qualified paths in your code).

    FakeFS is another library that uses this approach. I haven't used it personally, but it looks quite nice.

    Creating temp files and directories (with Construct)

    Besides mocking the filesystem, another option is to have tests interact with actual files and directories on disk. The advantages are that the test code can be simpler to write and you don't have to use any special filesystem methods.

    Of course, as always, you want the test itself to contain all the relevant setup and teardown - you don't want your tests to depend upon some set of files that have no explicit connection to the test itself (or create files that aren't cleaned up).

    To make this easy, we created a new library called Construct. Construct makes test setup simple by providing helpers to create temporary files and directories. It takes care of the cleanup by automatically deleting the directories and files that are created within the test. And because it creates regular files and directories, you can use plain old Ruby filesystem methods in your code and tests.

    To install Construct, simply run:

    # gem install devver-construct --source http://gems.github.com
    

    Using Construct, you can write code like this:

    require 'construct'
    
    class ExampleTest < Test::Unit::TestCase
      include Construct::Helpers
    
      def test_example
        within_construct do |construct|
          construct.directory 'alice/rabbithole' do |dir|
            dir.file 'white_rabbit.txt', "I'm late!"
            assert_equal "I'm late!", File.read('white_rabbit.txt')
          end
        end
      end
    
    end
    

    Let's look at each line in more detail.

        within_construct do |construct|
    

    When you call within_construct, a temporary directory is created. All files and directories are, by default, created within that temporary directory and the temporary directory is always deleted before within_construct completes.

    The block argument (construct) is a Pathname object with some additional methods (#directory and #file, which I'll explain below). You can use this object to get the path to the temporary directory created by Construct and easily create files and directories.

    Note that, by default, the working directory is changed to the temp dir within the block provided to within_construct.

          construct.directory 'alice/rabbithole' do |dir|
    

    Here we are using the construct object to create a new directory within the temp directory. As you can see, you can create nested directories like alice/rabbithole in one step. The block argument (dir) is again a Pathname object with the same added functionality noted above.

    Just like before, the working directory is changed to the newly created directory (in this case, alice/rabbithole) within the block.

            dir.file 'white_rabbit.txt', "I'm late!"
    

    Here we use the dir object to create a file. In this case, the file will be empty. However, it's easy to provide file contents using either an optional parameter or the return value of the supplied block:

    within_construct do |construct|
      construct.file('foo.txt','Here is some content')
      construct.file('bar.txt') do
      <<-EOS
      The block will return this string, which will be used as the content.
      EOS
      end
    end
    

    As a more real-world example, here's how you could use Construct to start testing the #rewrite_file! method we looked at before:

    require 'test/unit'
    require 'construct'
    require 'shoulda'
    
    class RewriterTest < Test::Unit::TestCase
      include Construct::Helpers
    
      context "#rewrite_file!" do
    
        should "alter each line in file" do
          within_construct do |c|
            c.file('bar/foo.txt',"a\nb\nc\n")
            Rewriter.new.rewrite_file!('bar/foo.txt') do |line|
              line.upcase
            end
            assert_equal "A\nB\nC\n", File.read('bar/foo.txt')
          end
        end
    
        should "not alter file if exception is raised" do
          within_construct do |c|
            c.file('foo.txt', "1\n2\nX\n")
            assert_raises ArgumentError do
              Rewriter.new.rewrite_file!('foo.txt') do |line|
                Integer(line)*2
              end
            end
            assert_equal "1\n2\nX\n", File.read('foo.txt')
          end
        end
    
      end
    
    end
    

    You can learn more at the project page (both the README and the tests have more examples).

    (As an aside, since Construct changes the working directory, it doesn't play nicely with ruby-debug. Specifically, if you place a breakpoint within a block, you'll see the message "No sourcefile available for test/unit/foo_test.rb" and you won't be able to view the source. If anyone knows an easy way to make Dir.chdir work with ruby-debug, I'd very much appreciate some help!)

    Conclusion

    We've been moving our filesystem tests over to using Construct and so far have found it to be very useful. How do you test interactions with the filesystem? Do you use one of the above approaches, or something else? Or do you skip testing the filesystem altogether?

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on August 25th, 2009 by Ben in Hacking, Testing and tagged , , .

  • Single-file Sinatra apps with specs baked-in

    by Avdi

    It's so easy to create little single-file apps in Sinatra that it almost seems a shame to start a second file just for tests.  The other day Dan and I decided to see if we could create a Sinatra app with everything - including the tests - baked right in.  Here's what we came up with.

    The code switches modes on the name of the executable used to run the file. If we run it with the spec command, we get a test run:

    $ spec -fs sinatra-tests-baked-in.rb

    Example App
    - should serve a greeting
    - should serve content as text/plain

    Finished in 0.007221 seconds

    2 examples, 0 failures

    Otherwise, if we call it as a Ruby program, it runs the Sinatra server as we would expect:

    $ ruby sinatra-tests-baked-in.rb
    == Sinatra/0.9.1.1 has taken the stage on 4567 for development with backup from Thin
    >> Thin web server (v1.0.0 codename That's What She Said)
    >> Maximum connections set to 1024
    >> Listening on 0.0.0.0:4567, CTRL+C to stop

    And there you have it: a true single-file application, specs and all.

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on May 13th, 2009 by Avdi in Development, Hacking, Ruby and tagged , , .

  • Managing Amazon EC2 with your iPhone

    by Dan

    I wanted a quick way when out and about to easily manage our AWS EC2 instances while out and about. It hasn't happened often, but occasionally I am away from the computer and I need to reboot the instances. Perhaps I remember our developer cluster isn't being used and want to shut it down to save some money.

    I didn't find anything simple and free with a quick Google search, so in a about an hour I wrote a nice little Sinatra app that will let me view our instances, shutdown, or reboot any specific instance or all of them. The tiny framework actually turned out to be even more useful as I now have options that let us tail error logs, reboot Apache, reboot mongrel clusters, or execute any common system administration task.

    I won't be going into detail on how to build a iPhone webapp using Sinatra and iUI, because Ben already created an excellent post detailing all of those steps. In fact I used his old project as the template when I created this project. I can't begin to explain how amazingly simple it is to build an iPhone webapp using Sinatra, so if you have been thinking of a quick project I highly recommend it.

    Here are some screen shots showing the final app. (screenshot courtesy of iPhoney):

    ec2 manager home view

    ec2 manager home view.

    ec2 manager describe view

    ec2 manager describe instances view.

    ec2 manager instance view.

    ec2 manager instance view.

    This app uses the Amazon EC2 API Tools to do all the heavy lifting. So this app assumes that you already have the tools installed and working on the machine you want this app to run on. This normally involves installing the tools and setting up some environment variables like EC2_HOME, so make sure you can run ec2-describe-instances from the machine. After that you should just have to change EC2_HOME in the Sinatra app to match the path where you installed the EC2 tools.

    Let me know if you have any issues, it is quick and dirty, but I have already found it useful.

    To run the app:
    cmd> ruby -rubygems ./ec2_manager.rb

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on March 5th, 2009 by Dan in Amazon Web Services, Development, Hacking, Ruby, Tips & Tricks, Tools and tagged , , , , .

  • Using Ruby to Send Update Emails to Our Mentors

    by Ben

    At Devver.net, we send out weekly email updates to an awesome set of mentors. We do this for a number of reasons. First and foremost, we get valuable feedback and advice from our mentors on a variety of issues. But it's also an easy and effective way to keep us on track and even maximize our chances of success. As Paul Graham says in How Not To Die (he was talking directly to YC teams, but you'll get the idea):

    "For us the main indication of impending doom is when we don't hear from you. When we haven't heard from, or about, a startup for a couple months, that's a bad sign.

    ...

    Maybe if you can arrange that we keep hearing from you, you won't die.

    That may not be so naive as it sounds. ... [The] mere constraint of staying in regular contact with us will push you to make things happen, because otherwise you'll be embarrassed to tell us that you haven't done anything new since the last time we talked."

    Foodzie started emailing their mentors early in the summer. We actually borrowed (stole) their email format and best practices.

    One thing we've tried to not do is send out a completely generic email to all our mentors. Depending on the content and the interaction we've had with a specific mentor, we'll adjust his email accordingly. We begin each email with their name and send it directly to them (in other words, we don't put a huge list of addresses in the To, CC, or BCC fields). We do this because we can tailor it and it helps elicit individual responses from each mentor (it's easier to ignore a question if it's sent to a group).

    But, of course, sometimes the emails to a few mentors can be identical. In this case, my not-so-well-kept secret is that I just use a simple Ruby script to send out a duplicate email that appears to be hand-crafted (or at least copied and pasted).

    I've been told that Outlook can perform this functionality easily, but I don't know of any way to do this within Gmail. If there is, let me know so I can feel a little silly (in any case, the Ruby code was fun to write).

    To run this code, you'll need to install the highline gem. You'll also need to add your Gmail account, recipients, subject message, etc. Finally, you'll want to put your message inside a separate file within project directory. That way, you can easily modify, spellcheck, and format to your heart's content before sending.

    You can get the entire gmailr source code (all two files!) at Github. Please use this script for good, not evil - no one likes a spammer. Enjoy!

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on January 20th, 2009 by Ben in Development, Devver, Hacking, Ruby.

  • Installing and running git-svn on Mac OSX 10.4 Tiger

    by Dan

    I am shocked at how much time it took me to get git-svn working on my mac. I use MacPorts, which works well most of the time. Sometimes it has problems which makes me really wish for apt-get on OS X. apt-get normally has worked much nicer for me, but can have its issues too. I even occasionally wish for Windows and a simple install.exe which works 95% of the time out of the box. Really I wish Apple would throw some engineer support to MacPorts and make the service rock solid.

    I have had git installed and working for awhile, but preparing to switch our main project from Subversion (svn) to git, I thought I should start using git-svn. It seemed smart to use git-svn for awhile to get used to git, before a full switch so I could fall back on svn in a crunch. I decided to start using git-svn, but the first run of the git-svn command caused this error, and I had no idea how much of my night was about to be wasted...

    Can't locate SVN/Core.pm in @INC

    Searching led to a couple of webpages, but the most useful was getting git to work on OS X Tiger. It had a quick fix that might work or the long route fix. For some lucky people it is just a path problem. I checked if that was the case for me, by the following command

    PATH=/opt/local/bin:$PATH; git svn

    unfortunately for me I got the same error, OK I need to reinstall SVN with additional bindings...

    > sudo port uninstall -f subversion-perlbindings
    > sudo port install -f subversion-perlbindings

    leading to this error:

    ---> Building serf with target all
    Error: Target org.macports.build returned: shell command " cd "/opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_ports_www_serf/work/serf-0.2.0" && make all " returned error 2
    Command output: /opt/local/share/apr-1/build/libtool --silent --mode=compile /usr/bin/gcc-4.0 -O2 -I/opt/local/include -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -no-cpp-precomp -I. -I/opt/local/include/apr-1 -I/opt/local/include/apr-1 -c -o buckets/aggregate_buckets.lo buckets/aggregate_buckets.c && touch buckets/aggregate_buckets.lo
    libtool: compile: unable to infer tagged configuration
    libtool: compile: specify a tag with `--tag'
    make: *** [buckets/aggregate_buckets.lo] Error 1

    I spent some time searching and eventually I find the solution to the serf error. I couldn't read the blog because it wasn't in English, but I could read enough to solve my MacPorts serf install problem. I followed these few lines from the blog

    cd /opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_ports_www_serf/work/serf-0.2.0
    $ sudo ./configure --prefix=/opt/local --with-apr=/opt/local --with-apr-util=/opt/local
    $ sudo make all
    $ sudo port install serf

    Awesome, I have serf. Now what is next? Back to building svn with perl bindings, that works. Now, let's build git again since svn with perl bindings is finally installed.

    sudo port install git-core +svn

    Which fails because of p5-svn-simple

    dyld: lazy symbol binding failed: Symbol not found: _Perl_Gthr_key_ptr
    Referenced from: /usr/local/lib/libsvn_swig_perl-1.0.dylib
    Expected in: flat namespace

    dyld: Symbol not found: _Perl_Gthr_key_ptr
    Referenced from: /usr/local/lib/libsvn_swig_perl-1.0.dylib
    Expected in: flat namespace

    Error: Status 1 encountered during processing.

    OK, I need to get p5-svn-simple working. Searching leads to this thread MacPort errors related to git. Here you will find the amazingly useful comment by Orestis:

    "As mentioned move your libsvn_swig_perl* out of /usr/local/lib AND out of /usr/lib into temporary folders.

    Uninstall and reinstall subversion-perlbindings

    Install p5-svn-simple (and git-core +svn which is what lead me here)

    Move the libsvn_swig_perl files back in /usr/lib and /usr/local/lib (or else git svn won't work). 

    > cd /usr/local
    > mv ./lib/libsvn_swig_perl* ./bak/
    > sudo port install p5-svn-simple

    Sweet that works now

    > sudo port install git-core +svn
    > cd /usr/local
    > mv ./bak/libsvn_swig_perl* ./lib/

    Finally I try to run git-svn, only to see the same ERROR I had from the very beginning! I am about to lose it but decide that I should try the quick fix again to see if it is the path issue...

    PATH=/opt/local/bin:$PATH; git svn

    It works! Alright now it is just a path problem. So I open up my .bash_profile, and notice I already have that path included

    # Setting the path for MacPorts.
    export PATH=/opt/local/bin:/opt/local/sbin:/Applications/MzScheme\ v352/bin:$PATH

    But I also have an additional path added from when I originally built git from source, and it looks like I was running my old broken version of git-svn. So I just had to remove this one line from my .bash_profile

    export PATH=~/projects/git-1.5.6.1:$PATH

    and hours later and with a ton of frustration I have a fully functioning git-svn.

    Now that it is working, you can move on to learning git-svn in 5 minutes.

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on December 9th, 2008 by Dan in Development, Hacking, Misc, Tips & Tricks, Tools.

  • Revisiting additional Ruby Tools

    by Dan

    I have heard about new Ruby tools since I did my Ruby Tools Roundup. I am always interested in tools that can help improve our code, so I had to check some of them out. Similar to my last tools post, I will be trying out a tool and writing my general impressions along with the basic usage.

    reek


    I have to start with reek, since it has been the most requested and searched on our site since I originally wrote about tools. reek will help identify code smells, allowing you to fix up your code. Instead of looking at cyclomatic complexity or other metrics, reek looks at patterns to warn you about bad code. Reek currently detects a few code smells (Long Method, Large Class, Feature Envy, Uncommunicative Name, Long Parameter List, Utility Function, Nested Iterators, Control Couple, Duplication) but more are on the way.

    I think this project is useful but would need to be more customized before a nightly run would yield very useful results. The biggest problem I have is the signal to noise ratio seemed pretty high. Reek was warning me about "long methods" that were only 7 statements long, which just isn't something I am concerned about. The warnings on duplicate methods calls can be useful, after running reek on a few files I found a couple places where duplicate method calls were wasting time. Many of the other smells are interesting like 'Feature Envy', and 'Utility Function'. I will need to use reek more before I know if these smells are good indicators or often false positives.

    Below reek finds a utility function next_tick which is definitely a helper function that actually exists in two of our files, which probably should be moved into a helper mixin.

    def next_tick
        if(EM.reactor_running?)
          EM.next_tick do
            yield
          end
        else
          yield
        end
    end

    I am really looking forward to see how the tool progresses. If the project allows for a simple config customization to change the thresholds as well as ignore some files/smells, this could become a very useful tool to help keep a team maintain a high expectation of code quality. It would be useful to get nightly reports about any code that might not meet expectations, so a quick group code review could decide if it is an exception (which can be quickly added to the config) or if the code should be refactored and cleaned up.

    dmayer$ sudo gem install reek
    dmayer$ reek ./lib/client/client.rb
    [Utility Function] Client#next_tick doesn't depend on instance state
    [Long Method] Client#process_done has approx 7 statements
    [Duplication] Client#process_ready calls @buffer.create_reload_msg more than once
    [Long Method] Client#process_ready has approx 10 statements
    [Duplication] Client#report_system_message calls result.msg more than once
    [Feature Envy] Client#report_system_message refers to result more than self
    [Duplication] Client#send_tests calls Time.now more than once
    [Long Method] Client#send_tests has approx 24 statements
    [Feature Envy] Client#send_tests refers to tests more than self
    #check a whole directory
    dmayer$ reek ./lib/client/*

    Towelie


    Towelie helps discover duplication in Ruby code, it will help keep your code DRY. It doesn't have a nice interface at the moment and it is pretty young code. That being said, it can still be a really useful tool to help guide refactoring and code cleanup.

    ~/projects dmayer$ git clone git://github.com/gilesbowkett/towelie.git
    dmayer$ cd ~/projects/devver/
    dmayer$ irb -r ~/projects/towelie/lib/towelie.rb
    irb(main):001:0> @t = Towelie.new
    => #, @model=#>
    irb(main):002:0> @t.parse "lib/client"
    (string):24: warning: useless use of a variable in void context
    => nil
    irb(main):003:0> puts @t.duplicates
    found in:
    lib/client/test_unit_reporter.rb
    lib/client/rspec_reporter.rb
    
    def nl
    report_nl
    end
    
    ... 2 more dupes in the reporters ...
    
    found in:
    lib/client/test_unit_reporter.rb
    lib/client/rspec_reporter.rb
    
    def report(str)
    print(str.to_s)
    end
    
    found in:
    lib/client/sync_client.rb
    lib/client/rev_sync_client.rb
    lib/client/rev_client.rb
    lib/client/client.rb
    
    def quit
    send(@buffer.create_quit_msg)
    end
    
    found in:
    lib/client/sync_client.rb
    lib/client/rev_sync_client.rb
    lib/client/rev_client.rb
    lib/client/client.rb
    
    def send_quit
    send(@buffer.create_quit_msg)
    end
    
    => nil
    irb(main):004:0>

    There are currently many duplications because we are maintaining two clients while deciding what route to eventually take. We have also moved a lot of our shared client code into a mixin, and Towelie finds some methods that really should be moved there as well such as the methods "quit" and "send_quit", which is currently duped in 4 files. Towelie also points to the fact that we should refactor our reporters because they both duplicate code.

    I have always been annoyed with copied and pasted functions accidentally working its way in code, this could be a useful nightly run to keep a team DRY. Sometimes two team members implement the same functionality without even knowing a solution already exists in the code base. If you want to go a bit more in depth, check out Giles Bowkett's (creator of Towelie) How to use Towelie

    Flay


    Flay is another great tool by Ryan Davis who also works on Heckle and Flog which I covered in the past. Flay, like Towelie, helps keep your code DRY, it detects exact and similar code throughout a project. It seems to be more powerful than Towelie, as seen in this Towelie and Flay comparison. My biggest complaint is the current release has some pretty basic output that you see below. The output I got from Towelie was immediately more recognizable and useful, while Flay currently requires you to dig in a bit deeper on your own into its suggestions. An improvement is already being worked on and a verbose output mode should be in the release soon. Once better output is included I think Flay will be immediately useful out of the box even with small amounts of developer effort.

    I like that Flay has weight system, which should make it easy to set some threshold to ignore, high level weights are more likely to be worth your time and attention. One piece of code Flay tagged with a low weight was code that rescued and logged different errors thrown, which while similar actually served a purpose.

    rescue Errno::EISDIR => ed
          @stderr.puts "Error: #{ed.message}" if @stderr
          @stderr.puts "You can't pass a directory to devver only test files. Quitting." if @stderr
          send_quit
        rescue LoadError => le
          @stderr.puts "Error: #{le.message}" if @stderr
          @stderr.puts "Not all of the files can be found. Quitting." if @stderr
          send_quit
        rescue SyntaxError, NameError => se
          @stderr.puts "Error: #{se.message}" if @stderr
          @stderr.puts "This file doesn't appear to be a valid Ruby file. Quitting." if @stderr
          send_quit
    end

    Digging into the Flay results turned up some duplicate code that Towelie had missed. Since Towelie also caught a method that was duped in 4 client files that Flay missed (I was expecting Towelie's results to be a subset of what Flay found), perhaps there is room for both of the tools and learning to work with both a little bit is worth the time. After a little bit of work perhaps one of the projects will become a clearly better option. Until then I will be following both of these projects.

    sudo gem install flay
    dmayer$ flay lib/client/*.rb
    Processing lib/client/client.rb...
    Processing lib/client/mod_client.rb...
    ...
    Processing lib/client/syncer.rb...
    
    Matches found in :defn (mass = 84)
    lib/client/mod_client.rb:86
    lib/client/mod_rev_client.rb:124
    
    Matches found in :block (mass = 57)
    lib/client/client.rb:201
    lib/client/client.rb:205
    lib/client/client.rb:209
    
    ... 6 more results ...
    
    Matches found in :if (mass = 34)
    lib/client/mod_client.rb:63
    lib/client/mod_rev_client.rb:111
    
    Matches found in :defn (mass = 32)
    lib/client/mod_rev_client.rb:36
    lib/client/mod_rev_client.rb:50


    Conclusions


    That should cover it for this Ruby tools post, but I am really enjoying checking out the tools showing up in the Ruby scene. So as always let me know if I missed something, or if there is a tool you would like to see a full write up on. After some of the tools mature a little bit I will have to revisit a few of the tools which are currently in the early stages. I hope the Ruby tools scene keeps as active as it has been lately because there are some interesting projects being worked on.

    honorable mentions (things I didn't think really needed a full write up)


    • metric-fu a great gem to give quick access to a bunch of tools and metrics about your code (RCov, Saikuro, Flog, SCM Churn, and Rails Stats)
    • CruiseControl.rb when you start using all of these tools, continuous integration starts to become more important (or doing nightly runs). CruiseControl.rb is dead simple continuous integration.
    • Simian another code duplication tool, which is mentioned in 3 tools for drying your Ruby code (free for OSS, $99 for a license)
    • Ruby Tidy a tool for cleaning up HTML (I haven't used this in Ruby, but loved the Java version in my Java days)
    • Watir is an open-source library for automating web browsers. It allows you to write tests that are easy to read and maintain. It is simple and flexible.
    • Autotest, if you haven't heard of autotest, check it out, continuously run your tests every time you save a file in your project.
    • Rufus a tool that checks if code you are about to load is safe. Allows you to look for custom patterns that you don't want to run.
    • I wrote about a couple benchmarking tools last time and here is a great article / tutorial on Ruby benchmarking

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on December 3rd, 2008 by Dan in Development, Hacking, Misc, Ruby, Tools.