~4 minutes

ActiveSupport: Testing and Stringy Booleans

One of the things that I really like about the Ruby community is the serious focus on testing. When you’ve really got your testing dialed in, there’s a tremendous sense of peace knowing that you can refactor to your heart’s content…and add new functionality of course…that too.

A Testing Artifact

So as I was finishing up my read through of ActiveSupport, I came across this bit of code in the testing subdirectory:

module ActiveSupport
  module Testing
    module Declarative

      def self.extended(klass) #:nodoc:
        klass.class_eval do

          unless method_defined?(:describe)
            def self.describe(text)
              class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
                def self.name
                  "#{text}"
                end
              RUBY_EVAL
            end
          end

        end
      end

We’ve seen class_eval before, but we haven’t come across the extended lifecycle hook. Basically, it gets called by Ruby when you extend a class with this module, so you can use it to bootstrap whatever functionality you want to shove into your class. In this case, we define a class level method called describe. It appears that that method then overrides the name class method with whatever you pass in as an argument to describe.

Just to confirm this, let’s petri dish this in the console. First I write up a barebones module that has this functionality:

module Thing
  def self.extended(klass) #:nodoc:
    klass.class_eval do

      unless method_defined?(:describe)
        def self.describe(text)
          class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
            def self.name
              "#{text}"
            end
          RUBY_EVAL
        end
      end

    end
  end
end

And make two classes, one that extends Thing and the other that doesn’t:

class Foo; extend Thing; end
class Bar; end

Foo.methods - Bar.methods
# => [:describe]

Neat, extending Foo with Thing gave me a new method…but does that method do what we were guessing?

class Foo
  extend Thing
  describe "this is a thing"
end

Foo.name
# => "this is a thing"

Bar.name
# => "Bar"

Yep, it does. Well that’s weird. Digging around in the history of this file, it looks like this is just an artifact of shifting to minitest (which is very cool by the way). This blockless describe seems to be a relic of the past, removed from Rails own tests as well. Still fun to try and figure it out.

Stringy Booleans

I really enjoy all the quality of life improvements that are in ActiveSupport. This one is relatively minor, but I like the implementation and the functionality, so here you go…

You know how you can check what Rails environment you are currently in via the method:

Rails.env.production?

That’s actually implemented via a StringInquirer. Let’s say you have a string you want to turn into a boolean looking method, you can tack .inquiry onto it and voila:

env = 'production'.inquiry
env.production?
# => true

Awesome, certainly handy. What’s behind this cleverness?

module ActiveSupport
  class StringInquirer < String
    private

      def respond_to_missing?(method_name, include_private = false)
        method_name[-1] == '?'
      end

      def method_missing(method_name, *arguments)
        if method_name[-1] == '?'
          self == method_name[0..-2]
        else
          super
        end
      end
  end
end

method_missing is called, of course, when we attempt to tell our object to do something that it doesn’t know how to do. In this case, the StringInquirer checks to see if we are calling a predicate method, and if so, does the comparison for us.

The other bit, the respond_to_missing? is just telling our object that if the requested method ends in a ‘?’ then respond true to a respond_to? request. Polite.

ActiveSupport Done!

Reading through every line of ActiveSupport has been a lot of fun. I’ve also realized that saturating your mind with good Ruby has the side effect of changing the Ruby you write. I end up thinking about “how they did it in Rails” and that benefits my own code.

So where are we at? Here’s lines of code (first is code only, second includes tests):

  • actionmailer: 1119/2975
  • actionpack: 30854/83422
  • activemodel: 4481/9701
  • activerecord: 30318/76848
  • activesupport: 15785/36089

So I guess that means I’m 19% of the way through. Awesome!

I’m not sure what subsystem will be next, we’ll have to see.

Tagged with: programming

My first novel is coming soon-ish!

Check out Singular