1. The Path to Rails 3: Greenfielding new apps with the Rails 3 beta

    Upgrading applications is good sport and all, but everyone knows that greenfielding is where the real fun is. At least, I love greenfielding stuff a lot more than dealing with old ghetto cruft that has 1,900 test failures (and 300 errors), 20,000 line controllers, and code that I’m pretty sure is actually a demon-brand of PHP.

    Building a totally new app in Rails 3 is relatively simple (especially if you’ve done it in previous Rails versions), but there a few changes that can trip you up. In the interest of not missing a step someone may need, this post is a simple walkthrough of building a new app with Rails 3. I would have simply posted about the Rails 3 version of the Getting Started guide, but it’s actually a bit out of date now. I’ve committed each step in its own commit on Github so you can step through it (the repository is here: http://github.com/jm/rails3_blog)

    An aside: Installing the Rails 3 beta

    Installing the Rails 3 beta can be sort of tricky since there are dependencies, it’s a prerelease gem, and RubyGems basically poops the bed when those two scenarios collide. Hopefully that’ll be fixed soon, but the mean time, install Rails’ dependencies like so:

    gem install rails3b
    gem install arel --pre
    
    # or if that gives you hassle...
    
    gem install i18n tzinfo builder memcache-client rack \
                rack-test rack-mount erubis mail text-format thor bundler
    

    Once all those lovely gems are installed (add --no-ri and --no-rdoc if you want to skip those/speed up your install), then install the prerelease version of Rails:

    gem install rails --pre
    

    Now you’re ready to roll on with the Rails beta!

    Using the new generator

    The application generator is basically the same with two key differences:

    So, let’s generate a blog application (really original, I know, right?):

    rails rails3_blog -d mysql
    

    If you get an error like “no value provided for required arguments ‘app_path’”, then you’ve gotten your parameters out of order. If you’d like to use another database driver, you can provide postgresql or sqlite (or nothing, since sqlite is the default). You’ll see a lot of text scroll by, and now we have a nice, fresh Rails 3 application to play with [4b6b763ac9378c6cde95b0815d2a4c2619a0e403].

    Let’s crank up the server (note that it’s different now!)…

    rails server
    

    Rails went the “Merb way” and has consolidated its many script/* commands into the rails binscript. So things like generate, server, plugin, etc. are now rails generate and so on. Once the server’s booted, navigate over to http://localhost:3000 and you should see a familiar friend:

    You're on board!

    Click on “About your application’s environment” to see more information about the app you’ve generated.

    Configuring an app

    Now comes the task of configuration. Again, not a whole ton of changes from previous versions, but navigating them can trip up the novice and journey(wo)man alike. First, setup all your database settings in database.yml; it’s just like previous versions of Rails, so no surprises there (and plenty of information abounds if you’re new to it).

    Next, pop open config/application.rb. This is where much of the configuration information that once lived in config/environment.rb now lives. The portion you probably want to pay attention to most when making a new application is the block that defines your options for ORM, template engine, etc. Here’s the default:

    config.generators do |g|
      g.orm             :active_record
      g.template_engine :erb
      g.test_framework  :test_unit, :fixture => true
    end
    

    I’m going to stick with the defaults, but you could substitute in something like :datamapper or :sequel for :active_record, :haml for :erb, or :rspec for :test_unit (once they get it working with Rails 3). Doing so will set the generators for models, views, etc. to use your tool of choice (remember that whole technology agnosticism thing?); I don’t know if all these generators are available yet, but there are some available here.

    The config/application.rb file also houses some configuration for other things.

    Other configuration bits like custom inflections, mime types, and so on have been moved out into their own initializers that you can find under config/initializers. [b613cef6f92ff7d3304da84dba530196ba51371d]

    The last big piece of configuration you’ll need to add is a Gemfile for bundler (get more information on Gemfiles and bundler here and here). We already have a basic Gemfile that has the following:

    # Edit this Gemfile to bundle your application's dependencies.
    source 'http://gemcutter.org'
    
    gem "rails", "3.0.0.beta"
    
    ## Bundle edge rails:
    # gem "rails", :git => "git://github.com/rails/rails.git"
    
    gem "mysql"
    
    ## Bundle the gems you use:
    # gem "bj"
    # gem "hpricot", "0.6"
    # gem "sqlite3-ruby", :require => "sqlite3"
    # gem "aws-s3", :require => "aws/s3"
    
    ## Bundle gems used only in certain environments:
    # gem "rspec", :group => :test
    # group :test do
    #   gem "webrat"
    # end
    

    Notice that it has added mysql as a dependency since that’s what we set as the database (or whatever driver you selected, for example, pg or sqlite). Since I want to write blog entries in Markdown, I’m going to add rdiscount as a dependency. To do so, I simply have to add this:

    gem "rdiscount"
    

    As I’ve said before, bundler is much more powerful than config.gem, and one of the great features it adds is the concept of a gem “group.” For example, let’s say I want to use mocha, but only when testing (obviously). You would add this to your Gemfile:

    group :test do
      gem "mocha"
    end
    

    Now this gem will only be added in when testing. This will also be useful for production only gems related to caching and what not. [598652fa49634eaa9d23ab8df652faf73dfd07f4]

    Next, run bundle pack if you want to vendor everything or bundle install to install the gems to system gems. After you’ve combed through this stuff and set whatever you need, you’re done configuring your application. Now on to actually building something.

    Building it out

    So, we’re going to build a very simple blog (and expand it later). First, let’s generate a scaffold for posts, since that’ll generate a lot of boilerplate code that we’ll go back and tweak:

    rails generate scaffold post title:string body:text
          invoke  active_record
          create    db/migrate/20100202054755_create_posts.rb
          create    app/models/post.rb
          invoke    test_unit
          create      test/unit/post_test.rb
          create      test/fixtures/posts.yml
           route  resources :posts
          invoke  scaffold_controller
          create    app/controllers/posts_controller.rb
          invoke    erb
          create      app/views/posts
          create      app/views/posts/index.html.erb
          create      app/views/posts/edit.html.erb
          create      app/views/posts/show.html.erb
          create      app/views/posts/new.html.erb
          create      app/views/posts/_form.html.erb
          create      app/views/layouts/posts.html.erb
          invoke    test_unit
          create      test/functional/posts_controller_test.rb
          invoke    helper
          create      app/helpers/posts_helper.rb
          invoke      test_unit
          create        test/unit/helpers/posts_helper_test.rb
          invoke  stylesheets
          create    public/stylesheets/scaffold.css
    

    Next, run rake db:migrate to create the database table for Post. Now if you go to http://localhost:3000/posts, you should see the standard scaffold interface. [8f27fe53282de70343afadaedd583ecc279d535d]

    Let’s a take a look at the controller code; you’ll see a lot of actions that look sort of like this:

    def show
      @post = Post.find(params[:id])
    
      respond_to do |format|
        format.html # show.html.erb
        format.xml  { render :xml => @post }
      end
    end
    

    That’s some clean code, but in Rails 3, we can compress down even further with the Responder. This class wraps very common rendering logic up into some really clean helpers. To use it, you’ll need to add what formats your actions respond with to the class:

    class PostsController < ApplicationController
      respond_to :html, :xml
    
      .
      .
      .
    end
    

    So your show action goes from the above to this:

    def show
      @post = Post.find(params[:id])
    
      respond_with(@post)
    end
    

    Now the action will automatically look at the state of the object, the format requested, and respond accordingly. So, for example, if you successfully create an object in create, it will redirect to show; if it fails, it will render new (this is assuming, of course, you’re requesting HTML). Of course, if you need custom logic, you’ll want to do something else, but these helpers make already clean, RESTful code even easier and cleaner. Make sure to rake to make sure you refactored it right! [53846f92393e10146fbf2d9b43b530a244d0137e]

    Next, open up config/routes.rb. It should look something like this (with oodles of extra commented out routes):

    Rails3Blog::Application.routes.draw do |map|
      resources :posts
    end
    

    To set PostController's index action to the root, we need to do two things. First, remove public/index.html otherwise it’ll always overtake any root route you set. Next, add a root route to config/routes.rb like this:

    Rails3Blog::Application.routes.draw do |map|
      resources :posts
    
      root :to => "posts#index"
    end
    

    Now going to http://localhost:3000 should show the posts index page. [120c377c8ec1c138d600f9b9bc39bedf1d43afd4] OK, so now that most of the functionality is in place, let’s make it look presentable; here’s my version of the index template:

    <% @posts.each do |post| %>
      <h2><%= link_to post.title, post %></h2>
      <p>posted at <%= post.created_at.strftime('%D') %></p>
      <p><%= post.body %></p>
    <% end %>
    
    <%= link_to 'New post', new_post_path %>
    

    You can see what other design edits I made in this commit [03b2c39d65331f7dfeb4ada89cf65604f7130e2d].

    Now we need to add the Markdown functionality to the Post model. First, let’s generate a migration [2cbb0b04411ac1712a4f5039ed93bdad0cb6e76e]:

    rails generate migration AddRenderedBodyToPosts rendered_body:text
    

    Migrate your database, and now we’re ready to move on to testing. Write a simple test to make sure it renders the body after a save [af83a5a2e85a1679896e989f6828d1f5ee4aa7d3]:

    require 'test_helper'
    
    class PostTest < ActiveSupport::TestCase
      test "renders Markdown after save" do
        post = Post.create(:title => "This post rocks.", :body => "Now *this* is an awesome post.")
    
        assert_equal "<p>Now <em>this</em> is an awesome post.</p>", post.rendered_body.chomp
      end
    end
    

    If you rake now, that test should fail. So, let’s make it pass:

    class Post < ActiveRecord::Base
      before_save :render_body
    
      def render_body
        self.rendered_body = RDiscount.new(self.body).to_html
      end
    end
    

    You should be all green [4730cd4e8601c74a05b9763d307b462f76e44b26]! Now we’ll need to go back and change the instances of body to rendered_body on the index and show views.

    That’s pretty standard Rails stuff, so let’s do something Rails 3-specific now. First, let’s add some validations; we’ll want to make sure that every post has a title and a body.

    test "requires title" do
      post = Post.create(:body => "Now *this* is an awesome post.")
      assert !post.valid?
      assert post.errors[:title]
    end
    
    test "requires body" do
      post = Post.create(:title => "This post rocks.")
      assert !post.valid?
      assert post.errors[:body]
    end
    

    Note the new API for Active Record errors (i.e., [] rather then on) [930e8868b0e4d8904d6f5090f6b445b0c428f71f]. Now, of course, we have to make them pass…

    class Post < ActiveRecord::Base
      before_save :render_body
    
      validates :title, :presence => true
      validates :body, :presence => true
    
      def render_body
        self.rendered_body = RDiscount.new(self.body).to_html
      end
    end
    

    As you probably noticed, the API for Active Record validations I’ve used here is different (the validations shown are equivalent to a validates_presence_of validation, which are still around) [178bb06839bb44978f42c922c9348bfe783da8b1]. You can read a little more about the new style of validations here. So, now if you try to create a post without a title or body, it’ll reject it.

    More later…

    I realize this introduction is extremely simple, but I’ll expand on it very soon (including authentication, commenting, post drafts, an API, spam protection, feeds, caching, etc. with a separate entry after it on deployment). I’ll get to that sort of stuff very soon, but my next post is going to be a walkthrough of upgrading an app step by step (very similar to this entry). Look for it in a few days!

    Comments

Powered by Tumblr; designed by Adam Lloyd.