Month: September 2010

  • During the initial phase of development of a Rails application I don’t use migrations as migrations but as table definitions. Until I deploy I feel free to modify the migration files as much as I want and I have one per table.

    The downside of that is that the only way to apply the changes is to destroy all the tables and re-build them. I’ve explained how to do that in my post really resetting the database. The nice side effect of doing this is that you end up with a task that sets sample data to work with.

    Being able to quickly set up sample data or download production data is very important. It helps new developers getting started with the project but it also allows you to play much more freely with the project, do destructive actions and then in a quick command have system reset to a known state. Once you have sample data you’ll probably become as addictive as I am to reseting.

    But the truth is that 90% of the time you reset your data, you don’t need to nuke the database and re-create all records, you just need to delete all records and this is the code I use to do that:

    def destroy_data
      puts "==  Data: Destroying all data ".ljust(79, "=")
      sql = ActiveRecord::Base.connection()
    
      sql.execute "SET autocommit=0"
      sql.begin_db_transaction
    
      if sql.adapter_name == "MySQL"
        sql.execute("/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */")
        sql.execute("/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */")
      end
    
      tables = sql.tables - ["schema_migrations"]
    
      tables.each do |table|
        puts "-- Deleting all for #{table}."
        # So far, disabling and enabling keys was not needed.
        #sql.execute("/*!40000 ALTER TABLE `#{table}` DISABLE KEYS */") if sql.adapter_name == "MySQL"
        record_count = sql.delete("DELETE FROM `#{table}`")
        #sql.execute("/*!40000 ALTER TABLE `#{table}` ENABLE KEYS */") if sql.adapter_name == "MySQL"
        puts "   -> done: #{record_count} reconds"
      end
    
      if sql.adapter_name == "MySQL"
        sql.execute("/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */")
        sql.execute("/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */")
      end
    
      sql.commit_db_transaction
    
      puts "==  Data: Destroying all data (done) ".ljust(79, "=") + "\n\n"
    end
    

    Note that I’m not deleting anything in the table schema_migration. The output is more or less like this:

    ==  Data: Destroying all data =================================================
    -- Deleting all for blogs.
       -> done: 4 reconds
    -- Deleting all for posts.
       -> done: 10 reconds
    -- Deleting all for users.
       -> done: 11 reconds
    ==  Data: Destroying all data (done) ==========================================
    

    I also have some nice code to generate sample data, but that is for another post.

  • If you are getting this error:

    ActionView::Template::Error: undefined method `authenticate?' for nil:NilClass
    

    in your call to Devise’s user_signed_in? or similar, you probably forgot to add this:

    class ActionController::TestCase
      include Devise::TestHelpers
    end

    at the bottom of the test_helper.rb file. Not that that would ever happen to me…

  • Rails come with some awesome assertion methods for writing tests:

    assert_difference("User.count", +1) do
      create_a_user
    end
    

    That asserts that the count of user was incremented by one. The plus sign is not needed, that’s just an integer, I add it to make things clear. You can mix several of this expressions into one assert_difference:

    assert_difference(["User.count", "Profile.count"], +1) do
      create_a_user
    end
    

    That works as expected, it asserts that both users and profiles were incremented by one. The problem I have is that I often found myself doing this:

    assert_difference "User.count", +1 do
      assert_difference "Admin.count", 0 do
        assert_difference "Message.count", +3 do  # We send three welcome messages to each user, like Gmail.
          create_a_user
        end
      end
    end
    

    That looks ugly. Let’s try something different:

    assert_difference("User.count" => +1, "Admin.count" => 0, "Message.count" => +3) do
      create_a_user
    end
    

    Well, that looks nicer, and straightforward, so I implemented it (starting from Rails 3 assert_difference):

    def assert_difference(expressions, difference = 1, message = nil, &block)
      b = block.send(:binding)
      if !expressions.is_a? Hash
        exps = Array.wrap(expressions)
        expressions = {}
        exps.each { |e| expressions[e] = difference }
      end
    
      before = {}
      expressions.each {|exp, _| before[exp] = eval(exp, b)}
    
      yield
    
      expressions.each do |exp, diff|
        error = "#{exp.inspect} didn't change by #{diff}"
        error = "#{message}.\n#{error}" if message
        assert_equal(before[exp] + diff, eval(exp, b), error)
      end
    end
    

    Do you like it? If you do, let me know and I might turn this into a patch for Rails 3 (and then let them now, otherwise they’ll ignore it).

    Update: this is now a gem.

  • I always dislike setting up cron jobs for web applications. The whole application lives in /var/www but I manually create this single line somewhere else that I have to remember to setup if I switch servers, and turn off if needed, and maintain accordingly. Well, it so happens that it can be done much better with the tools we already have: the usual cron and capistrano (or any other similar release tool).

    In Restraq, a project I’m working on, I created a file named crontab in the config directory with these contents:

    MAILTO=pupeno@restraq.com
    */5 * * * * www-data cd /var/www/restraq/current && RAILS_ENV=production rake mail:get
    

    Cron can read from some predefine files but it also reads every file in /etc/cron.d/. That’s a very common Linux pattern, the files in the directory with the .d extension are read as if they were one big configuration file. Other programs do it too.

    Then it’s only a matter of installing it there on deployment. At first I’ve tried with a symlink but it seems cron ignores symlinks so I decided to copy it with this line on my deploy.rb file:

    run "#{try_sudo} cp #{current_path}/config/crontab /etc/cron.d/restraq"
    

    and that’s it. No more fragmentation. Whenever, wherever I install my web app, there goes the cron jobs.

  • In Ruby on Rails there’s a very easy way to create a select tag:

    form.select("period", [["Month", 1], ["Year", 12]])

    In my case I have the options in a hash, like:

    periods = {
      1 => "Month",
      12 => "Year"
    }

    but when I did this:

    form.select("period", periods)

    I was surprised to find out that the keys of the hash are used as the content of the options, and the values as the keys, producing this code:

    <select id="resource_period" name="resource[period]">
      <option value="Month">1</option>
      <option value="Year">12</option>
    </select>

    Definitely not what I wanted, so I wrote this method:

    class ApplicationController < ActionController::Base
      helper_method :hash_for_select
    
      def hash_for_select(hash, sort_by = :value)
        sort_by = sort_by == :value ? 0 : 1
        options = hash.map { |k, v| [v, k] }.sort { |x, y| x[sort_by]  y[sort_by] }
      end
    

    and now I can do

    form.select("period", hash_for_select(periods))

    if I want the options sorted by key value or

    form.select("period", hash_for_select(periods, :key))

    if I want them sorted by keys.