All aboard! An introduction to Rails 3 Martin Streicher Software Developer Pixel, Byte, and Comma 23 March 2010 The impending release of Ruby on Rails version 3 both refines and expands the capabilities of the popular Web application framework. Offering cleaner controllers and savvier SQL queries, you can expect to write less code than before. Better yet, you can include most of the components of Rails 3 in any Ruby application. Here's a look at what's changed for the better. Over the past two years, the Ruby on Rails application framework has garnered a cottage industry of hosting and service providers, an expansive and impressive array of development tools, and a wide variety of complementary libraries—called gems and plug-ins in Ruby parlance—that increase the capabilities of the software. For example, Engine Yard and Heroku are just two companies that provide virtual and headache-free Rails software hosting; the colorfully named Oink and Bullet profile memory usage and performance, respectively; and Clearance and Sunspot provide off-the-shelf authentication and fast, indexed search. Frequently used acronyms • • • • CRUD: Create, read, update, delete HTML: Hypertext Markup Language REST: Representational State Transfer SQL: Structured Query Language Since 2007, the Rails community has grown, too. The global legion of Rails developers is vibrant, helpful, and always eager to improve the software. Indeed, it's not hyperbole to say that the community is determined to improve Rails, with coders constantly leapfrogging one another to build something better. Iteration after iteration, features quickly evolve from nascent to utilitarian to powerful to elegant and ultimately to indispensable. In many instances, gems and plugins the community finds essential are enshrined in the Rails core. Rails's named scopes—a query shorthand—followed that very trajectory, as did nested forms, a fairly recent addition that supplanted previous attempts to create and edit multiple models within the same HTML form. Indeed, perhaps the most difficult task for Rails developers is keeping pace with change. (Luckily, a number of weekly Ruby and Ruby on Rails podcasts organize and present trends and best practices.) Rails version 3, the next major release of Rails, continues the rapid advancement of the toolkit. True to its heritage, the software remains "opinionated," preferring convention over configuration. Rails's core components—RESTful routes, relationships, validations, templates, and database © Copyright IBM Corporation 2010 All aboard! An introduction to Rails 3 Trademarks Page 1 of 14 developerWorks® ibm.com/developerWorks/ abstractions—persist. However, much of the internals of those units have been rewritten or refined. Most notably, and borrowing heavily from Merb, many of Rails's essential features are no longer tightly coupled. For instance, the data-validation conveniences previously available only to a Rails application are now stand-alone components and can be included in vanilla Ruby code. Controller capabilities, such as rendering partials and templates, are also independent and can be embedded in any library. In this article, you'll take a look at Rails 3 and its many changes and additions and create a new Rails 3 application from scratch. As of the middle of February 2010, Rails 3 is a beta prerelease, and the core team is collecting patches, feedback, and documentation to prepare an official release prior to the start of summer. Nonetheless, the current incarnation of Rails 3 is sufficient for building applications and learning about the multitude of new capabilities. Big changes, little changes The number of changes in Rails 3 is too significant to recount fully here. To read a complete list along with supplemental material, consult the Rails 3 Release Notes (see Resources for a link). Here, however, are some of the alterations most likely to affect developers: • One command to rule them all. Rails 3 obsolesces the family of scripts (script/server, script/ generate, and the rest) found in each application and replaces it with a single command, aptly named rails. For example, where you previously typed ./script/console, you now type rails console. The rails command also generates new applications, as it did before. Its operation differs according to whether it's launched amid an existing Rails application. • A manifest solution for dependencies. Reconciling and resolving gem dependencies is something of a knotty problem. Gem revisions can vary from one system to another, as can the collection of available gems. With such variety, it can be difficult to widely deploy or share a Rails application. Rails 3 introduces the Bundler, a special utility for managing dependencies (thus obsolescing config.gem). You declare dependences in a catalog named Gemfile in the root of your application. The Bundler downloads and stores all the named gems. You can even "pack" the gems in your application to preclude downloads from external repositories. • Queries without the queries. Historically, Rails had made good use of domain-specific languages (DSLs) throughout—think of has_one or validates_numericality_of—with one notable exception: database queries. Certainly, Rails's dynamic finders ease the burden, but code littered with option hashes replete with :conditions, :order, and :limit are common, as are find_by_sql statements. Rails 3 incorporates relational algebra, a DSL designed to express queries. Primitives include project (to select columns), where (to express conditions), join (to specify relationships), take and skip (for limits and offsets, respectively), and group (for aggregation), among others. • Controllers sans that fussy boilerplate code. The core actions of a Rails controller —new, create, edit, update—typically do not vary, especially if the controller is largely there for CRUD operations. In fact, the output of the controller generator, ./script/generate controller, often suffices without further modification. Given those similarities, Rails 3 introduces the Responder to simplify the code further. For example, a few lines of code is all that's needed for a create action: All aboard! An introduction to Rails 3 Page 2 of 14 ibm.com/developerWorks/ developerWorks® class PostsController respond_to :html, :xml def create @post = Post.create(params[:post]) respond_with(@post) end end In this snippet, respond_with(@post) routes to show to display the new record if @post was saved successfully, or to new if the object failed validations, for example. Again, this is just a small sampling. You can find examples of these new features and more in the next section, in which you build a Rails 3 application from scratch. A first Rails 3 application To run Rails 3, your system must have either Ruby version 1.8.7 or Ruby version 1.9.2 or a newer release of the programming language and its attendant libraries and interpreter. It is also beneficial to have the Git software version control system installed on your machine, as Rails 3 and many other influential Rails projects are maintained in Git. Your system should also have a database engine, such as SQLite (version 3), MySQL, or PostgreSQL. A Web server is not required to develop a Rails application, but it's usually part of a production deployment. To create a Rails 3 application, you must have the Rails 3 prerelease gem and all its dependencies. At the moment, you can install the required components with just a few commands (see Listing 1). (Check the documentation for Rails 3 before you continue, as the specifics may change from release to release.) Listing 1. The Rails 3 prerelease gem and dependencies $ gem install rails3b Due to a rubygems bug, Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed Successfully installed 14 gems installed you must uninstall all older versions of bundler for 0.9 to work mime-types-1.16 mail-2.1.2 text-hyphen-1.0.0 text-format-1.0.0 memcache-client-1.7.8 rack-1.1.0 rack-mount-0.4.7 abstract-1.0.0 erubis-2.6.5 i18n-0.3.3 tzinfo-0.3.16 bundler-0.9.5 thor-0.13.1 rails3b-3.0.1 $ gem install arel --pre Successfully installed activesupport-3.0.0.beta Successfully installed arel-0.2.pre 2 gems installed $ gem install rails --pre Successfully installed activemodel-3.0.0.beta Successfully installed actionpack-3.0.0.beta Successfully installed activerecord-3.0.0.beta Successfully installed activeresource-3.0.0.beta All aboard! An introduction to Rails 3 Page 3 of 14 developerWorks® ibm.com/developerWorks/ Successfully installed actionmailer-3.0.0.beta Successfully installed railties-3.0.0.beta Successfully installed rails-3.0.0.beta 7 gems installed The next step is to generate the application—a small wiki, shown in Listing 2. The application creates and administers articles. Each article has a title and some prose, and you create a new article simply by creating a reference to it from the body of an existing page. A reference is any camel case word, such as TheSolarSystem or TheOscars. Note: The source code for the wiki application is available from the Download table below. Listing 2. The wiki Rails application $ rails wiki If you run ls -lR to see the contents of the application, a few new files stand out: • Gemfile is the gem manifest mentioned earlier. At a minimum, the file must contain two lines: one to point to the source of the Rails 3 beta gem and one to bundle the Rails 3 beta gem itself. You probably want a third line (at least) to connect to a database: source 'http://gemcutter.org' gem "rails", "3.0.0.beta" gem "sqlite3-ruby", :require => "sqlite3" • config/application.rb contains many of the options previously found in config/environment.rb. The latter remains but is largely deprecated. One significant addition to config/application.rb is the generators block: config.generators do |g| g.orm :active_record g.template_engine :erb g.test_framework :test_unit, :fixture => true end Your Rails 3 application can use one of a number of compatible object-relational mappers (ORM), template engines, and test frameworks. The generators block specifies your preferences for the application and invokes the proper generator for your models, views, and so on. • db/seeds.rb is not new to Rails 3, but it's important to mention, because it was added fairly recently (it was introduced in Rails version 2.3.4). If your application requires initial data to run properly, such as an administrative user, price codes, or static pages, create that data in db/seeds.rb and run the task rake db:seed. Prior to the seed file, no convention existed for initialization, and many developers put code in migrations, clouding the differentiation between creating the database and populating it. • config.ru, found in the root of each Rails 3 application, is a so-called rackup file, or a configuration for a Rack-based application. Rails 3 is a Rack application and is compatible with any Web server that also supports Rack. In general, you need not touch config.ru unless you want to add other Rack components. All aboard! An introduction to Rails 3 Page 4 of 14 ibm.com/developerWorks/ developerWorks® There are a few other new files; most, though, should seem familiar from Rails version 2.3. The config/routes.rb file serves the same purpose as before, albeit with a much-simplified and more Ruby-like flavor. You'll see an example momentarily. After you generate the application and edit Gemfile to capture your dependencies, your next step is to collect the gems your application requires. That's the job of the new utility, bundle (see Listing 3). Listing 3. Collect the required gems $ bundle installFetching source index from http://gemcutter.org Resolving dependencies Installing abstract (1.0.0) from system gems Installing actionmailer (3.0.0.beta) from system gems Installing actionpack (3.0.0.beta) from system gems Installing activemodel (3.0.0.beta) from system gems Installing activerecord (3.0.0.beta) from system gems Installing activeresource (3.0.0.beta) from system gems Installing activesupport (3.0.0.beta) from system gems Installing arel (0.2.1) from rubygems repository at http://gemcutter.org Installing builder (2.1.2) from system gems Installing bundler (0.9.7) from rubygems repository at http://gemcutter.org Installing erubis (2.6.5) from system gems Installing i18n (0.3.3) from system gems Installing mail (2.1.2) from system gems Installing memcache-client (1.7.8) from system gems Installing mime-types (1.16) from system gems Installing rack (1.1.0) from system gems Installing rack-mount (0.4.7) from system gems Installing rack-test (0.5.3) from system gems Installing rails (3.0.0.beta) from system gems Installing railties (3.0.0.beta) from system gems Installing rake (0.8.7) from system gems Installing sqlite3-ruby (1.2.5) from rubygems repository at http://gemcutter.org with native extensions Installing text-format (1.0.0) from system gems Installing text-hyphen (1.0.0) from system gems Installing thor (0.13.3) from rubygems repository at http://gemcutter.org Installing tzinfo (0.3.16) from system gems Your bundle is complete! The bundle utility, short for Bundler, downloads and installs all the gems named in Gemfile and any of those gems' prerequisites (see Listing 4). The bundle utility can also copy all those dependencies into your application, making your code base self-sufficient. Specifically, if you run bundle pack, the Bundler copies the corpus of gems to vendor/cache. Listing 4. Running the bundle utility $ bundle pack Copying .gem files into vendor/cache * bundler-0.9.7.gem * thor-0.13.3.gem * abstract-1.0.0.gem * mime-types-1.16.gem * text-hyphen-1.0.0.gem * rack-mount-0.4.7.gem * rake-0.8.7.gem * text-format-1.0.0.gem * tzinfo-0.3.16.gem All aboard! An introduction to Rails 3 Page 5 of 14 developerWorks® * * * * * * * * * * * * * * * * * ibm.com/developerWorks/ rack-test-0.5.3.gem builder-2.1.2.gem erubis-2.6.5.gem memcache-client-1.7.8.gem rack-1.1.0.gem sqlite3-ruby-1.2.5.gem i18n-0.3.3.gem activesupport-3.0.0.beta.gem arel-0.2.1.gem mail-2.1.2.gem activemodel-3.0.0.beta.gem activerecord-3.0.0.beta.gem actionpack-3.0.0.beta.gem railties-3.0.0.beta.gem actionmailer-3.0.0.beta.gem activeresource-3.0.0.beta.gem rails-3.0.0.beta.gem $ ls vendor/cache abstract-1.0.0.gem memcache-client-1.7.8.gem actionmailer-3.0.0.beta.gem mime-types-1.16.gem actionpack-3.0.0.beta.gem rack-1.1.0.gem activemodel-3.0.0.beta.gem rack-mount-0.4.7.gem activerecord-3.0.0.beta.gem rack-test-0.5.3.gem activeresource-3.0.0.beta.gem rails-3.0.0.beta.gem activesupport-3.0.0.beta.gem railties-3.0.0.beta.gem arel-0.2.1.gem rake-0.8.7.gem builder-2.1.2.gem sqlite3-ruby-1.2.5.gem bundler-0.9.7.gem text-format-1.0.0.gem erubis-2.6.5.gem text-hyphen-1.0.0.gem i18n-0.3.3.gem thor-0.13.3.gem mail-2.1.2.gem tzinfo-0.3.16.gem Think of vendor/cache as your application's own gem repository. You can move the code base anywhere and have the gem software and versions you depend on—no remote repositories required. For example, if you run bundle install after bundle pack, the gems are installed from your application repository to your system (see Listing 5). Listing 5. Installing the gems Fetching source index from http://gemcutter.org Resolving dependencies Installing abstract (1.0.0) from .gem files at /Users/strike/projects/rails3/wiki/vendor/cache Installing actionmailer (3.0.0.beta) from .gem files at /Users/strike/projects/rails3/wiki/vendor/cache Installing actionpack (3.0.0.beta) from .gem files at /Users/strike/projects/rails3/wiki/vendor/cache ... Installing thor (0.13.3) from .gem files at /Users/strike/projects/rails3/wiki/vendor/cache Installing tzinfo (0.3.16) from .gem files at /Users/strike/projects/rails3/wiki/vendor/cache Your bundle is complete! Working on the wiki To create the application, generate a scaffold for a page, create the database, seed the database with an initial page, and set up the necessary routes (see Listing 6). To keep things simple, a wiki page record is limited to a handful of fields: a title, a slug (an abbreviation of the title), a body, and timestamps to record when the page was created and when it was last modified. The title All aboard! An introduction to Rails 3 Page 6 of 14 ibm.com/developerWorks/ developerWorks® and slug are string fields; prose is a text field; and the timestamps are date and time fields. (Of course, a real wiki would have additional fields, such as the most recent author and previous revisions of the page. For brevity, this example also omits users and sessions, formatting, and any kind of authentication and authorization.) You can generate an initial model, a set of views, and a controller with the command rails generate scaffold. Listing 6. The full wiki application $ rails generate scaffold page title:string slug:string body:text --timestamps invoke active_record create db/migrate/20100221115613_create_pages.rb create app/models/page.rb invoke test_unit create test/unit/page_test.rb create test/fixtures/pages.yml route resources :pages invoke scaffold_controller create app/controllers/pages_controller.rb invoke erb create app/views/pages create app/views/pages/index.html.erb create app/views/pages/edit.html.erb create app/views/pages/show.html.erb create app/views/pages/new.html.erb create app/views/pages/_form.html.erb create app/views/layouts/pages.html.erb invoke test_unit create test/functional/pages_controller_test.rb invoke helper create app/helpers/pages_helper.rb invoke test_unit create test/unit/helpers/pages_helper_test.rb invoke stylesheets create public/stylesheets/scaffold.css If you're wondering what happened to ./script/generate, recall that it's now subsumed by the omnipotent rails command. Run rake db:create db:migrate to create the database: $ rake db:create db:migrate == CreatePages: migrating ==================================================== -- create_table(:pages) -> 0.0010s == CreatePages: migrated (0.0011s) =========================================== The wiki exists now, but it's empty. Add an initial page to serve as an anchor for all other pages. Edit the file db/seeds.rb, and write code to create a new page, as shown in Listing 7. Listing 7. The wiki anchor page Page.create( :title => 'The Marx Brothers Wiki', :slug => 'Home', :body => 'An encyclopedic guide to the Marx Brothers.') Run rake db:seed to execute the code. You can verify the page with a quick glance using rails console, as shown in Listing 8. All aboard! An introduction to Rails 3 Page 7 of 14 developerWorks® ibm.com/developerWorks/ Listing 8. Verify the anchor page $ rake db:seed (in /Users/strike/projects/rails3/wiki) $ rails console Loading development environment (Rails 3.0.0.beta) irb(main):001:0> Page.all => [#<Page id: 1, title: "The Marx Brothers Wiki", slug: "Home", body: "An encyclopedic guide to the Marx Brothers.", created_at: "2010-02-21 12:24:43", updated_at: "2010-02-21 12:24:43">] Before proceeding with the code, set up the routes. Two routes are required: a default route to find the home page and another route to find a page by its slug. Listing 9 shows the final config/ routes.rb file. Listing 9. config/routes.rb (final) Wiki::Application.routes.draw do |map| resources :pages root :to => "pages#show" end The rails generate scaffold page line in Listing 6 automatically created the route in line 2, which is RESTful. You must add the route in line 3 manually. The syntax to specify a default "root" of the site route is new in Rails 3. Line 3 says, "Map the route '/' to the 'show' method of the pages controller." The code for the show method finds the home page in the database and displays it. After adding the new root route, delete the file public/index.html to preclude conflicts: $ rm public/index.html Now, turn your attention to the page controller. The code for a controller in Rails 3 can be exceedingly spartan. Listing 10 shows the initial implementation of the controller, with a sole show method. Listing 10. A Rails 3 controller class PagesController < ApplicationController respond_to :html def show @page = Page.where( :slug => ( params[:id] || 'Home' ) ).first respond_with( @page ) end end As you can see, all the boilerplate typically found in a Rails 2 controller is missing. respond_to lists the formats the controller supports; here, it responds solely to requests for HTML. respond_with is shorthand for the logic to decide how the controller should proceed. The syntax for the query is also quite different. The lookup is an example of the Rails 3 relational algebra. You may be wondering why the first suffix is required. where and other operands that All aboard! An introduction to Rails 3 Page 8 of 14 ibm.com/developerWorks/ developerWorks® express the query do not actually cause the query to execute. Instead, the query sits idly by until the data is actually needed. This is lazy loading, or deferring the query as long as possible. first sparks an actual inquiry from the database. If you run the application now, you should see something similar to Figure 1. Figure 1. The Rails 3 wiki application Now, add more code to the controller. Listing 11 shows the complete controller. Listing 11. The complete Rails 3 controller class PagesController < ApplicationController respond_to :html before_filter :get_page, :except => [ :create ] def create respond_with( @page = Page.create( params[ :page ] ) ) end def edit end def index render :action => :show end def show @page ||= Page.new( :slug => params[ :id ] ) if @page.new_record? render :action => :new else respond_with( @page ) end end def update @page.update_attributes( params[ :page ] ) respond_with( @page ) end private def get_page @page = Page.where( :slug => ( params[:id] || 'Home' ) ).first || Page.where( :id => params[:id] ).first end All aboard! An introduction to Rails 3 Page 9 of 14 developerWorks® ibm.com/developerWorks/ end In the controller, the index method merely reflects the show action with no page identifier, thus rendering the home page. show displays a page, given an ID or a slug (the lookups for all actions are centralized in get_page, further reducing the amount of code); if a page does not exist, a new page is prepared for editing. The Page model merely validates that all its fields are present: class Page > ActiveRecord::Base validates_presence_of :body, :slug, :title end The work to translate the camel case references to links to other pages occurs in the view for the Page model. A helper function in app/helpers/pages_helper.rb does the heavy lifting, keeping the view minimal (see Listing 12). Listing 12. The camel case translation helper function module PagesHelper def wikify( page ) return '' if page.body.blank? page.body.gsub( /^([A-Z][[:alnum:]]*([A-Z][[:alnum:]]*)+)/ ) do |match| link_to( $1, :action => :show, :id => $1 ) end end end The view is typical, as shown in Listing 13. Listing 13. A typical view <p> <b>Title:</b> <%= @page.title %> </p> <p> <b>Body:</b> <%= raw wikify( @page ) %> </p> <%= link_to 'Edit', edit_page_path(@page) %> | <%= link_to 'Back', pages_path %> The raw operator is new to Rails 3. Counter to previous releases of Rails, all strings are emitted safe, stripped of HTML, by default. If you want to emit a string with HTML, you must use raw. Switching Rails Beyond the improvements and conveniences shown here, Rails 3 offers better performance than its predecessors, especially in rendering partials. You can also create your own validator classes and take advantage of more streamlined standard validations. For instance, this validation, written by Jeremy McAnally, once required four separate lines of code: All aboard! An introduction to Rails 3 Page 10 of 14 ibm.com/developerWorks/ developerWorks® validates :login, :presence => true, :length => {:minimum => 4}, :uniqueness => true, :format => { :with => /[A-Za-z0-9]+/ } The Rails Guides, the official tutorials for Rails, are currently being updated for Rails 3. You can also find extensive instruction and clever solutions on the blogs of Jeremy McAnally, Yehuda Katz, Gregg Pollack, and other community leaders (see Resources). A number of popular books are under revision for the new release, too, including the seminal "pickaxe" book, Agile Web Development with Rails (see Resources). All aboard! An introduction to Rails 3 Page 11 of 14 developerWorks® ibm.com/developerWorks/ Downloads Description Name Size Source files for the example wiki wiki.zip 120KB All aboard! An introduction to Rails 3 Page 12 of 14 ibm.com/developerWorks/ developerWorks® Resources Learn • Rails 3 release notes: Read the release notes for a comprehensive and up-to-date list of additions, enhancements, and changes in the code. • Merb: Rails 3 integrates many of the features of the Merb framework. Discover Merb's history and features. • Jeremy McAnally's blog: Read this blog for tutorials and expert insights. • Yehuda Katz's blog: Katz is a core Rails contributor and a chief architect of the integration of Merb and Rails. • Gregg Pollack's blog: Pollack hosts a weekly podcast and occasional screencasts about Rails. • Agile Web Development with Rails (Sam Ruby, Dave Thomas, David Heinemeier Hansson, et al., The Pragmatic Bookshelf, 2009): This book is an invaluable resource if you want to learn Rails. • developerWorks Web development zone: The Web development zone is packed with tools and information for Web 2.0 development. • IBM technical events and webcasts: Stay current with developerWorks' technical events and webcasts. Get products and technologies • Ruby on Rails: Visit the Rails home page to read more about Rails and to download the software. • Sunspot: Download a copy of the Sunspot text search plug-in for Rails from Github. • Clearance: This prepackaged Rails solution helps with user authentication. • Bullet: Incorporate Bullet into your application to find queries that are too lazy or too eager. • Oink: Try the aptly-named Oink to reduce the memory usage of your Rails application. • IBM product evaluation versions: Download these versions today and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®. Discuss • developerWorks blogs: Check out developerWorks blogs and get involved in the developerWorks community. All aboard! An introduction to Rails 3 Page 13 of 14 developerWorks® ibm.com/developerWorks/ About the author Martin Streicher Martin Streicher is a freelance Ruby on Rails developer and the former Editor-in-Chief of Linux Magazine. Martin holds a Master of Science degree in computer science from Purdue University, and has programmed UNIX-like systems since 1986. He collects art and toys. © Copyright IBM Corporation 2010 (www.ibm.com/legal/copytrade.shtml) Trademarks (www.ibm.com/developerworks/ibm/trademarks/) All aboard! An introduction to Rails 3 Page 14 of 14