Category: Tutorials

  • Getting started with Streamlined (part 1)

    If you were one of the unfortunate souls who missed RailsConf this year, I’m sorry for you. If you happened to be at RailsConf but missed Justin Gehtland’s excellent Streamlined tutorial then you are truly of all people most pitiable.

    On a more serious note, the tutorial was very good and has sparked a lot of interesting ideas for further enhancing Streamlined beyond its already hefty feature set. For those who don’t know, Streamlined is a Rails plugin that brings the declarative goodness of ActiveRecord to the UI. It enhances your views with full-featured scaffolds that include relationship management, quick adding of associated models, and much more.

    If you haven’t played with Streamlined before, why not give it a try? With several fresh screencasts over at the blog, a bundle of new documentation, a new sample project, and version 1.0 on the horizon… there’s no time like the present.

    Getting started with Streamlined is easy…

    (more…)

  • Turn finders into associations and get caching for free

    Let’s say we have a method on a model that looks something like this:

    def last_assignment
      assignments.find(:first, : order => 'created_at DESC')
    end
    

    We call this method several dozen times from various other methods on the model. The problem is, every time we call the method a new database query is triggered. This happens even if we make multiple calls within the same method. For example:

    def last_assignment_is_old?
      last_assignment && last_assignment.completed_at && last_assignment.completed_at < 30.days.ago
    end
    

    This results in three identical queries to the database. How wasteful! Let's fix it:

    def last_assignment
      @last_assignment ||= assignments.find(:first, : order => "created_at DESC")
    end
    

    Ahhhh, this is much better. Now we're storing the result as an instance variable on the model. Our last_assignment_is_old? method will only trigger a single call to the database now since we're caching the result. But what happens if we do something like this?

    puts model.last_assignment.nil?
    model.assignments.clear
    puts model.last_assignment.nil?
    

    On the console, we should see false and then true, right? What we actually see is false and false. Even though we clear out the assignments from the model, the last assignment is still stored in the @last_assignment instance variable. Since we're dealing with the same instance of the model, the cached value is returned. In this particular case, that's not good!

    The way to get around this is by using an association:

    has_one :last_assignment, :class_name => "Assignment", : order => "created_at DESC"
    

    This association basically says, "Sort the assignments by the creation date and give me the first one in the list." After running our test code again, we're golden. It turns out that Rails' associations provide caching automatically. All we have to do is remember to call reload on our model before querying it again. This will ensure that we're working with fresh data:

    puts model.last_assignment.nil?
    model.assignments.clear
    puts model.reload.last_assignment.nil?
    

    Now we get false and then true as expected. The reload trick won't work with instance variable caching, unfortunately, which is why the association is a better choice in this case.

    After examining a few more of our models, we discover that there are many such finder methods that can be converted to associations. We quickly begin converting them, hoping nobody notices that we've been senselessly operating without freebie caching for so long.

  • Overriding existing Rake tasks

    I added some long-running integration tests to a Rails application today and quickly began getting irritated that issuing the rake command runs all tests… unit, functional, AND integration. Since I run rake quite frequently, any sizable delay can quickly get annoying.

    The task that gets executed by rake is the :test task. After spending a few minutes trying to replace it, I discovered that there isn’t an immediately obvious way to override an existing task in Rake. After jumping through a few hoops, though, I did manage to do it.

    First, here’s my replacement for the existing :test task:

    task :test do
      Rake::Task["test:units"].invoke rescue got_error = true
      Rake::Task["test:functionals"].invoke rescue got_error = true
      raise "Test failures" if got_error
    end
    

    All it does is run the unit and functional tests, but no integration tests. I stuck this in my Rakefile right after the require 'tasks/rails' line. Next up, I reopened the Rake::TaskManager module to create my own little helper method to remove a task:

    Rake::TaskManager.class_eval do
      def remove_task(task_name)
        @tasks.delete(task_name.to_s)
      end
    end
    

    Lastly, I called this method from another method defined inside my Rakefile. This way, I could use syntax like remove_task :test which would fit with my other task definitions in the file. This is how everything looks put together (remember that this code should be inserted immediately after the require 'tasks/rails' line or it won’t work):

    Rake::TaskManager.class_eval do
      def remove_task(task_name)
        @tasks.delete(task_name.to_s)
      end
    end
    
    def remove_task(task_name)
      Rake.application.remove_task(task_name)
    end
    
    # Override existing test task to prevent integrations
    # from being run unless specifically asked for
    remove_task :test
    task :test do
      Rake::Task["test:units"].invoke rescue got_error = true
      Rake::Task["test:functionals"].invoke rescue got_error = true
      raise "Test failures" if got_error
    end
    

    This did the trick for me, but it’s kind of long. Anyone know a better way of doing it?

  • Show database migration versions with Rake

    Something I frequently need to do when deploying a Rails application is determine the current migration version of a database. In the past, I’ve done this by bringing up mySQL and typing:

    SELECT version FROM schema_info

    Well, I’m finally tired of doing that so I cooked up this Rake task:

    namespace :db do
      desc "Print current database migration version to the console"
      task :version => :environment do
        puts "VERSION=#{ActiveRecord::Migrator.current_version}"
      end
    end
    

    To install, copy and paste into lib/tasks/common.rake (or wherever you store your custom Rake tasks). Then fire up a terminal window and feel the power:

    $ rake db:version
    (in /Users/matt/project)
    VERSION=125
    

    I’m surprised this isn’t already in Rails. Sure, there’s script/about, but I always forget to use it. It also spits out other information I don’t typically need to know.

    Why care about the migration version in the first place? When you’re on a project with multiple Rails deployments (dev/test/prod) and multiple developers creating migrations in multiple branches, it comes in quite handy.

  • Converting Mike’s Capistrano Cadillac into a Camero

    Mike Clark made a recent post on his blog about how to handle Rails versions with Capistrano. Very nice stuff. I’ve put his code samples to immediate use with an app I’m preparing to convert to Rails 1.2.1.

    I’ve run into some trouble this week, though, as I want to update one of my client projects that still runs version 1.1.6 of Rails. Mike’s Capistrano task takes a revision number so checking out a fresh copy with his script looks something like this.

    rake deploy_edge REVISION=5990 RAILS_PATH=/path/to/shared/rails
    

    This checks out 1.2.1 into the specified directory. After spending a few minutes trying to find the revision number for 1.1.6 (and chatting briefly with my Campfire peeps about the best way to proceed) I’ve decided that it’s safer to use tags instead of revisions. Deploys now look like this:

    rake deploy_edge TAG=rel_1-1-6 RAILS_PATH=/path/to/shared/rails
    

    Much more readable. The only downside, of course, is losing revision granularity. I’m essentially tied to the whim of the Core team as to when they decide to create new tags. But on the other hand, most of my projects aren’t currently running on Edge so this is suitable for my needs at the moment.

    I’ve included my rewrite of the task below. Just drop it in common.rake or wherever you prefer to put your custom tasks. Reading Mike’s post would be a good idea too since he explains how to get this working with a remotely deployed Rails application.

    (more…)

  • Rails test results without the cruft

    Ever notice the cruft you get when running Rails tests from the command line? It’s horrible, especially if your project has more than a dozen test files:

    /opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb"
    "test/unit/activity_test.rb" "test/unit/address_test.rb" "test/unit/application_helper_test.rb"
    "test/unit/course_calculations_test.rb" "test/unit/course_test.rb" "test/unit/course_type_test.rb"
    "test/unit/credit_hour_test.rb" "test/unit/email_test.rb" "test/unit/exam_test.rb" "test/unit/gender_test.rb"
    "test/unit/grade_test.rb" "test/unit/level_test.rb" "test/unit/notifier_test.rb" "test/unit/numeric_test.rb"
    "test/unit/obfuscator_test.rb" "test/unit/order_test.rb" "test/unit/school_test.rb" "test/unit/string_test.rb"
    "test/unit/student_test.rb" "test/unit/transcript_test.rb" "test/unit/user_test.rb" 
    Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
    Started
    ..................................................................
    Finished in 2.509903 seconds.
    
    66 tests, 140 assertions, 0 failures, 0 errors
    

    M@ McCray recently posted a hack to the Terralien Campfire that gives you a nice, clean summary instead:

    Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
    Started
    ..................................................................
    Finished in 2.855885 seconds.
    
    66 tests, 140 assertions, 0 failures, 0 errors
    

    Want to start enjoying this super slim summary yourself? Just stick this code in your project’s Rakefile right before the require 'tasks/rails' bit:

    Rake::TestTask.class_eval do
      alias_method :crufty_define, :define
      def define
        @verbose = false
        crufty_define
      end
    end
    

    Much better! We’re both surprised something like this isn’t already available in Core.

  • Introduction to rcov, code coverage for Ruby

    Do you routinely test your code? Are you using rcov to run code coverage reports for your tests? If you aren’t, you should be. rcov is a code coverage tool for Ruby that is available as a Gem or a Rails plugin. It can generate text or HTML reports outlining what percentage of your code base is being exercised by your tests. The HTML reports even allow you to drill down and see exactly which lines in your code are being touched. If you’ve ever used a tool like Cobertura for Java, you’ll know exactly what to expect with rcov.

    Using rcov on a regular basis will enable you to isolate parts of your codebase that aren’t being tested very well (if at all). For example, the first time I ran rcov on one of my projects, it reported that the tests written against my Rails models touched 87% of the code. Not too shabby! However, my Rails controllers only had 45% coverage. Where do you think I concentrated my testing efforts after rcov was kind enough to inform me of these facts?

    Installing and using rcov with an existing Rails project is a cinch. Let’s get started:

    gem install rcov
    

    Now move to the root of your Rails project and type:

    script/plugin install http://svn.codahale.com/rails_rcov
    

    Voila! Installation complete. Don’t you just love Rails? Now let’s fall in love with Rake:

    rake test:units:rcov
    rake test:functionals:rcov
    

    Running these tasks will generate a text-based report of your project’s code coverage. The percentage of each class covered is listed, along with a total for the entire set of tests (unit vs. functional). Rcov also creates a /coverage directory beneath the root of your Rails project. Inside this directory, you’ll find some beautiful HTML-based coverage reports.

    Want to report on just your model classes? What about controllers? Easy enough:

    rake test:units:rcov SHOW_ONLY="app/models"
    rake test:units:rcov SHOW_ONLY="app/controllers"
    

    Delightful. Now I’d be set if only rcov integrated with Zen’s autotest to display a coverage report that automatically updates every time I change some code. One other minor nitpick is that, much as it seems like rake test:rcov should work, it doesn’t. Something else to add to a future release, I suppose.

    Are you in a team environment? Hook rcov up to a continuous integration system and catch Jonny slacking off on his tests… a full half hour before the morning stand-up meeting.

    For the price, you can’t beat what rcov provides. It won’t tell you if your tests are logically correct, but it sure as heck will scream at you if you’re not writing them to begin with.

  • Infinite loop while configuring SVK on Mac OS X

    I attempted to install SVK on my Mac using DarwinPorts yesterday and ran into a strange problem. SVK appeared to compile just fine, but the installer eventually got to a step where it said “Configuring SVK…” and then it just froze. I could still do things on the system, but both my processor cores were maxed out. I let the computer sit there for a while, but the installer never recovered. It looked something like this:

    matt@matthew:~/ sudo port install svk
    --->  Configuring svk
    

    After hitting Ctrl-C to get out, the cores went back to normal.

    Some research on Google turned up this ticket that appeared to document exactly what was going on. According to the ticket, SVK needed the latest copy of p5-pathtools or else it would enter an infinite loop while configuring itself.

    First I uninstalled my current copy of p5-pathtools with:

    sudo port uninstall p5-pathtools

    I also cleaned up any leftover files from p5-pathtools and the previous attempt to install SVK:

    sudo port clean svk
    sudo port clean p5-pathtools
    

    I then installed a new copy of p5-pathtools, making sure to pass the -f flag to force an overwrite of any existing files:

    sudo port install -f p5-pathtools

    Finally, I ran the SVK install again, passing the -f flag just in case:

    sudo port install -f svk

    It worked this time. The configuration proceeded normally and finishing within about a minute. I was then able to access the SVK tool from the command line without any difficulty.

    Update: I recently purchased a MacBook and discovered that following the steps above did not fully solve the infinite loop problem for me. I also had to configure CPAN for my system, after which the SVK installation proceeded normally. This is something to keep in mind if the procedure above doesn’t work for you.

  • Using protected attributes in your Rails models

    Let’s say we’ve created a simple profile page to allow our users to update their e-mail address, locale, time zone, and various other settings. This profile page exposes only those model attributes that we want the user to be able to update. Other attributes on the model should not be updateable by the user.

    We opt to surface the updateable attributes on the profile page and assume that the user won’t be able to update the remaining attributes since they won’t even appear on the page. But does that really provide enough protection?

    Our model and database schema are defined as follows:

    class User < ActiveRecord::Base
    end
    
    create_table :user |t|
      t.column :username, :string  t.column :password, :string
      t.column :fname, :string
      t.column :lname, :string
      t.column :email, :string
      t.column :permission, :string
    end
    

    Our model can store a username, password, first and last name, e-mail address, and permission level. The permission level is used to determine what the user is allowed to do on the site. A permission level might be "Moderator" or "Admin."

    Our profile form surfaces the attributes that we want to be updateable:

    <%= start_form_tag %>
      <%= text_field :user, :fname %> First Name
    <%= text_field :user, :lname %> Last Name
    <%= text_field :user, :email %> E-mail Address
    <%= submit_tag %> <%= end_form_tag %>

    It's obvious that we only want the user to update his first and last name and e-mail address. Now let's examine the action being called when we submit the profile form:

    def update
      @user = User.find(params[:id])
      if @user.update_attributes(params[:user])
        redirect_to :action => "success"
      end
    end
    

    Our action looks up a user, updates its attributes with the new values passed on the request, and redirects to a new action if the update was successful. Simple enough, yet the code above could allow a malicious user to update the permission level of his account... something we certainly don't want him doing! Assuming the action is not protected against GET requests, we could simply visit the following URL in our browser to give the user a different permission level:

    http://localhost:3000/user/update/1?user[permission]=Admin
    

    Protecting the action against GET requests would prevent the attack from occuring via a URL, but the attacker could easily generate a POST request through other means. Rails provides a way to protect against such attacks: the #attr_protected class method:

    class User < ActiveRecord::Base
      attr_protected :permission
    end
    

    Now if we attempt our attack again, the permission level simply doesn't get updated. The other attributes sent on the request are assigned correctly, but the permission level isn't. Why not?

    It turns out that #attr_protected makes it impossible to update the permission attribute through the #update_attributes method (or any other mass assignment method for that matter). The only way we can update the permission level now is by using single-assignment techniques such as:

    @user.update_attribute(:permission, "Admin")
    

    It's generally a good idea to protect attributes that could cause a security breach in your system. These might include usernames, passwords, software keys, and even product prices. While by no means a silver bullet, #attr_protected is still a good tool to use as you begin locking down sensitive parts of your application.

    #attr_protected accepts multiple attributes so we could rewrite our model again as:

    class User < ActiveRecord::Base
      attr_protected :username, :password, :permission
    end
    

    #attr_protected also has a more conservative cousin, #attr_accessible. It renders all attributes on the model unavailable for mass assignment unless they are specifically named. This differs from #attr_protected which lets you start in an "all-open" state and only restrict attributes as necessary. Both methods are handy to have in your toolbox.