iBrasten

My methods of calculating time are far superior to yours, in every way.

 

This is the blog of Brasten Sager, a software engineer, Mariners fan, guitarist, haphazard philosopher.

Autotest w/ ScriptingContainers

November 18, 2010 @ 05:37 PM

In my last blog post, I demonstrated a couple ways to improve JRuby startup performance by remove various startup steps (Bundler). In this post, I'll show you how to reduce your waiting time when running Autotest/RSpec in JRuby by instantiating ScriptingContainers and loading library code so it's ready to go when needed.

There are a couple other libraries that do similar things (Spork, for example), but I have been unable to get any of them to work [well].

A warning: whereas my last post showed The Way It Should Be (according to me), this post is a quick-n-dirty hack that just happens to work for me. Benchmarks are far from scientific, but they shouldn't surprise anyone either.

The Project

Same little application as the last post. There's only one actual spec, the rest are Pending. We're focusing more on the time it takes to start executing the specs, not finish them.

For both runs I'm including the initial execution of all specs, plus three individual executions triggered by modified spec files.

Autotest / rspec_rails

First, running an almost-standard autotest setup. The only thing different here is I hacked in the "time " prefix.

Initial run of all tests

time bundle exec /Users/brasten/.rvm/rubies/jruby-1.5.5/bin/jruby -rrubygems -S /Users/brasten/.rvm/gems/jruby-1.5.5/gems/rspec-core-2.1.0/bin/rspec --autotest '/Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/customers_controller_spec.rb' '/Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/inventory_items_controller_spec.rb' '/Users/brasten/Development/Projects/Scratch/warehouse/spec/helpers/customers_helper_spec.rb' '/Users/brasten/Development/Projects/Scratch/warehouse/spec/helpers/inventory_items_helper_spec.rb' '/Users/brasten/Development/Projects/Scratch/warehouse/spec/models/inventory_item_spec.rb' '/Users/brasten/Development/Projects/Scratch/warehouse/spec/requests/customers_spec.rb'
*****.

Pending:
  CustomersController add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/customers_controller_spec.rb
    # Not Yet Implemented
    # ./spec/controllers/customers_controller_spec.rb:4
  InventoryItemsController add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/inventory_items_controller_spec.rb
    # Not Yet Implemented
    # ./spec/controllers/inventory_items_controller_spec.rb:4
  CustomersHelper add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/helpers/customers_helper_spec.rb
    # Not Yet Implemented
    # ./spec/helpers/customers_helper_spec.rb:14
  InventoryItemsHelper add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/helpers/inventory_items_helper_spec.rb
    # Not Yet Implemented
    # ./spec/helpers/inventory_items_helper_spec.rb:14
  InventoryItem add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/models/inventory_item_spec.rb
    # Not Yet Implemented
    # ./spec/models/inventory_item_spec.rb:4

Finished in 0.208 seconds
6 examples, 0 failures, 5 pending

real     0m15.324s
user     0m21.998s
sys     0m1.223s


Running modified specs

time bundle exec /Users/brasten/.rvm/rubies/jruby-1.5.5/bin/jruby -rrubygems -S /Users/brasten/.rvm/gems/jruby-1.5.5/gems/rspec-core-2.1.0/bin/rspec --autotest '/Users/brasten/Development/Projects/Scratch/warehouse/spec/requests/customers_spec.rb'
.

Finished in 0.164 seconds
1 example, 0 failures

real     0m15.428s
user     0m22.083s
sys     0m1.258s
time bundle exec /Users/brasten/.rvm/rubies/jruby-1.5.5/bin/jruby -rrubygems -S /Users/brasten/.rvm/gems/jruby-1.5.5/gems/rspec-core-2.1.0/bin/rspec --autotest '/Users/brasten/Development/Projects/Scratch/warehouse/spec/models/inventory_item_spec.rb'
*

Pending:
  InventoryItem add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/models/inventory_item_spec.rb
    # Not Yet Implemented
    # ./spec/models/inventory_item_spec.rb:4

Finished in 0.002 seconds
1 example, 0 failures, 1 pending

real     0m15.415s
user     0m22.214s
sys     0m1.231s
time bundle exec /Users/brasten/.rvm/rubies/jruby-1.5.5/bin/jruby -rrubygems -S /Users/brasten/.rvm/gems/jruby-1.5.5/gems/rspec-core-2.1.0/bin/rspec --autotest '/Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/customers_controller_spec.rb'
*

Pending:
  CustomersController add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/customers_controller_spec.rb
    # Not Yet Implemented
    # ./spec/controllers/customers_controller_spec.rb:4

Finished in 0.001 seconds
1 example, 0 failures, 1 pending

real     0m15.908s
user     0m22.447s
sys     0m1.233s


Autotest w/ ScriptingContainers

To get this running, make the following changes:

  1. Modify (or create) the autotest/discover.rb file in your project so that it looks like the following:

                        
    Autotest.add_discovery { "jruby" }
    Autotest.add_discovery { "rails" }
    Autotest.add_discovery { "rspec2" }               
                        
                   

  2. Next, create autotest/jruby_rails_rspec2.rb with the following contents:

                        
    require 'java'
    require 'benchmark'
    require 'autotest/rails_rspec2'
    require 'pathname'
    
    # Sends RSpec to stdout and provided buffer
    #
    class ResultsCollector
     
      class << self
       
        # ... inexplicable syntax preference
        def into(buffer)
          self.new(buffer)
        end
      end
     
      def initialize(resultsBuffer)
        @buffer = resultsBuffer
      end
     
      def puts(line="")
        Kernel.puts(line)
        @buffer << "#{line}\n"
      end
      def print(line="")
        Kernel.print(line)
        @buffer << line
      end
     
    end
    
    class Autotest::JrubyRailsRspec2 < Autotest::RailsRspec2
      java_import 'org.jruby.embed.ScriptingContainer'
      java_import 'org.jruby.embed.LocalContextScope'
      java_import 'org.jruby.embed.LocalVariableBehavior'
    
      def initialize
        super
        setup_scripting_container
      end
     
      # Setup a ScriptingContainer so it's ready to go
      # the next time we need it
      #
      def setup_scripting_container
        trap('INT') do
          exit!(1)
        end
       
        tms = Benchmark.measure {
          puts "setting up scripting container..."
          @runtime = ScriptingContainer.new(LocalContextScope::SINGLETHREAD, LocalVariableBehavior::PERSISTENT)
    
          # ... requiring spec_helper here offloads even more startup time from our testing cycle, but for
          #     some projects (maybe most?) this may not work.  If spec_helper does anything that loads your
          #     application files, then you should comment out that line.
          #
          #     This should be a config option somewhere probably.
          #
          @runtime.runScriptlet(<<-SCRIPT)
            $: << 'spec'
            require 'config/application'
            RSpec::Core::Runner.disable_autorun!
          SCRIPT
        }
    
        puts "scripting container set up in #{format('%.3f', tms.real)} seconds"
        puts
      end
    
      def run_tests
        hook :run_command
    
        new_mtime = self.find_files_to_test
        return unless new_mtime
        self.last_mtime = new_mtime
    
        files_to_test = self.files_to_test
        file_list = normalize(files_to_test).keys.flatten.join(' ')
        return if file_list.empty?
    
        file_display = normalize(files_to_test).keys.flatten.map {|f|
                         " -> " + Pathname.new(f).relative_path_from(Pathname.new(File.expand_path('../..', __FILE__)))
                      }.join("\n")
    
        puts "Running:"
        puts file_display
    
        self.results = ""
         
        @runtime.put("outputStream", ResultsCollector.into(self.results))
    
        tms = Benchmark.measure {
          # ... we should probably respect config settings here... eventually.
          #
          @runtime.runScriptlet(<<-SCRIPT)     
            specs = %w(--color --autotest #{file_list})
            RSpec::Core::Runner.run(specs, outputStream, outputStream)
          SCRIPT
        }
       
        # ... ping any success/failure hooks first
        hook :ran_command
        handle_results(self.results)
    
        puts "tests completed in #{format('%.3f', tms.real)} seconds"
        puts
        puts
    
        # ... then clean up
        @runtime.terminate
        setup_scripting_container
      end
    
    end
                        
                   

And you're done. To the specs!

Initial run of all tests

setting up scripting container...
scripting container set up in 7.047 seconds

Running:
 -> spec/controllers/customers_controller_spec.rb
 -> spec/controllers/inventory_items_controller_spec.rb
 -> spec/helpers/customers_helper_spec.rb
 -> spec/helpers/inventory_items_helper_spec.rb
 -> spec/models/inventory_item_spec.rb
 -> spec/requests/customers_spec.rb
*****.

Pending:
  CustomersController add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/customers_controller_spec.rb
    # Not Yet Implemented
    # ./spec/controllers/customers_controller_spec.rb:4
  InventoryItemsController add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/inventory_items_controller_spec.rb
    # Not Yet Implemented
    # ./spec/controllers/inventory_items_controller_spec.rb:4
  CustomersHelper add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/helpers/customers_helper_spec.rb
    # Not Yet Implemented
    # ./spec/helpers/customers_helper_spec.rb:14
  InventoryItemsHelper add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/helpers/inventory_items_helper_spec.rb
    # Not Yet Implemented
    # ./spec/helpers/inventory_items_helper_spec.rb:14
  InventoryItem add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/models/inventory_item_spec.rb
    # Not Yet Implemented
    # ./spec/models/inventory_item_spec.rb:4

Finished in 0.29 seconds
6 examples, 0 failures, 5 pending
tests completed in 2.624 seconds

setting up scripting container...
scripting container set up in 5.168 seconds


Running modified specs

Running:
 -> spec/requests/customers_spec.rb
.

Finished in 0.156 seconds
1 example, 0 failures
tests completed in 1.952 seconds


setting up scripting container...
scripting container set up in 4.635 seconds

Running:
 -> spec/models/inventory_item_spec.rb
*

Pending:
  InventoryItem add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/models/inventory_item_spec.rb
    # Not Yet Implemented
    # ./spec/models/inventory_item_spec.rb:4

Finished in 0.001 seconds
1 example, 0 failures, 1 pending
tests completed in 1.785 seconds


setting up scripting container...
scripting container set up in 4.441 seconds

Running:
 -> spec/controllers/customers_controller_spec.rb
*

Pending:
  CustomersController add some examples to (or delete) /Users/brasten/Development/Projects/Scratch/warehouse/spec/controllers/customers_controller_spec.rb
    # Not Yet Implemented
    # ./spec/controllers/customers_controller_spec.rb:4

Finished in 0.001 seconds
1 example, 0 failures, 1 pending
tests completed in 1.825 seconds


setting up scripting container...
scripting container set up in 4.405 seconds


The Results

It's a little tough to see exactly what's happening here just from the text. Autotest is ensuring that a fresh ScriptingContainer is set up and ready to go shortly after a test run is completed. The initial run isn't going to see any significant improvements, of course, but executions triggered by detecting file modifications already have an environment set up to run against.

Anyway, it's been a long day so I'm sure I did not do a fantastic job explaining this, but give it a shot and see for yourself.

Update: This seems to leak memory. I'll be filing a JRuby bug momentarily. In the meantime you may need to restart Autotest every once in a while. Like I said at the beginning, hack.

Reducing JRuby Startup by Ditching Bundler

November 14, 2010 @ 08:11 PM

UPDATE 2:

Amazingly, I was unaware of the Crown RubyGem when I wrote this article. It clearly would simplify the task of creating your monolithic 'lib' directory called for toward the end of this post.

UPDATE:

The introduction of --standalone to Bundler (head) largely invalidates this post, and hurray for that! At the risk of repeating myself: Bundler is a fantastic dev-time tool, but should not be a run-time dependency. I haven't yet had the time to play with --standalone to see how it changes the steps specified in this post, but as soon as I do I'll post an update.

I'd like to think that the timing of --standalone's introduction into Bundler was influenced by this post. I have no evidence to back that up. :-)

Prelude

Has it really been over two years since I last blogged? I certainly *started* a lot of blog posts in that time but usually run out of steam before finishing them. Turns out that Twitter / Facebook is a lot more suited to my writing style.

Nevertheless, a post such as this simply won't fit on Twitter and won't reach many folks on Facebook, so here I am.

I Love JRuby, but...

This shouldn't surprise most people: JRuby kicks ass; JRuby startup sucks(1). This is not an indictment against JRuby, @headius or any of the other JRuby devs. JRuby is in a sense booting up a VM on top of a VM, so it's going to take a little time.

Turns out, though, you can cut JRuby startup times significantly for many applications -- over 50% on several projects I've worked on.

I Love Bundler, but...

I really do. But, Bundler is -- or should be -- a dev-time tool, not a runtime tool. This is not a new concept (http://tomayko.com/writings/require-rubygems-antipattern) and I'm a little surprised at how quickly the Ruby community in general has accepted 'require "bundler"' in their code even as they were starting to reject 'require "rubygems"'.

It also turns out to be a huge performance drain during startup on JRuby. I'm guessing this is because the JIT can't really optimize that startup code in time to make a difference.

In any case, Bundler -- at runtime -- has to go.

The Project and Initial Benchmark

Let's start with a standard Rails 3 application and run some performance tests(2).

To assist with benchmarking, I've modified the Rakefile and wrapped the standard header lines in individual benchmarked blocks. I've also created a task that executes Rails' "routes" task -- this is just to ensure that the Rails environment is loaded and hit once.

Rakefile

require 'benchmark'

Benchmark.bm(20) do |x|
  x.report("application:") {
    require File.expand_path('../config/application', __FILE__)
  }
  x.report("rake:") {
    require 'rake'
  }
 
  x.report("load_tasks:") {
    Warehouse::Application.load_tasks
  }
end

task :report do
 
  Benchmark.bm(20) do |x|
    x.report("Routes:") {
      Rake::Task[:routes].invoke()
    }
  end
  puts "Bundler  : #{defined?(Bundler) ? 'YES' : 'NO'}"

end

Because I will be changing them later, here is the original config/boot.rb and config/application.rb files for our Rails 3 application:

boot.rb

require 'rubygems'

# Set up gems listed in the Gemfile.
gemfile = File.expand_path('../../Gemfile', __FILE__)
begin
  ENV['BUNDLE_GEMFILE'] = gemfile
  require 'bundler'
  Bundler.setup
rescue Bundler::GemNotFound => e
  STDERR.puts e.message
  STDERR.puts "Try running `bundle install`."
  exit!
end if File.exist?(gemfile)

application.rb

require File.expand_path('../boot', __FILE__)
require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module Warehouse
  class Application < Rails::Application
    config.encoding = "utf-8"
    config.filter_parameters += [:password]
  end
end

Now let's run some benchmarks; first, for comparison, Ruby 1.9.2:

Ruby 1.9.2

brasten@SilverBook ~/D/P/S/warehouse> time ruby -I. -S rake report
(in /Users/brasten/Development/Projects/Scratch/warehouse)
                          user     system      total        real
application:          0.680000   0.180000   0.860000 (  0.870297)
rake:                 0.000000   0.000000   0.000000 (  0.000031)
load_tasks:           0.030000   0.010000   0.040000 (  0.045394)
                          user     system      total        real
Routes:               0.720000   0.130000   0.850000 (  0.853033)
Bundler  : YES
        2.03 real         1.62 user         0.37 sys

2 seconds, not horrible ... now JRuby:

JRuby 1.5.3

brasten@SilverBook ~/D/P/S/warehouse> time rake report
(in /Users/brasten/Development/Projects/Scratch/warehouse)
                          user     system      total        real
application:          4.851000   0.000000   4.851000 (  4.851000)
rake:                 0.000000   0.000000   0.000000 (  0.000000)
load_tasks:           0.410000   0.000000   0.410000 (  0.410000)
                          user     system      total        real
Routes:               2.118000   0.000000   2.118000 (  2.118000)
Bundler  : YES
       10.15 real        14.61 user         0.75 sys

Ouch, 10 seconds. So this is our starting point.

The Great Bundler Removal

Next, let's remove Bundler. The steps for removing Bundler from your Rails 3 application are as follows:

  1. Install your gems somewhere in your project.

    One way to do this is simply to run "bundle install --path=vendor/gems".

    For my projects, I have a Rake task that packages gems in "deps/development" and "deps/runtime" to handle gem groups. Following examples will assume an install path of deps/development (via "bundle install --path=deps/development").

  2. Create a file that adds your gem lib dirs to the load path. In my case, we'll assume a file called "deps/development.rb."

    deps/development.rb
    
    platform = (defined?(JRUBY_VERSION) ? 'jruby/1.8' : 'ruby/1.9.1')
    deps_dir = File.expand_path("deps/development/#{platform}/gems")
    
    Dir["#{deps_dir}/**/lib"].each { |d| $:.unshift(d) }
    
    

  3. Replace the default boot.rb code with a 'require "deps/development"':

    boot.rb
    
    require 'deps/development'
    
    

  4. Remove or comment the Bundler.require line in application.rb:

    application.rb
    
    require File.expand_path('../boot', __FILE__)
    require 'rails/all'
    
    # BUNDLER is commented out!
    # Bundler.require(:default, Rails.env) if defined?(Bundler)
    
    module Warehouse
      class Application < Rails::Application
        config.encoding = "utf-8"
        config.filter_parameters += [:password]
      end
    end
    
    

You're done! To the benchmarks:

JRuby 1.5.3

brasten@SilverBook ~/D/P/S/warehouse> time rake report
(in /Users/brasten/Development/Projects/Scratch/warehouse)
                          user     system      total        real
application:          1.696000   0.000000   1.696000 (  1.696000)
rake:                 0.000000   0.000000   0.000000 (  0.000000)
load_tasks:           0.471000   0.000000   0.471000 (  0.471000)
                          user     system      total        real
Routes:               2.744000   0.000000   2.744000 (  2.744000)
Bundler  : NO
        7.28 real        11.05 user         0.73 sys

Viola! Almost 30% reduction in startup time. This is essentially an empty project. In my experience, the more gems your application depends on the more dramatic a startup reduction you'll see. I was able to reduced the startup time of a recent large-ish project by over 50%.

For what it's worth, you get a barely noticeable improvement in 1.9.2 as well:

Ruby 1.9.2

brasten@SilverBook ~/D/P/S/warehouse> time ruby -I. -S rake report
(in /Users/brasten/Development/Projects/Scratch/warehouse)
                          user     system      total        real
application:          0.300000   0.180000   0.480000 (  0.513892)
rake:                 0.000000   0.000000   0.000000 (  0.000045)
load_tasks:           0.050000   0.020000   0.070000 (  0.078305)
                          user     system      total        real
Routes:               0.840000   0.210000   1.050000 (  1.075324)
Bundler  : NO
        1.92 real         1.38 user         0.46 sys

Even More Awesomeness

If your project requires even faster startup times, you can go even one step further.

This is not a trivial change, but it can accomplish some impressive startup time reductions.

Apparently, scanning the load_path for each required file is a relatively expensive operation in JRuby. One solution to this is simply to flatten your load path as much as possible.

Not all gems behave well this way, so it can take some trial and error. You should be able to combine the load paths from *most* of your dependencies and keep the misbehaving gems separate.

The steps are:

  1. Copy all files from each gem's "lib" directory into a single directory in your project. My directory is called "deps/development/rap" for irrelevant reasons.

  2. Create a bin/rake file. You'll use this to run rake:

    bin/rake
    
    #!/usr/bin/env ruby
    require 'deps/development'
    require 'rake'
    Rake.application.run
    
    

  3. Modify deps/development.rb to add your combined library directory to the load_path:

    deps/development.rb
    
    deps_dir = File.expand_path("deps/development/rap")
    $:.unshift(deps_dir)
    
    

And that's it. Benchmarks:

JRuby 1.5.3

brasten@SilverBook ~/D/P/S/warehouse> time bin/rake report
(in /Users/brasten/Development/Projects/Scratch/warehouse)
                          user     system      total        real
application:          1.328000   0.000000   1.328000 (  1.328000)
rake:                 0.000000   0.000000   0.000000 (  0.000000)
load_tasks:           0.173000   0.000000   0.173000 (  0.173000)
                          user     system      total        real
Routes:               2.376000   0.000000   2.376000 (  2.376000)
Bundler  : NO
        5.53 real         8.07 user         0.40 sys

Also interesting, the startup times for Ruby 1.9.2 are noticeably faster, too:

Ruby 1.9.2

brasten@SilverBook ~/D/P/S/warehouse> time ruby -I. bin/rake report
(in /Users/brasten/Development/Projects/Scratch/warehouse)
                          user     system      total        real
application:          0.210000   0.100000   0.310000 (  0.324459)
rake:                 0.000000   0.000000   0.000000 (  0.000013)
load_tasks:           0.030000   0.010000   0.040000 (  0.037808)
                          user     system      total        real
Routes:               0.750000   0.170000   0.920000 (  0.908417)
Bundler  : NO
        1.40 real         1.08 user         0.31 sys

Conclusion

So, that's that. You'll have to decide for yourself whether or not the extra effort is worth the reduced startup time.

Ideally gem authors would ensure that their gems only depended on files on the load_path and would properly namespace their code. The Bundler or some other tool could more easily automate packaging libraries into combined directories.

Anyway, something to think about.

  1. Where "sucks" is roughly equivalent to a compile and relaunch of a Java app. Not necessarily prohibitive, but as Ruby folk we've gotten used to instant gratification.
  2. Benchmarks were ran on JRuby 1.5.3 and Ruby 1.9.2. In general, I ran each benchmark ~10 times, tossed the first few and picked up the most common benchmark. This is entirely informal, but I did run tests and select examples in good faith. In any case, feel free to run the scenario yourself.

It shouldn't surprise anyone to learn that I haven't been able to work on Scruffy -- the Ruby-based SVG graphing engine -- in a very long time. And yet, it seems like people continue to download and use it more and more. Since my need for a graphing library is nonexistent for the foreseeable future, I don't think I'm the best person to continue working on Scruffy at this point. Software is best written by those who need it.

I do not intend on handing Scruffy to any random person, but if anyone out there thinks they can make a case for themselves you're welcome to contact me. In general, I'm keeping an eye out for someone who has been using Scruffy professionally for some time and has ideally already made significant code changes to Scruffy for their projects.

I'd also like to find someone who has a similar vision for Scruffy as I do. I never intended for Scruffy to be simply a Gruff replacement. Gruff is good enough for what it does. Today, most Scruffy usage involves rasterizing the SVG graph to an image. At the time this was a necessary evil, but I think we're rapidly approaching a time when Scruffy could serve straight SVG. This is, I believe, the ultimate goal for Scruffy and opens a bunch of new possibilities ( layout and theming via CSS, dynamic graph updates via JavaScript, etc ).

I'll be maintaining control of Scruffy for the foreseeable future as I'm not currently aware of anyone I'd trust to take it. But I'd love to hear from anyone who thinks they may be that developer.

A quick summary: Perl and Merb

April 13, 2008 @ 01:27 PM

There are quite a few little topics to catch up on since I've last posted, so I'm going to try and break them up into fewer, smaller posts. This post is just a general overview of what I've been up to over the last few months.

A brief detour into Perl-land

Just over six months ago I took a contract working in Perl. I had never worked in Perl and quite honestly had never intended on working in Perl. Still, the project sounded fun and I enjoy the opportunity to learn new [to me] things. It also provided the opportunity to work for/with a guy who is basically the Perl version of Tobias Lütke or Rick Olson. It's difficult to turn down an opportunity to learn a new technology from one of the best that technology has to offer.

Six months of Perl development later, I'm able to say that Perl doesn't suck as much as I'd always thought. The language itself is tricky, and this seems to have produced an awful lot of horrible Perl code "out there." I discovered, though, if you know how to write good Perl you can produce some decent looking code. With the project soon coming to an end, I'm saddened that I'll not be able to continue using my newly-acquired skills. Getting comfortable with Perl took me a little time, but I think I was just starting to [relatively] rock at it! If the right opportunity came along, I would not be opposed to working in Perl again.

My language-crush on Ruby did not disappear, however. The mental separation of working in Perl and playing in Ruby gave me a chance to play with some personal projects in my spare time -- free of the outside influences of a Ruby-based project. All of which leads me to my second topic...

Merb, Merb, the musical fruit...

This isn't a very elegant way of putting it, but: Merb. Kicks. Ass.

I had honestly begun to feel a bit disenchanted with Rails when I took the Perl project. I quickly discovered that I was not as eager to build a random Rails application in my spare time when I no longer had any financial incentive to work with it. I'm not complaining; I completely respect that DHH did not develop Rails to make Brasten Sager happy. Rails is what DHH needs it to be, and in my completely irrelevant opinion it's still the second-best framework for my purposes.

This post is more of a personal narrative than any kind of technical article, so I'll skip the bullet-point list of Reasons I Like Merb. Instead, here's the one-paragraph explanation. Every time I created a new Rails application I often ended up writing the same code (or copying from a previous project) to solve a whole list of minor annoyances. Merb largely solves all those things for me right out of the box. The Merb guys obviously write web applications the same way I do and they've written a framework that feels the way I want a framework to feel.

Of course, your mileage may vary.

More posts hopefully coming soon, stay tuned!