My Five Favorite Things About Rails 3

By Yehuda Katz | August 18th, 2009 at 11:08AM

Over the last few months Rails 3 has really begun to take shape. We’ve been hard at work building, refactoring, building, and then refactoring all over again, and I’m pretty pleased with how things are going. There’s still a lot of work to be done (we’re working on it, these things just take time :P ), but I wanted to pause for a bit and talk about some of my favorite features.

I know a lot of you are eager for information on Rails 3. While this isn’t a whole lot of code… think of it more like a movie trailer, and keep an eye out here, on the rubyonrails.org blog and on my personal blog for more information as we get closer to the release.

1. New Bundler

A lot of you have had trouble with the various efforts to make it possible to declare gem dependencies in your application and ensure that they’re available on your development and production environment. Merb had an early solution to this problem that got about 90% of the way there, but didn’t handle a large number of gems very well. This was exposed by the Engine Yard Cloud team, who had some trouble with the Merb gem bundler with a large number of potentially interdependent gems.

The new bundler handles virtually all issues we could think of around bundling gems in Rails applications, and then some.

Rubygems Resolution

This problem arises when you have multiple gems that depend on overlapping versions of some other gem. For instance, let’s say you depend on ActiveMerchant 1.4.2 and Rails 2.3.2. ActiveMerchant depends on activesupport >= 2.3.2. Rails 2.3.2 depends on activesupport = 2.3.2. Now let’s say you have Rails 2.3.2 and Rails 2.3.3 on your system.

If you go ahead and require "activemerchant", it will see the >= 2.3.2. Since you have 2.3.3 on your system, it will pull it next. Next, you gem "action_controller", "= 2.3.2". Since ActionController depends on activesupport = 2.3.2, it’ll see that 2.3.3 was already activated and fail. You’ll get the dreaded Gem::LoadError: can't activate activesupport (= 2.3.2, runtime), already activated activesupport-2.3.3.

This problem arises because gems are currently resolved linearly instead of doing an algorithmic dependency resolution. It turns out that doing dependency resolution with overlapping arbitrary ranges over a large set of potential dependencies is a hard problem. It took us a number of iterations over about a year to get it right, but our current solution is simple, elegant, reasonably fast, and most importantly, correct.

Limit Network Dependencies for Deploy

This is something we felt very strongly about with the Merb bundler, and most people seem to be coming around. The basic idea is that you should store your app’s dependencies inside your application, not require a connection to Rubyforge, Github, and who knows what else at deploy time.

In addition to the possible risk of a network resource being down, there is the risk that you declared a dependency on something like nokogiri, and a new version was released to Rubyforge since staging, which breaks your code. You could solve this by always hardcoding all of your versions, but it’s nicer to be able to declare your conceptual dependencies (”I want Nokogiri — any version”). Once you pull the latest versions of your declared dependencies into your application, they should stay put, rather than possibly changing at deploy time.

Consistent Environment

By bundling specific .gem files inside of your application, you ensure that every developer, staging, and production are all running with the same environment.

Native Gems

By storing just the .gem file in the application, we can have a command that compiles the native gems for the architecture in question.

2. ActionController Architecture

In Rails 3, ActionController::Base is built on top of ActionController::Metal, a stripped down version of ActionController with support for simple Rack semantics. We then include a series of modules to add support for things like callbacks, rendering, layouts, helpers, and on and on. This means that it’s easy for you to start with ActionController::Metal and pull in just the features you want, paying just the performance cost you want.

We even implemented deprecated 2.3 features as a module that gets mixed in to ActionController::Base. If you aren’t using any deprecated features, you can simply stop including the module and get some performance back. You also know that you’re not using any features you shouldn’t be.

3. Responder

Since we got RESTful controllers, it has been very common to see idiomatic controllers that have actions looking like this:

def create
  @user = User.new(params[:user])

  respond_to do |format|
    if @user.save
      flash[:notice] = 'User was successfully created.'
      format.html { redirect_to(@user) }
      format.xml { render :xml => @user, :status => :created, :location => @user }
    else
      format.html { render :action => "new" }
      format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
    end
  end
end

In Rails 3, this will be shrunk down to:

respond_to :html, :xml # class level

def create
  @user = User.new(params[:user])
  flash[:notice] = 'User was successfully created.' if @user.save
  respond_with(@user)
end

What we do is move the idiomatic logic into a new object called a Responder. By default, we use the Responder that ships with Rails, but you can use your own subclass the same way you would set a controller-wide (or application-wide) layout. That way, you can package up your own RESTful idioms into objects and use those.

4. ActiveModel

You’ve probably heard that things like validations are now available to any Ruby object by simply including a module: it’s true. Take a look:

class Person
  include ActiveModel::Validations

  validates_presence_of :name
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end

Person.new.valid?         #=> false
Person.new.errors         #=> {:name => ["cannot be blank"]} # localizable of course
Person.new("matz").valid? #=> true

This is pretty cool, and provides a lot of ActiveRecord functionality for other ORMs. What’s really exciting to me, however, is that ActiveModel provides an API that can be used by ORM plugins to hook directly into ActionPack. If an object complies with the ActiveModel API, it will work seamlessly with the rest of Rails, just as well as ActiveRecord works in Rails 2.3.

This requires:

  • Object#to_model: A method that returns an ActiveModel compliant version of the object. In most cases, this will return self
  • Model#new_record?: true if the record has not yet been persisted
  • Model#valid?: true if the record is valid (or the object has no concept of validations)
  • Model#errors: a valid errors object. The errors object should respond to #[](field) and #full_messages
  • Model#class.model_name: a valid naming object that responds to singular, plural, element, collection, partial_path, and human. This is used in routing, URL generation, and a number of other areas, and is possibly the most important integration point.

If your object implements these APIs, it will work with ActionPack with the same level of support as ActiveRecord. Pretty exciting!

5. Performance

Over the past several weeks, I’ve spent a lot of time working on reducing Rails’ overhead. There’s still a lot to be done, but I was able to reduce the overhead of Rails between two and five times, with especially good gains on Ruby 1.9. While this does not improve the time spent in your code, it reduces the overhead in calling partials, rendering collections, rendering templates, and rendering the entire response. This will be especially apparent for action-cached pages where most of the cost of the request is in Rails, not your request.

I’ve also started to look at general bottlenecks, like stylesheet_link_tag (surprising!) and URL generation in Paperclip, two things that bubbled up in a recent performance analysis of a real application.

Most of the improvement came from finding bottlenecks and then caching them, which can be tricky because of the possibility of dynamic changes. For instance, it is possible to modify the view_paths during any request, so it’s hard to cache view lookup, since someone might prepend a path to the lookup order. In this case, my current approach has been to take a slower path, which means that doing dynamic things in Rails 3 is noticeably more expensive than the same operations in Rails 2.3 (where none of this was cached). I’ll have some details on what kinds of things to avoid for optimal performance in a later post.

For now, until the next batch of awesomeness, there are my top upcoming features of Rails 3. Feel free to comment with questions on these or other Rails 3 features and topics. As always, I’m happy to help however I can.

This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

47 Responses to “My Five Favorite Things About Rails 3”

  1. One other feature that I'm excited about and wasn't mentioned here is the fact that "javascript helpers" will produce HTML with HTML 5's data attributes that then the javascript framework can hook into and unobstrusively apply the intended behavior. This makes the output cleaner, and easier to swap out the javascript framework (JQuery anyone?)

    Thanks for the update. Looking forward to Rails 3!

  2. Jonathan Jonathan says:

    I'm excited about mountable apps coming to rails 3 and greater support for rack. The possibility of creating an app from a bunch of small, pluggable parts is really interesting.

    @Gimenez That is something that will be awesome, too. Going along with that, it would also be interesting if helpers put out microformatted HTML as a sort of API for an app.

  3. tonyc tonyc says:

    +1 to the Responder. The old style always bugged me.

    Not so big on the paradigm of naming things "Active*", (beyond say ActiveRecord) but I guess I'll live with it.

  4. Can you describe how ActiveModel and ActiveORM will work together? Are there plans to provide more support for the many new "NoSQL" alternative data stores? The ActionPack integration steps outlined above are wonderful, but other pain points like the ActiveRecord-centricity of Rails plugins still exist.

  5. iain iain says:

    DHH talked about a new router dsl in which I'm also very interested to see what that will become. Maybe not that important, but I just love these kind of ruby tricks :)

  6. Andrew Andrew says:

    Top stuff. One thing that I haven't heard much about is rails initialisation in V3. Can you shed some light on what work is being done on it?

    • wycats wycats says:

      We're working on that right now. There's a lot of small improvements. For one, we're moving global state in general into an Application object, cleaning up dependency loading, and unifying customizable path logic. You can also hook in and add your own initializer at any POINT in the init process easily now (or delete specific initializers). This is all heading toward having multiple apps in a single process.

  7. Elmer Elmer says:

    love the Reponder & new Bundler

  8. nachokb nachokb says:

    Mountable apps will be the killer feature, hands down!

    Some word about Orchestra, Yehuda? :D

    nachokb

  9. RailsGirl RailsGirl says:

    All this sounds great. But what will be more pleasing to eyes would be reading a Rails 3 Beta date. What is the ETA for rails 3.0 Beta. You should also blog once in a while about the features completed and features under progress.

    Such blogs would be helpful to.

    RailsGirl

  10. grigio grigio says:

    I'll love mountable apps with a localization system

  11. kolach kolach says:

    The Bundler. Can it be used outside of Rails?

  12. Hi Yehuda,

    What's the current core position on tag helper generation of non self-closing tags for those of us who choose to use HTML4 rather than XHTML or HTML5? There have been a number of tickets opened in Trac and Lighthouse over the years which have been shut down, citing lack of "strictness" or "purity" in open tags. This means those of us using HTML4 have either needed to patch Rails, use a plugin, or manually write the HTML generated by helpers like image_tag, stylesheet_link_tag etc.

    It would be great if Rails core had shifted from their previous position of "if you're using HTML4, you're stuck in the dark ages".

    Cheers,

    Nathan de Vries

  13. Looks very Exciting.. All the new features will save lots of extra work to be done….. Great concepts.. I was looking for it.. eagerly waiting for Rails 3. :)

  14. Roy Quader Roy Quader says:

    Oooh, the new responder stuff looks great. I wanted to try and do something similar on my own, but I figured it would eventually be implemented in rails and I didn't want to cause maintainability problems. Good thing I waited!

  15. Jim Jim says:

    Great stuff all; I'm looking forward to playing with responders and the ActiveModel bits. The Bundler has started me thinking about ways to distribute pre-built gems for multiple architectures … but that starts feeling a bit rpm/apt/yum-ish, so my brain runs screaming in the other direction.

    You mentioned "…things that bubbled up in a recent performance analysis of a real application." It would be great to see a post about how you approach app perf analysis.

  16. sensei sensei says:

    Javascript validations?

  17. Conrad Taylor Conrad Taylor says:

    Hi, I'm interested in implementing the API in (5) but I was wondering, what's the proper way to tell ActionPack about this new ORM and should this API be implemented as a module? Anyway, examples would be greatly appreciated.

    Thanks in advance,

    -Conrad

  18. Thanks for useful post as usual.
    I would like to see all other new stuffs that left in this post and will get released as a part of rails 2.3

    Thanks,
    Sandip R~

  19. Diego Viola Diego Viola says:

    I'm interested in the modularity, like using Sequel instead of ActiveRecord, etc. The Responder looks nice too.

    Can't wait for Rails 3.

  20. wycats wycats says:

    Not necessary. HTML5 validators won't complain, and the HTML5 doctype will work on all browsers. I think people have just been burned so hard by browsers that they're afraid to even THINK that this stuff will work. But it does.

Leave a Reply