• Living on the Edge (of Rails) - 1st week of the year edition

    Yup, it's time for your weekly dose of the changes on edge Rails, more or less covered in the latest Rails Envy podcast. Using edge Rails is neither arcane nor terrifying, and hopefully weekly reports like these will allow you to take control of your own release schedule with your Rails apps.

    This week's report covers changes from 31 Dec 2007 to the day the podcast was recorded (6 Jan 2007).

    Caching changes

    Looks like most of the changes from the 2.1 caching branch have been
    merged into the trunk. Some key points:

    1. memcache-client has been vendored (included in Rails directly). MemCacheStore works out of the box in Rails now, no need to install the memcache-client gem!
    2. The caching code has been refactored and moved into ActiveSupport (ActiveSupport::Cache::*).
    3. Added ActiveRecord::Base.cache_key to make it easier to cache Active Records in combination with the ActiveSupport cache libraries introduced in this changeset.
    4. Fragment cache keys are now by default prefixed with 'views/'.
    5. Deprecation: ActionController::Base.fragment_cache_store is now ActionController::Base.cache_store

    Fragment caching now works in RJS and Builder templates

    Yup, you couldn't do fragment caching in non-erb views before - now you can.

    Freezing Rails now automatically updates your Rails app

    If you're using edge Rails and use the rails:freeze:edge rake task, you probably usually forget to run (or maybe you're not even aware of) rake rails:update to update your Rails app with the latest config/, scripts/ and javascript files from the version of Rails you just froze to. On edge Rails, the rake rails:freeze:edge task runs the rails:update task for you. +1 for convenience!

    I prefer to use Piston so I'm gonna have to keep remembering to run my rake rails:update now and then!

    Check out the related changeset.

    Optimizations

    Only 1 optimization in the past week worth talking about: the ActiveRecord::Base#exists? method is faster. It now uses ActiveRecord::Base#select_all instead of a more expensive ActiveRecord::Base#find that unnecessarily instantiates AR objects. (Check out the related changeset.)

    Bug fixes

  • self.plug :with => 'Rails Hackfest'

    Yes, it's a plug for myself and it's shameless:

    Dec 2007 Rails Hackfest


    Whatever Zed Shaw may say about Rails its community, Rails has done a huge part in making web development fun again for jaded web developers (first-time web developers probably won't be able to tell the difference), and more importantly, raising the profile of Ruby more than any other Ruby project/library/framework/tool ever did (and I'm sure we're all constantly rolling our eyeballs at the new web frameworks that're so terribly familiarly Rails-like). Hell, I bet at least 90% of Rubyists now wouldn't even be Rubyists if they hadn't come across the web framework that could 2 or 3 years ago. So yes I'd still be contributing to Rails so that it can make my life as a web developer easier, thank you.

    I think I had about 18 accepted patches in December 2007 Rails Hackfest (a few of the patches were not attributed properly in the Hackfest due to technical issues with the Rails Hackfest site).

    Probably the only significant patches were:

    • Allowing a proc to be set for ActionController::Base.asset_host (changeset 8421).
    • to_xml should not automatically pass :procs to associations included with :include (changeset 8258).

    Of course Ruby 1.9 compatibility was a big deal in December 2007 since that Ruby 1.9 was targetted to be released on Christmas (changesets 8369, 8309, 8398, 8397, 8412). Yes, I know, Ruby 1.9 is a development release.

    There were also broken tests to be fixed (changeset 8271), bugs to be fixed (fixing Array#to_sentence), and some refactoring (changesets 8343 and 8522).

    And what would patching Rails be without random documentation fixes (changesets 8457, 8471, 8472, 8280, 8279, 8278, 8521). The API documentation always needs a hand, so if you see any outdated or incorrect documentation, or think you can improve them with better examples or whatever, just submit a patch (it's easy and should take you all of 3 minutes).

    This month's hackfest has a terribly attractive top prize of a RailsConf ticket. Too bad I won't be able to take part in this month's hackfest since winners of past month's hackfest are automatically excluded from the next's. But maybe you can ;).

  • Living on the Edge (of Rails) - the pilot

    Gregg Pollack of Rails Envy (the guys behind your favorite Ruby on Rails vs everything commercials) asked me recently whether I could provide them with weekly updates of changes on edge Rails for the Rails Envy Podcast (which is often hilarious besides being informative), seeing as I am already keeping up with developments on the Rails trunk while doing my small part contributing to Rails. Of course, I responded with a +1.

    The podcast is now available and contains only abbreviated notes about the changes that I'd sent over to Gregg, so here's the detailed version of changes on edge Rails from the release of Rails 2.0.2 (17 Dec 2007) to the day the podcast was recorded (30 Dec 2007).

    Noteworthy changes on edge Rails: 2007-12-17 to 2007-12-30

    Native mongrel handler

    A native Mongrel handler has been introduced. This is so that it can be worked on independent of Mongrel's release cycle (the Rails handler is currently in the Mongrel codebase). Jeremy Kemper (bitsweat) has already made a minor performance improvement by moving the mutex from the handler itself into the dispatcher. This means that as Rails gets more threadsafe, the Rails team can push the mutex further down so that it's not a (scary) giant lock (the existing Mongrel Rails handler has a synchronized block around Rails' dispatcher).

    Note: The native mongrel handler is not used by default at this time on edge Rails.

    Caching branch for Rails 2.1

    A caching branch has been introduced, targeted for Rails 2.1. Other than being a big refactoring, there's also native cache key management (e.g. Product.find(5).cache_key # => "products/5") and memcache integration is also being looked at.

    Ruby 1.9 compatibility continues...

    Yup, work on Ruby 1.9 compatibility for Rails continues. Rails is fortunate to have committers and contributors who are so keen on being 1.9-compatible - free performance gains are hard to turn down after all. There's been lots of patching going on to deal with the changes in Ruby 1.9 (Unicode changes, new syntax, deprecations, etc.).

    Yes, the Rails committers and contributors working on 1.9-compatibility are fully aware that Ruby 1.9 is a development release, but nothing's lost in making sure Rails is compatible with a version of Ruby that will eventually be a production version of Ruby. Besides, I think most of the 1.9-compatibility contributors have a dream that sometime soon, when Ruby 1.9 becomes "stable" enough, we'd be running our Rails applications on Ruby 1.9 for a free performance boost.

    Too bad running Rails on Ruby 1.9 is not really possible at the moment since other projects like Mongrel need to be updated for Ruby 1.9 as well, first.

    Bugfixes

    • The new TestCase subclasses (ActionController::TestCase,
      ActionMailer::TestCase) introduced in Rails 2 have a gotcha. This has been fixed quickly by Josh Peek.
    • Fixed a bug where trying to :include for a belongs_to association inferred the foreign key incorrectly from the class name, rather than from the association name (changeset contributed by Jonathan Viney). Code is probably easier to understand:
      class Post < ActiveRecord::Base
        belongs_to :main_category, :class_name => 'Category'
      end
      
      Post.find(:first, :include => :main_category)
      # => ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
      'posts.category' in 'on clause'

    Optimizations

    I can has feedback nao plz?

    Please leave any suggestions for improvements you may have - about anything really: format, level of detail, what you'd like to see, whatever. Until next week...

  • Why I love RSpec nested example groups

    Prior to the introduction of nested example groups in RSpec, I'd always felt that descriptions got a little unwieldy when trying to describe the different cases and disliked specifying the controller_name repeatedly. For example (and these are real examples from real projects at work, with actual code removed for conciseness):

    describe 'POST HotelDealsController#create with a valid hotel deal' do
      controller_name :hotel_deals
    
      before(:each) do
        # ...
      end
    
      it "should create and save a new hotel deal" do
        # ...
      end
    end
    
    describe 'POST HotelDealsController#create with an invalid hotel deal' do
      controller_name :hotel_deals
    
      before(:each) do
        # ...
      end
    
      it "should not perform any redirection" do
        # ...
      end
    end

    Nested example groups allow me to do this instead:

    describe SearchController do
      before(:each) do
        @search = mock_model(Search)
      end
    
      describe "POST 'create'" do
    
        it "should render 'new' when creating an invalid search" do
          # ...
        end
    
        describe "with a valid search that has a location_id" do
          before(:each) do
            @search.stub!(:valid?).and_return(true)
            @search.stub!(:location_id).and_return(1)
          end
    
          it "should save a new search that has a location_id, location_code and location_name, and redirect to 'show'" do
            # ...
          end
        end
    
        describe "with a valid search that doesn't have a location_id" do
          before(:each) do
            @search.stub!(:valid?).and_return(true)
            @search.stub!(:location_id).and_return(nil)
          end
    
          it "should ask the Location model for possible locations if the search doesn't have a location_id" do
            # ...
          end
        end
      end
    end

    What's the difference you say? Well, while it may sound trivial, I can do describe SearchController only once and nest all examples for the different actions and scenarios inside without breaking it up into separate top-level example groups where I'd need to say controller_name :search multiple times.

    Another (more important) benefit is I can progessively specify different scenarios I want to test by nesting them and providing a before block to setup mocks and stubs for that specific scenario. Let's look at what I mean with some code. Over here, I'm specifying the create action of my SearchController:

    
    describe "POST 'create'" do
      it "should render 'new' when creating an invalid search" do
        # ...
      end
    
      describe "with a valid search that has a location_id" do
        # ...
      end
    
      describe "with a valid search that doesn't have a location_id" do
        # ...
      end
    end

    There're 3 possible scenarios here: an invalid search, a valid search with a location_id, and a valid search without an location_id. I can write examples for invalid searches within the top-level example group itself (it "should render 'new' when creating an invalid search"). Now, to test valid searches, I break out 2 nested example groups with their own before block to setup mock searches, one which has a location_id, the other doesn't. What's the big deal? It just feels much more organized than the first, non-nested example where a top-level example group is created for each scenario.

    And I know it may not seem like much, and I don't think there's anything particularly wrong with non-nested examples - it's a matter of personal preference. Nesting allows me to write examples for controllers in a saner fashion where I can say confidently to myself that "all examples for XXX action go here". I'm a big believer in readable tests (if you're on the Rails Trac much and have seen my reviews and patches you should know), so being able to write specs where I feel they belong makes me happier, and the examples read really nicely too.

    I guess what I'm really trying to say is this: when trying to add a new example to legacy specs (legacy being anything that was written more than 5 mins ago) I instantly know where to place it - gone are the days of going through different example groups looking for the right place to put my new specification (and often settling on just simply creating a new top-level example group). I like to think we've all been there.

    That said, I'm not saying that deeply nested examples are a good thing. When testing Rails controllers I find that you don't often have to go more than 3 deep to allow for all the possible scenarios. Any more would suggest that your controller is doing too much work.

  • DRY in your functional and ActionMailer tests - Rails 2.0 a feature a day #7

    That's right, Don't Repeat Yourself in your functional and ActionMailer tests. If you're a Test::Unit user, this will probably look familiar to you when writing Rails functional tests (for your controllers):

    class PostsControllerTest < Test::Unit::TestCase
      def setup
        @controller = PostsController.new
        @request    = ActionController::TestRequest.new
        @response   = ActionController::TestResponse.new
      end
    
      def test_should_not_explode
        # Invariably sexy test code.
      end
    end

    You'd probably have noticed how setup always sets the @controller, @request and @response instance variables in all your controller tests. In Rails 2.0, you can instead subclass ActionController::TestCase (in turn a subclass of Test::Unit::TestCase) in your test cases and avoid repeating the instance variable assignments.

    class PostsControllerTest < ActionController::TestCase
      # No need to define setup.
    
      def test_should_not_explode
        # Invariably sexy test code.
      end
    end

    Isn't that much better?

    Watch out for a gotcha though, when you actually do need to define a setup method (like when you need to setup several other things before your tests run), as Jeffrey Allan Hardy reports. The issue has been resolved on edge Rails. If you're not using edge Rails, you should remember to always call super in your setup method should you define one:

    class PostsControllerTest < ActionController::TestCase
      def setup
        super
        @user = users(:konata)
      end
    
      def test_should_not_explode
        # Invariably sexy test code.
      end
    end

    A similar subclass of Test::Unit, ActionMailer::TestCase is available for your ActionMailer unit tests as well. That means you can replace this:

    class UserMailerTest < Test::Unit::TestCase
      include ActionMailer::Quoting
    
      def setup
        ActionMailer::Base.delivery_method = :test
        ActionMailer::Base.perform_deliveries = true
        ActionMailer::Base.deliveries = []
    
        @expected = TMail::Mail.new
        @expected.set_content_type "text", "plain", { "charset" => "utf-8" }
        @expected.mime_version = '1.0'
      end
    
      def test_signup
        # Test code.
      end
    end

    with this:

    class UserMailerTest < ActionMailer::TestCase
      def test_signup
        # Test code.
      end
    end

    For the geeks among you, check out the related changeset.

    About the contributor, Michael Koziarski

    Michael Koziarski, better know as nzkoz, is a long-time Rails core committer. Michael has done a signficant amount of work improving the performance of ActiveRecord for Rails 2.0 so we have him to thank for a good portion of the optimization work done on ActiveRecord. He also keeps a fairly new personal blog.

subscribe via RSS