Yehuda Katz is a member of the Ruby on Rails core team, and lead developer of the Merb project. He is a member of the jQuery Core Team, and a core contributor to DataMapper. He contributes to many open source projects, like Rubinius and Johnson, and works on some he created himself, like Thor.

@timhaines Yeah... it's an REE bug :(

Archive for March, 2009

Another Dispatch: AbstractController

On Monday, Carl started pairing with me on Rails 3. This week, we worked on further fleshing out the AbstractController. A few principles we’re following:

  • The AbstractController should be a low-level API. Nobody should be using the AbstractController directly, and subclasses of AbstractController (like ActionController::Base) are expected to provide their own #render method, since rendering means different things depending on the context.
  • However, AbstractController should provide enough facilities to avoid reinventing the wheel. For instance, the subclasses might be responsible for figuring out which layout to use, but should not need to know how to render a template with a layout.
  • It is a common desire to be able to add new options to render. This should be possible in an isolated way without interfering with other parts of the controller. We achieved this by making it possible to do:
module ActionController
  module Layouts
    def render_to_string(options)
      options[:_layout] = options[:layout] || _layout
      super
    end
 
    def _layout
      # implementation here
    end
  end
end
 
class ActionController::Base < ActionController::HTTP
  include ActionController::Layouts
end

In this case, options[:_layout] is used by AbstractController’s Layouts module. If you are implementing a subclass of AbstractController and want to make it possible to supply or use a layout, simply make a new module that sets the :_layouts key in the options hash and super. It may well turn out that all subclasses have the same logic for implicit layouts. If that’s the case, we can move the _layout up into AbstractController and let subclasses invoke it.

The specifics aren’t particularly important (yet), because we’re still working out what exactly needs to be modular, and what is more appropriately shared. But the bottom line is that we’re working toward some simple APIs that people can use to build their own customized controllers as well as extend built-in controllers without fear of breaking something else. Effectively, the API is “take in options hash, modify it if appropriate (possibly adding in keys to be used by AbstractController), super”.

Of course, we will need to document what the AbstractController keys are (so far we have :_prefix to specify the path to the template name to use, and :_layout to specify the layout to use). The specifics of all of this may change, and a crucial part of this is to cleanly document the architecture and API (my biggest frustration with the similarly architected current ActionController::Base is just how difficult is it to discover what’s going on), but I think we’re on the right track.

Finally, we’ve begun adding a large number of happy-path tests using Rack::Test that have been very effectively in helping to design and develop the more modular code. I’ll have more on this in the next couple of days, but the tests we’ve been writing have been very cool.

Share and Enjoy:
  • Digg
  • Reddit
  • HackerNews
  • Twitter

alias_method_chain in models

As people know, there’s been a fair bit of back-and-forth between me, the apparent foe of alias_method_chain, and folks who feel that alias_method_chain is a perfectly reasonable API that people should not blindly hate.

There are basically two use-cases for alias_method_chain:

  1. Organizing internal code
  2. Modifying existing code

Using alias_method_chain to organize internal code is an interesting discussion that I will hopefully continue to have into the future. Today, I want to address uses of alias_method_chain to override methods in ActionController::Base or ActiveRecord::Base. People who do this are blindly using the technique that has been most evangelized as the solution to all their problems when Ruby comes with a perfectly good solution.

Consider this post from vaporbase, which I am decidedly not picking on. It represents a common idiom that people have been trying to use in Rails. First, the usage in a model:

class Foo < ActiveRecord::Base
  include FooBar
end

Second, the code implementation:

module FooBar
  module ClassMethods
    def find_with_bar( *args ) 
      find_without_bar( *args )
      #...or whatever 
    end
  end
 
  def self.included(base)
    base.class_eval do
      extend ClassMethods
      class << self
        alias_method_chain :find, :bar
      end
    end
  end  
end

This is exactly equivalent to:

module FooBar
  def self.included(base)
    base.extend(ClassMethods)
  end
 
  module ClassMethods
    def find(*args)
      super
      #...or whatever
    end
  end
end

That’s right… if you’re looking to modify subclasses of ActiveRecord::Base or ActionController::Base, keep in mind that you’re (gasp) in an OO language with inheritance and super.

If you want to modify all of the models in your application, create your own custom ActiveRecord::Base subclass, and inherit from that throughout your application. That’s what inheritance is there for!

Another example

Another example, by a very, very smart person, goes even further overboard.

First, he started with:

class Post < ActiveRecord::Base
  class << self
    def find_with_tags(*args)
      options = extract_options_from_args!(args)
      if tag = options.delete(:tags)
        options[:select] ||= 'posts.*'
        options[:joins]  ||= ''
        options[:joins]  << <<-END
          INNER JOIN posts_tags AS inner_posts_tags ON posts.id = inner_posts_tags.post_id
          INNER JOIN tags AS inner_tags ON inner_tags.id = inner_posts_tags.tag_id
        END
        add_to_conditions(options, tags.map { 'inner_tags.name = ?' }.join(' OR '), *tags)
      end
      find_without_tags(*(args + [options]))
    end
    alias_method_chain :find, :tags
 
    def find_with_query(*args)
      options = extract_options_from_args!(args)
      if query = options.delete(:query)
        if query.empty?
          add_to_conditions(options, 'false')
        else
          term = "%#{query}%" 
          add_to_conditions(options, "posts.content LIKE ? OR posts.title LIKE ?", term, term)
        end
      end
      find_without_query(*(args + [options]))
    end
    alias_method_chain :find, :query
 
    protected
 
    def add_to_conditions(options, condition, *args)
      condition = args.empty? ? condition : [condition, *args]
      if options[:conditions].nil?
        options[:conditions] = condition
      else
        options[:conditions] = sanitize_sql(options[:conditions]) + " AND (#{sanitize_sql(condition)})" 
      end
    end
  end
end

Noticing it wasn’t very DRY, he resorted to metaprogramming:

class Post < ActiveRecord::Base
  class << self
    def handle_find_option(name, &block)
      eigenclass = class << self; self; end
      eigenclass.send :define_method, "find_with_#{name}_handled" do |*args|
        options = extract_options_from_args!(args)
        if option = options.delete(name)
          block[options, option]
        end
        send("find_without_#{name}_handled", *(args + [options]))
      end
      eigenclass.send :alias_method_chain, :find, "#{name}_handled" 
    end
  end
end
 
class Post < ActiveRecord::Base
  handle_find_option(:tags) do |options, tags|
    options[:select] ||= 'posts.*'
    options[:joins]  ||= ''
    options[:joins]  << <<-END
      INNER JOIN posts_tags AS inner_posts_tags ON posts.id = inner_posts_tags.post_id
      INNER JOIN tags AS inner_tags ON inner_tags.id = inner_posts_tags.tag_id
    END
    add_to_conditions(options, tags.map { 'inner_tags.name = ?' }.join(' OR '), *tags)
  end
 
  handle_find_option(:query) do |options, query|
    if query.empty?
      add_to_conditions(options, 'false')
    else
      term = "%#{query}%" 
      add_to_conditions(options, "posts.content LIKE ? OR posts.title LIKE ?", term, term)
    end
  end
end

An alternative, using super:

class Post < ActiveRecord::Base
  class << self
    def find(*args)
      options = args.last.is_a?(Hash) ? args.last : {}
      add_tag_conditions(options)
      add_query_conditions(options)
      super
    end
 
    private
    def add_tag_conditions(options)
      if tag = options.delete(:tags)
        options[:select] ||= 'posts.*'
        options[:joins]  ||= ''
        options[:joins]  << <<-END
          INNER JOIN posts_tags AS inner_posts_tags ON posts.id = inner_posts_tags.post_id
          INNER JOIN tags AS inner_tags ON inner_tags.id = inner_posts_tags.tag_id
        END
        add_to_conditions(options, tags.map { 'inner_tags.name = ?' }.join(' OR '), *tags)
      end
    end
 
    def add_query_conditions(options)
      if query = options.delete(:query)
        if query.empty?
          add_to_conditions(options, 'false')
        else
          term = "%#{query}%" 
          add_to_conditions(options, "posts.content LIKE ? OR posts.title LIKE ?", term, term)
        end
      end      
    end
 
    def add_to_conditions(options, condition, *args)
      condition = args.empty? ? condition : [condition, *args]
      if options[:conditions].nil?
        options[:conditions] = condition
      else
        options[:conditions] = sanitize_sql(options[:conditions]) + " AND (#{sanitize_sql(condition)})" 
      end
    end
  end
end

We just override find, have it modify the options as appropriate, and call super. This same technique works fine for your own applications’ ActionController modifications, and in any case where the framework API involves subclassing. Folks: this is what subclassing is FOR!

Share and Enjoy:
  • Digg
  • Reddit
  • HackerNews
  • Twitter

Rack as a Transformative Figure

I had occasion to think about Rack a few times today. First of all, I was writing some proposals to a few conferences about Rails, which always gets me thinking about architecture. In this case, it got me thinking about how Rack is influencing Rails’ architecture moving forward. Second, a friend told me he was considering moving from an archaic web language to Python (specifically Django). I got to thinking about how the future of Ruby is intertwined with Rack, and why that might not be the case with Python and WSGI, unfortunately.

Like Ruby, Python has a single dominant web framework in Django. According to Google Trends, Django gets about eight times as many programming-related searches as the next-most-popular framework, Turbogears. And TurboGears is 1.5 to 2x the size of Pylons and CherryPy, other frameworks. Naturally, Rails dominates the Ruby space more thoroughly (in the last 12 months, Rails beat the next-most-popular framework, Merb, by more than 60 times), but the effect is roughly the same. Many more people contribute to the Django and Rails ecosystem than the Turbogears and Merb ecosystems.

Fortunately for Ruby, however, Rails (our dominant framework) has thoroughly embraced Rack, an abstraction layer and specification for the code between a web framework and a web server. This embrace of Rack has brought much-needed oxygen to the specification, and has allowed Merb, Rails, Sinatra, and others to cooperate in a shared space. In the next few months, Merb and Rails will be making their routers a shared Rack component, and the same is true for a number of smaller elements, like parameter parsing. In part, this is possible because unlike WSGI, Rack is a specification *and* a library. In the WSGI universe, the WebOb library provides similar functionality, but is not as widely shared.

In particular, while Django itself can run on WSGI servers, Django does not use WebOb, nor is its middleware WSGI-compliant. Let’s look at a specific example, a piece of middleware that wraps all requests in Django in a transaction:

from django.db import transaction
 
class TransactionMiddleware(object):
    """
    Transaction middleware. If this is enabled, each view function will be run
    with commit_on_response activated - that way a save() doesn't do a direct
    commit, the commit is done when a successful response is created. If an
    exception happens, the database is rolled back.
    """
    def process_request(self, request):
        """Enters transaction management"""
        transaction.enter_transaction_management()
        transaction.managed(True)
 
    def process_exception(self, request, exception):
        """Rolls back the database and leaves transaction management"""
        if transaction.is_dirty():
            transaction.rollback()
        transaction.leave_transaction_management()
 
    def process_response(self, request, response):
        """Commits and leaves transaction management."""
        if transaction.is_managed():
            if transaction.is_dirty():
                transaction.commit()
            transaction.leave_transaction_management()
        return response

As you can see, Django allows multiple methods in its middleware (process_request, process_response, process_exception, and process_view). As with rack, WSGI middleware uses a single method to wrap the entire request. Here’s how the above Django middleware would look in Rack:

class TransactionMiddleware
  def initialize(app) @app = app end
 
  def call(env)
    transaction.enter_transaction_management
    tranaction.managed(true)  
    begin
      @app.call(env)
    rescue
      transaction.rollback if transaction.dirty?
      transaction.leave_transaction_management
    end
    if transaction.managed?
      transaction.commit if transaction.dirty?
      transaction.leave_transaction_management
    end
  end
end

Leaving aside the differences in language, the Django middleware is incompatible with WSGI for no discernable reason. This means that Django middleware cannot be shared with the rest of the (admittedly smaller) Python web ecosystem, which makes it harder, for instance, to use Django’s ORM separately from Django (because you’d have to write your own transaction-wrapping middleware).

The big win of WSGI and Rack is that like Unix pipes, they make it easy to mix and match web framework elements to develop something that works for you. It makes it easier to develop entire frameworks, like CloudKit, that can be used as simple plugins to larger frameworks like Rails. Rails’ embrace of Rack, in the long term, is going to significantly open up experimentation in the community to non-Rails solutions that can be used WITH RAILS while they are being ironed out. In fact, being able to trivially run a Sinatra app inside a Rails app is an important design goal for Rails moving forward.

When you look inside the Rails middleware folder, you see things like this in the Rails Failsafe middleware:

def call(env)
  @app.call(env)
rescue Exception => exception
  # Reraise exception in test environment
  if env["rack.test"]
    raise exception
  else
    failsafe_response(exception)
  end
end

Instead of assuming something specific about Rails test mode, this middleware is allowing the community to come together around specifications that all Ruby frameworks can use together (in this case, a generic “test mode”). And this middleware can be used in Sinatra, Merb, or CloudKit by simply requiring “action_dispatch/middleware/failsafe” (as of Rails3) and then adding ActionDispatch::FailSafe as a middleware. No other Rails assumptions are made.

The bottom line is that this is going to be very powerful moving forward, and it’s too bad that Django’s insistence on a go-it-your-alone approach is damaging the Python community’s attempts to do the same.

On a side note, I want to be clear that I am aware that Django can run inside of a WSGI server, and can use WSGI middleware. However, Django middleware cannot be used by WSGI, and a recent post on the topic, which advocated the use of WSGI middleware in Django apps, had to be sure to include “I’m not trying to stir up any controversy, I’m not saying we should stop making Django middleware or anything like that”. I, for one, think that’s a shame.

Share and Enjoy:
  • Digg
  • Reddit
  • HackerNews
  • Twitter

S3 Moneta Store (also, a Rails update)

As I had hoped, moneta has been encouraging various members of the community to play with using a unified key/value abstraction where they would typically have used just a Hash. Both Cloudkit and DataMapper have been playing with using moneta for various parts of their infrastructure, and Anthony Eden just contributed an S3 adapter.

How to use moneta in your own project

It’s actually quite simple. Any place where you’re currently using or considering using a Hash for its key/value properties, simply continue to use the Hash, but provide a configuration option for swapping in a different object instead.

Here’s a potential example from DataMapper. The original code:

def initialize(second_level_cache = nil)
  @cache = {}
  @second_level_cache = second_level_cache
end

And the code with moneta:

module DataMapper
  cattr_accessor :identity_map_klass
  self.identity_map_klass = Hash
end

def initialize(second_level_cache = nil)
  @cache = DataMapper.identity_map_klass.new
  @second_level_cache = second_level_cache
end

As demonstrated above, you can use a bare Hash as a moneta store, with one caveat. A bare Hash does not support Moneta’s expiration features. If you want to be able to assume those features, change the above to:

module DataMapper
  cattr_accessor :identity_map_klass
  self.identity_map_klass = Moneta::Memory
end

def initialize(second_level_cache = nil)
  @cache = DataMapper.identity_map_klass.new
  @second_level_cache = second_level_cache
end

Moneta::Memory inherits from Hash, but adds expiration features. Of course, if you go with Moneta::Memory, you will need to include Moneta as a dependency (if you used a bare hash, the user could choose whether or not to use Moneta, but expiration would not be available).

Rails

I’ve been traveling an insane amount recently, but I’ve still been working on Rails. I’ll have a more thorough post on this soon, but I’ve started work on a more modular base class for ActionController::Base and ActionMailer::Base that should also serve as a base class for a Rails port of Merb’s parts. You can follow along at the abstract_controller branch of my github fork. I pulled in my callbacks branch as well, which is so far working wonderfully on my fork.

Share and Enjoy:
  • Digg
  • Reddit
  • HackerNews
  • Twitter