Rails 3 Beta is Out — A Retrospective

By Yehuda Katz | February 4th, 2010 at 12:02PM

The Rails team has finally released the Rails 3 beta, after more than a year since the Rails and Merb teams started working on this release. You can read all about it at the official Rails blog, but I figured I’d take the opportunity to share my take on the release.

First of all, you’re probably sick of hearing this, but we’ve done far, far more than we ever expected. A lot of that happened in the last few weeks.

One of the things that most surprised and impressed me is the Rails core team’s (and especially DHH’s) attention to detail and the experience of the beta release. For weeks, we’ve been “this close” to releasing, but the experience of starting up a new Rails app or upgrading from a Rails 2.3 app still felt too unpleasant. In this kind of situation, it’s tempting to say “it’s just a beta—people who use beta software know what they’re getting,” but that would have been a major cop-out. For many people on the leading edge, a poor beta experience will shape their perception of the product as a whole. So we waited a bit, but now we’re finally here.

In addition to a surprising (in a good way) amount of polish, we touched nearly every part of Rails: I’ll walk you through the top-level blocks.

Dependencies

Rails gets a bunch of new dependencies, stepping out of the “virtually zero dependencies” box it has lived in since the beginning. In order to achieve this, we wrote a brand new dependency management system (called Bundler) from scratch, remedying many of the problems with Rubygems that have made using a number of dependencies difficult (and which we encountered in Merb when taking a more-dependencies approach). Taking another step into the future, we built Bundler itself as a standalone component, so you can use it for Rails, Merb, Sinatra, or even a plain old Ruby script.

And even though the Rails happy path uses Bundler to manage dependencies, that happy path is limited to the generated code only. The Rails library itself does not rely on Rubygems, Bundler, or any package manager. This makes using a different package manager, like Debian or rip, as simple as changing a few lines of the generated code.

Instrumentation

A number of companies and projects have sprung up over the years attempting to provide useful information about the runtime status of a Rails process. In order to facilitate this work going forward, we’ve baked instrumentation into Rails itself. Instead of having to monkey-patch an appropriate method to learn about which templates Rails renders, for instance, simply listen for an event. While Rails has never officially supported the monkey-patching approach, we will commit to supporting instrumentation events in the same way as we support any other public APIs, making them future-proof stable ways to learn about what’s happening inside of Rails.

Better yet, Rails’ logging system uses these very events to emit information into the log!

Every Framework is an Extension

In what is possibly the biggest conceptual shift in Rails 3, we’ve exposed an extension API that allowed us to make ActiveRecord, ActionController, and ActionMailer simple Rails extensions. In other words, the DataMapper ORM can take a look at the part of ActiveRecord that implements its Rails extension, implement similar features, and know for sure that it’ll work. Additionally, users can completely remove the ActiveRecord gem, and be rid of the generators, rake tasks and other peripheral elements. An example: if DataMapper implements a db:test:prepare rake task, a Rails developer can replace ActiveRecord with DataMapper, and the test:units rake task will use DataMapper’s new tasks.

In short, we have completely removed references to other frameworks from Railties itself, making the frameworks of Rails true extensions to Rails itself. This makes it possible to replace any part of Rails without having to worry about entanglements with Railties.

Rack

Rails 2.3 took a very firm stance in favor of Rack. Rails 3 completes the story. In Rails 3, the Application itself, the router, and controller actions themselves are all Rack applications. Rails ships with a standard config.ru, and can boot using the rackup tool that ships with Rack, or with Passenger or Glassfish in standard Rack mode. In fact, any Rails host or server that supports the Rack protocol can run a Rails 3 application unaltered.

Because so many parts of Rails are Rack, you can also insert standard Rack middleware in config.ru, in your Rails application, in the router, or even in a controller’s actions. Rails itself ships with a number of standard middlewares, for exception handling, cookies, parameter parsing, flash notifications, sessions and a bunch more. You can use any of these middlewares in standard Rack applications (without Rails).

ActionController

ActionController got a major overhaul, dividing up a monolithic component into multiple pieces.

ActionDispatch

You can think of ActionDispatch as rack-ext. Essentially, we packaged up all of the parts of ActionController involved in the Rack dispatching process into a single reusable component. This includes ActionDispatch::Request, ActionDispatch::Response, our mime type handling, a bunch of middleware, and integration testing support.

It also includes a wrapper around rack-mount a new powerful routing library. Josh Peek, a member of the Rails Core Team, wrote rack-mount as a standalone component, so that it could be used in standard Rack projects without any Rails dependencies. By default, it simply dispatches inbound requests to Rack endpoints.

Because Rails 3 controller actions are Rack endpoints, Josh could easily wire up a thin shim around rack-mount to dispatch inbound requests to Rails controller actions. But because we built the Rails router on top of rack-mount, Rails applications can route inbound requests to any Rack application, like Sinatra or Cloudkit.

AbstractController

The disparity between ActionController and ActionMailer has long been a personal pet peeve of mine. For Rails 3, we refactored ActionController and ActionMailer to inherit from a common superclass: AbstractController. AbstractController includes the basic concept of controllers, rendering, layouts, helpers, and callbacks, but nothing about HTTP or mail delivery. This means that ActionMailer can no longer drift away from ActionController, leaving us with two distant cousin APIs.

ActionController

ActionController got a big overhaul, completely eliminating alias_method_chain in favor of a module-based system that extension author can more easily extend. For instance, we exposed a Renderer interface, allowing extension authors to easily add render :pdf or render :csv without having to override the render method in brittle ways.

While the API did not change much in general, we did add a few new features, such as the ability to add middleware directly to a controller’s actions or the ability to wrap up common idioms more cleanly using responders. Additionally, we improved the overall architecture to reduce the need for certain hacks.

For instance, ActionController will now look up the layout for an action using the details (such as format and locale) that its template used. In Rails 2.3, an RJS template could end up using an HTML layout, requiring us to exempt RJS templates from layouts altogether. However, this was a band-aid (what if you wanted an RJS layout?) and the improved architecture eliminates the possibility for other, similar problems.

ActionView

ActionView got a significant overhaul, improving its internal architecture, increasing performance, adding pervasive XSS protection, and making all JavaScript helpers unobtrusive.

In terms of architecture, Rails 3 defines a clear boundary between ActionController and ActionView. Now, ActionView exposes a single API entry point for rendering templates and partials, making the code path easier to understand, eliminating a number of latent (but unusual) bugs, and making it easier to reason about caching lookups for performance.

When doing a performance audit of Rails applications, ActiveRecord and ActionView (specifically, template rendering) pop up as major costs. In particular, ActionView has had a fair bit of overhead that’s impossible to avoid without throwing certain features out the window. For instance, partials and especially collections of partials had enough overhead to become noticeable on profiles of real applications.

In Rails 3, we optimized both the template lookup logic (which should now take up extremely small amounts of time) and the logic inside the loop of partial collections. In particular, partial collections should often be much faster in Rails 3 than a manual loop, and not much slower than manually inlining and unrolling the loop. Finally, the improved architecture has made additional optimizations possible, so expect further performance improvements in Rails 3.1.

We also switched to Erubis, a fully compatible ERB implementation with much better extensibility characteristics. In particular, it allows us to easily hook in the code that it generates for any ERB construct. This will allow compile-time performance optimizations in the future and enables the next feature: pervasive XSS protection.

Rails has always shipped with a simple way to escape any user-generated content. Unfortunately, this required constant vigilance. A number of highly visible, successful attacks against websites on a number of platforms taught the world that successful mitigation of this threat requires treating all Strings as suspicious, and allowing developers to mark Strings as safe. Rails 3 escapes all Strings, allowing developers to convert Strings into safe Strings easily. We have also modified all of our internal helpers to mark Strings that we create as safe, while not modifying Strings passed in by the application developer.

Finally, we have overhauled all of the JavaScript helpers to emit plain HTML markup instead of JavaScript. As part of the process, we have created drivers for jQuery and Prototype that bind events to the appropriately marked up elements in the DOM. This means that Rails can now support any JavaScript library as a first-class citizen. We will support and maintain the markup and JavaScript developers can develop pure-JavaScript libraries to make use of the markup (without ever having to touch Ruby). If other frameworks emit the same markup, the same JavaScript libraries will work with their helpers.

ActiveModel

Over the years, people have created a number of ActiveRecord lookalike libraries for things like MongoDB or CouchDB. In Rails 3, we have extracted elements of ActiveRecord unrelated to RDBMS persistence, such as validations, callbacks, dirty tracking, and serialization into a separate library. Other libraries, or even plain old Ruby code, can use these components on an à la carte basis, choosing to look as much or as little like ActiveRecord as they wish.

Additionally, ActiveModel defines an API that any object can implement in order to work seamlessly with ActionPack. It provides drop-in implementations of the API, but Ruby libraries that wish to participate may implement the APIs themselves. This means that ActionPack itself is agnostic as to the ORM used, and even supports hand-rolled models.

ActiveRecord

ActiveRecord has undergone a large internal change, integrating Nick Kallen’s ActiveRelation codebase. This replaces the internal ad-hoc query generation with query generation based on relational algebra. In essence, it means that you can chain queries for refinement before Rails builds the actual SQL query.

For instance, instead of Post.scoped immediately connecting to the database and retrieving all posts, it creates a relation object representing the idea of “all posts”. You can then refine further, such as Post.scoped.where(:author => "Yehuda"). This means that you can pass around relations, refining them as appropriate in helper methods before the view finally kicks off the query when it iterates over the objects.

This also unifies similar concepts in ActiveRecord, like named scopes, into a single relational concept that pervades the entire ORM.

ActiveSupport

In order to facilitate use of ActiveSupport by other libraries, ActiveSupport 3.0 makes it easy for developers to cherry-pick specific parts of the library they wish to use. In particular, we have made all dependencies between ActiveSupport files explicit, so that if you require a particular extension, you can be sure that you get everything that you need. In the past, in order to use ActiveSupport extensions reliably, you needed to require the full library.

ActiveSupport also comes with some new and retooled modules, such as ActiveSupport::Concern (which makes it easier to manage dependencies between modules) and a significantly faster ActiveSupport::Callbacks.

Railties

We made the biggest user-facing changes in Railties. A Rails 3 application is an object (of the class Rails::Application), which also doubles as a valid Rack application. Instead of configuring your application in a block, you configure the Application itself (which now lives in config/application.rb).

The Rails::Application class also inherits from an Engine class, making Engines behave almost identically to Applications, as appropriate. And Engines are a special kind of Railtie, again simplifying and unifying the concepts in the Rails glue code.

The rails command itself now has special powers inside of Rails applications, replacing the commands in script with subcommands of rails. For instance, use rails server instead of script/server.

In Conclusion…

It’s been an incredibly busy and productive year. Rails 3 is the culmination of thousands of man-hours by numerous dedicated open source contributors. It’s been a long journey, filled with debate, feedback, and writing code, refactoring it and  then refactoring it yet again. While this release is just a beta, it represents the bulk of things to come in a stable and tested state.

Plugin authors should take a look at the new exposed APIs and start porting plugins to work on Rails 3. The community is excited, and we expect adoption to be rapid, aggressive and just as importantly, vocal. We’ll be listening to that feedback to help get Rails 3 to its final perfected state, and you should be listening too, so your plugins can make the jump with us.

Leave comments here and on the official Ruby on Rails blog; we’ll be here to respond!

Share this post:
  • email
  • Digg
  • del.icio.us
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Google Bookmarks
  • Facebook
  • LinkedIn
  • Print
Popularity: 6% |
Post rating: 2 votes, average: 5.00 out of 52 votes, average: 5.00 out of 52 votes, average: 5.00 out of 52 votes, average: 5.00 out of 52 votes, average: 5.00 out of 5
Loading ... Loading ...

Homebrew: OS X’s Missing Package Manager

By Andre Arko | February 2nd, 2010 at 10:02AM

Managing software packages on Unix has always been, to put it politely, a giant pain, and most Linux distributions are built around the different ways we’ve all been trying to alleviate that pain. In this post, I’ll walk you through Homebrew, a fantastic new option for package management made simple.

Pre-Homebrew, there were various attempts to create effective package managers for OS X. The two most popular efforts were Fink and MacPorts, but they each had their frustrations. In both cases, creating packages or portfiles was still complex and difficult.

Max Howell’s done a great job with Homebrew; it’s easy to edit, and creating new packages is a breeze. Let’s dig in!

What Does It Do?

The pitch is simple: Homebrew alleviates the drudgery and repetition of downloading and installing Unix software packages on OS X. If you’re sick of ./configure && make && make install, Homebrew can help.

Why Homebrew?

As previously mentioned, OS X already has two package managers: Fink and MacPorts. If one of those is working for you, great. But if you’ve been frustrated by them in the past, I strongly suggest you give Homebrew a try. It’s easy to create and edit formulae, and even to edit Homebrew itself, since the core is just a few hundred lines of Ruby code.

It doesn’t impose external structure on you: the default is to install it to /usr/local, but you can install it anywhere. Inside your Homebrew directory, software is installed in subdirectories inside Homebrew’s cellar, like Cellar/git/1.6.5.4/. After installation, Homebrew symlinks the software into the regular Unix directories. If you want to hand-install a package or version that isn’t officially part of Homebrew yet, it can happily coexist in the same location.

That’s usually not necessary, though, since formulae can install directly from version control. If a package has a public git, svn, cvs, or mercurial repository, you can install the latest development version as often as you’d like with a simple brew install.

Installing packages is faster, too, because Homebrew also works hard to avoid package duplication. No more installing yet another version of Perl as a package dependency when you already have a working install of Perl built into OS X. Best of all, Homebrew has a basic philosophy that you shouldn’t have to use sudo to install or manage software on your computer.

Sounds Pretty Great… How Do I Get It?

The first (and only) dependency that Homebrew has is the OS X Developer Tools, which are on the OS X installer disc, and available from Apple as a free download.

Unless you have a reason not to, the easiest place to install Homebrew is in /usr/local. You can do that in just a few steps on the command line:

# Take ownership of /usr/local so you don't have to sudo
sudo chown -R `whoami` /usr/local
# Fix the permissions on your mysql installation, if you have one
sudo chown -R mysql:mysql /usr/local/mysql
# Download and install Homebrew from github
curl -L http://github.com/mxcl/homebrew/tarball/master | tar xz --strip 1 -C /usr/local

Once you’ve done that, you’re good to go! Assuming /usr/local/bin is in your PATH, feel free to try it out:

brew install wget
brew info git

The Homebrew wiki also has more about integrating with RubyGems, CPAN, and Python’s EasyInstall.

Keeping your copy of Homebrew up to date is easy, too:

brew install git
brew update

Once you have git installed, you can just run brew update any time you want to pull down the latest formulae.

Contributing

Creating a new formula is almost that easy. If Homebrew didn’t have a formula for wget, you could create one like this:

brew create http://ftp.gnu.org/gnu/wget/wget-1.12.tar.bz2

After you save your formula, you can test it out with brew install -vd wget, to enable verbose logging and debug mode. If you need help getting your formula working, there’s more documentation on the Homebrew wiki. You can also learn by example from already existing formula, like git or flac.

You can check out lots of example formulae, as well as the internals of Homebrew, by running brew edit. The code is pretty straightforward. If you have questions, or are interested in future plans, the contributors to Homebrew tend to hang out in the #machomebrew channel on Freenode.

Once you have a working new formula, it’s easy to create your own fork of Homebrew on GitHub to push your new formula to, by using the github gem:

git add .
git commit -m "Added a formula for wget"
gem install json github
github fork
git push <your github username> mastergitx

After pushing your change to GitHub, go to the Homebrew issue tracker and create a ticket with the subject “New formula: “. Assuming everything checks out, your formula will be added to the main Homebrew repository and available for everyone else to use.

Wrapping Up

Homebrew is a compelling alternative to MacPorts and Fink. The Homebrew core and all the formulae are written in Ruby, so it’s easy to add new packages or even new features. If you’re looking for more control over the Unix software you have installed on your Mac, or you’ve been frustrated by other package managers in the past, check it out. I think you’ll be happily surprised.

Share this post:
  • email
  • Digg
  • del.icio.us
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Google Bookmarks
  • Facebook
  • LinkedIn
  • Print
Popularity: 6% |
Post rating: 2 votes, average: 5.00 out of 52 votes, average: 5.00 out of 52 votes, average: 5.00 out of 52 votes, average: 5.00 out of 52 votes, average: 5.00 out of 5
Loading ... Loading ...

Iteration Shouldn’t Spin Your Wheels!

By Evan Phoenix | January 27th, 2010 at 10:01AM

This article was originally included in the September issue of the Engine Yard Newsletter. To read more posts like this one, subscribe to the Engine Yard Newsletter.

In this series, Evan Phoenix, Rubinius creator and Ruby expert, presents tips and tricks to help you improve your knowledge of Ruby.


Ruby is a rich language that believes there should be more than one way to express yourself—the many ways of counting and iterating are no exception.

Most Ruby programmers are familiar with the most common one:

Integer#times
  100.times { |i| p i }

Integer#times counts from 0 up to 99, yielding the current number to the block. This a simple, expressive way to execute some code a number of times.

But there are cases where you want to start counting at a number other than 0, no problem:

Integer#upto
  10.upto(20) { |i| p i }

This prints out 10, 11, 12, until it hit 20. It increments by 1, and you'll notice it is inclusive, meaning that in this case we yield 11 items, not 10.

Going up is nice, but sometimes you need to go down, so use #upto's sister:

Integer#downto.
  20.downto(10) { |i| p i }

If you need a little more control over your iteration, you can use:

Range#step
  (10..20).step(2) { |i| p i }

This will print 10, 12, 14, 16, 18, 20.

Now, in this case, we've introduced a Range, which most Ruby programmers are familiar with. It is basically an object that expresses a beginning and an end — in this case, 10 and 20. Range has another trick up it's sleeve:

  (10...20).step(2) { |i| p i }

You'll notice the 3 dots instead of 2. This indicates that this range is exclusive of the end, not inclusive. So 20 is the terminator, but is not in the set of valid values itself.

Range also support #each:

  (10..20).each { |i| p i }

This works exactly the same as Integer#upto. I personally prefer Integer#upto, because I feel it expresses the operation better.

Another domain is counting on a collection. Before 1.8.7 and 1.9, there was pretty much only one method to help you with doing that: Array#each_with_index.

  [:foo, :bar, :baz].each_with_index { |sym, index| p [sym, index] }

This prints out [:foo, 0], [:bar, 1], and [:baz, 2].

This is nice, but it's pretty limiting because the only place you've got that index is with simple iteration. Say you wanted to map the Array and take the position into account — you'd have to do:

  ary = [1, 3, 5]
  i = 0
  ary.map { |element| x = element * i; i += 1; x }

It's kind of messy to just take the position into account. So with 1.8.7 and 1.9, Enumerator support was baked into most methods which makes this much simpler!

  ary = [1,3,5]
  ary.map.with_index { |element, index| element * index }

For those that haven't seen Enumerators yet, you're saying "Hey! Where did the block to map go!" Well there isn't one. Array#map, when passed no block, returns a Enumerator object. This object, when you call #each, calls the original method on the original object and passes the block along. To begin with, this provides external iteration, but it also gives Ruby a place to add iteration alteration methods, such as Enumerator#with_index. Now you never need to use a while loop again!

See you next time!

Update

1.8.7 is a bit inconsistent about when Enumerators are returned. You can instead do:

ary.dup.map!.with_index { |e,i| ... }

Or, as a commenter pointed out:

ary.to_enum(:map).with_index { |e,i| ... }

Share this post:
  • email
  • Digg
  • del.icio.us
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Google Bookmarks
  • Facebook
  • LinkedIn
  • Print
Popularity: 7% |
Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars
Loading ... Loading ...

Announcement: Engine Yard Cloud Price Reductions

By Ezra Zygmuntowicz | January 26th, 2010 at 10:01AM

Today, we’re announcing new lower pricing on Small and Medium instances for Engine Yard Cloud. Small instances now cost $0.11 per hour (down from $0.145) and Medium instances now cost $0.20 per hour (down from $0.24). This means you can run a full-time 1 ECU, 1.7GB instance on Engine Yard Cloud for less than $80 per month or a powerful 5 ECU, 1.7GB instance for $144 per month.

We’re backdating this change to January 1st, so all usage in January so far will be billed at the new lower prices. [Happy New Year!]
.
We’re able to provide this new lower pricing because of a new capability from Amazon Web Services that allows us to group all our customer accounts into a single billing entity with Amazon. So, we’re now able to use a single virtual pool of reserved instance capacity from Amazon, and pass along these savings to customers. This means all of our customers get to reap the benefits of reserved instances without the need to pay the reserved instance costs up front. No other cloud platform provider has been as aggressive as us in slashing prices as our cost goes down.
.
We also recognize and are hearing your desire for explicit reserved instance pricing and are working hard to deliver it in the future.
.
We’ve been very happy with the continuing stream of customers choosing Engine Yard Cloud as their Rails deployment platform :-) We’re hard at work bringing you new features and services that take the busywork out of your lives and make our bells and whistles as easy to use as possible. Thanks for your continued patronage!

Share this post:
  • email
  • Digg
  • del.icio.us
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Google Bookmarks
  • Facebook
  • LinkedIn
  • Print
Popularity: 7% |
Post rating: 1 vote, average: 5.00 out of 51 vote, average: 5.00 out of 51 vote, average: 5.00 out of 51 vote, average: 5.00 out of 51 vote, average: 5.00 out of 5
Loading ... Loading ...

Rails and Merb Merge: Rails Core (Part 4 of 6)

By Yehuda Katz | January 22nd, 2010 at 12:01PM

After working to make Rails faster and more modular, we took a look at the glue code holding Rails together: Railties. Railties started life as a relatively modest piece of code in the early days of Rails, but it eventually grew to encompass quite a few different areas. For instance, Rails 2.3 included quite a bit of code for managing plugins, and some additional code for managing gems. Adding in explicit support for engines meant yet more code.

In isolation, each of these pieces of code made a lot of sense as they were added, but as the pieces accumulated, plugin authors, and even Rails itself, had to often resort to contortions to get the job done. Our goal in Rails 3 was to solve these problems.

An Application Object

The first thing we addressed was that a Rails 2.3 application had pieces spread out in a number of places. For example, it stored the configuration on a global configuration object, stored the routes on a global router object, and booted up the server and console from this collection of global state. It also looked up plugins using PluginLocators and PluginLoaders. While all of this worked, we found it difficult to reason about and it was easy to trip over when implementing new features. We wanted to unify these pieces.

Second, we wanted to make it possible to run multiple applications in a single process. This meant we had to remove as much global state as possible. We will likely be continuing to work toward this goal over the next several releases, but the nice thing is that removing global state has already resulted in improvements in code quality and we have found that it makes code easier to understand.

If you take a look at a newly generated Rails 3 app, you now find a named application.

module YourApp
  class Application &lt; Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.
 
    # Add additional load paths for your own custom dirs
    # config.load_paths += %W( #{config.root}/extras )
 
    # Only load the plugins named here, in the order given (default is alphabetical).
    # :all can be used as a placeholder for all plugins not explicitly named
    # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
 
    # Activate observers that should always be running
    # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
 
    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US &amp; Canada)'
 
    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
    # config.i18n.default_locale = :de
 
    # Configure generators values. Many other options are available, be sure to check the documentation.
    # config.generators do |g|
    #   g.orm             :active_record
    #   g.template_engine :erb
    #   g.test_framework  :test_unit, :fixture =&gt; true
    # end
  end
end

This looks a whole lot like the initializer block in Rails 2.3, but the Application object, not a global configuration object, now has the configuration information. Similarly, routes.rb now begins YourApp::Application.routes.draw. The application object lives in the center of Railties in Rails 3, providing a core object that the rest of the frameworks can build on.

Improving Initialization

Rails 2.3 initialized a Rails application by calling a number of methods in sequence. Correct operation relied heavily on the order of those methods, and Rails ran some methods multiple times (apparently to hack around long-forgotten issues). As a next step towards getting a grip on Railties, we refactored the initializers so that Rails declared each initializer separately in a way that users could easily hook into.

This idea originated in Merb, where the framework defined each initializer as a separate class. Merb also provided a mechanism for declaring in the class body, that the framework should run a particular initializer before or after another one. By breaking the initializers up into reusable chunks, we provided a convenient API for extensions to hook in at whatever part of the process they wanted to.

In Rails 3, we have defined a slightly different API, inspired by the same ideas.

initializer :load_all_active_support do
  require "active_support/all" unless config.active_support.bare
end
 
# Set the <tt>$LOAD_PATH</tt> based on the value of
# Configuration#load_paths. Duplicates are removed.
initializer :set_load_path do
  config.paths.add_to_load_path
  $LOAD_PATH.uniq!
end

These first initializers in the Rails bootup process require active_support/all and set the load paths. Each initializer has a name, which allows other initializers (in plugins) to hook in at the appropriate time. Initializers also have access to the configuration object.

Making initializers their own isolated chunks definitely improves the code, and exposed a number of issues in the existing initializers. With initializers now living on the brand new application object, we then took a hard look at plugins.

Unifying Plugins

We wanted to unify plugins into one coherent system. Rails 2.3 supported several different kinds of plugins: vendor/plugins, gem plugins, vendor/plugins engines, and gem engines.

Rails 2.3 had a number of objects to find and load each kind of plugin, that resulted in a certain amount of magical behavior in each case. For instance, it automatically ran init.rb for plugins in vendor/plugins at the appropriate time. For Rails 3, we have created a new class, called Rails::Plugin, that can itself set configuration and have initializers. Rails 3 now merges in those initializers, giving plugins the ability to directly participate in the initialization process.

Each plugin in vendor/plugins automatically become a Rails::Plugin, but we also allow gems to explicitly create a subclass and add their own initialization. This makes plugins more powerful, giving them the ability to hook in at more precise parts of the process.

initializer :load_init_rb, :before =&gt; :load_application_initializers do |app|
  file   = "#{@path}/init.rb"
  config = app.config
  eval File.read(file), binding, file if File.file?(file)
end

Here, vendor/plugins defines an initializer called :load_init_rb, which runs before the application initializers. The block receives the application object, so it can take a look at the configuration, or other parts of the application in question.

Railties (Or, How Frameworks Became Plugins)

After making all of this progress, we revisited an earlier mission set out at the beginning of the merge: make all frameworks, like ActiveRecord and ActionController, behave like regular plugins. This would allow other plugins, like DataMapper, to use exactly the same APIs used by ActiveRecord. By definition, other projects could then replace all the functionality in ActiveRecord in a self-contained gem.

After moving each initializer into its own block, we noticed that a large number of the initializers were wrapped in if config.frameworks.include?(:active_record) and the like. If we fixed this, we could make good on our promise to make each framework a plugin. So now, instead of having Railties inspect the list of available frameworks and run appropriate initializers, the frameworks themselves add their own initializers, but only when loaded. When the user loads ActiveRecord, its plugin adds its initializers; when the user loads DataMapper, its plugin adds its initializers.

The monolithic Railties framework is now a set of individual Railties, each inheriting from a new class, Rails::Railtie. Railties has more power than simple plugins. For instance, a Railtie’s name also serves as its key in the configuration object. And a Railtie can also specify rake tasks. For instance, if the user chooses DataMapper instead of ActiveRecord, he only sees DataMapper’s rake tasks.

Because Railties merely specifies how Rails initializes them, requiring them has no side-effects. Taking a look at the ActiveRecord Railtie, you can see that the file first requires “rails” and “action_controller/railtie”, ensuring that Rails runs the ActionController initializers before the ActiveRecord ones. It also allows ActiveRecord to define initializers that run before or after ActionController initializers.

Exposing an API to allow ActiveRecord to run decoupled from Railties forced us to enable functionality for third parties. For instance, because Rails loads Railties before initialization, this allows third-party plugins to hook into the very earliest stages of the process. By making the keys on config more general, we now can have config.data_mapper as well as config.active_record. By moving rake tasks and generators to the frameworks, and triggering them through the Railtie, Rails guarantees that the user can remove all artifacts of these frameworks by installing an alternative plugin. It also means that any plugin can add these artifacts.

Most usefully, it moves everything about how Rails should initialize ActiveRecord in a different context, such as booting up an app, running rake tasks, or running generators, into a single location that we can more easily maintain and that others can more easily copy. We think this will result in people being able to easily write Rails extensions that behave similarly to builtin Rails frameworks.

In short, Rails frameworks are now plugins to Rails.

Share this post:
  • email
  • Digg
  • del.icio.us
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Google Bookmarks
  • Facebook
  • LinkedIn
  • Print
Popularity: 9% |
Post rating: 3 votes, average: 5.00 out of 53 votes, average: 5.00 out of 53 votes, average: 5.00 out of 53 votes, average: 5.00 out of 53 votes, average: 5.00 out of 5
Loading ... Loading ...