Rails Nits — Error Messages

REXML could not parse this XML/HTML: 
<i>Update: Due to a misconfiguration some of my blog entries from the
month of Feburary recently lost. This is merely a repost of the
original content.<i>

At the [Ruby User Groups meeting][] the other day someone asked me
what things I did not like about Ruby and Rails. At the time I did not
have any really good answers, which bothered me because that is an
important question. No technology is perfect and honest critiques are
a vitally important way to improve the state of the art. In that
spirit this is the first in a series to relate some issues I have with
Rails (and fixes when possible).

When everything works Rails is absolutely brilliant. However, it when
things do not go well it often yields ambiguous, vague or misleading
error messages. I have noticed this several time but today it was
driven home once again. Error messages might seem like a little thing
but a single bad error message can send a developer off in the wrong
direction wasting hours (or at least tens of minutes ).

For example, earlier today I was investigating moving our database
schema management onto ActiveRecord::Migration.[^migrations-good] I
found Jamis' [Getting Started With ActiveRecord Migrations][] article,
which is excellent. I followed all the steps but when I ran the
migrate rake task I got the following instead of the correct schema.

    pwilliams@xps:~/projects/ramps$ rake migrate 
    (in /home/pwilliams/projects/ramps) 
    rake aborted!  
    MysqlError: Table 'ramps_development.schema_info' doesn't exist: SELECT version FROM schema_info

My first thought was that I needed create the schema_info table that
the SQL above references. However, while looking for the shape that
table I find that the [ActiveRecord:Migration API documentation][]
says it does not need to be create manually.

> To run migrations against the currently configured database, use
> rake migrate. This will update the database by running all of the
> pending migrations, creating the schema_info table if missing.


At that point I was totally confused, the documentation says this
table will be created for you if necessary but the code is *not
actually creating it*.  After digging around in the
code[^reading-rails-is-complicated] a little I finally
figured that the problem was the table create code was eating the real
error message. The code that creates the schema_info table looks like
this</p>
# Should not be called normally, but this operation is non-destructive.  
# The migrations module handles this automatically.  
def initialize_schema_information 
  begin 
    execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})" 
    execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
  rescue ActiveRecord::StatementInvalid 
    # Schema has been intialized 
  end      
end

That code just tries to create the needed table. It catches any failures while executing the table creation and eats the error under the assumption that a failure to create the table means the table already exists. And therein lies the problem. I had not granted the user specified in my database.yml rights to added tables. The rails user did not need this privilege before because I was loading sql files as myself not as the rails user. It is appropriate that ActiveRecord::Migration failed, it cannot add tables if the RDBMS does not allow it to, but that error message is totally unacceptable. If the actual problem had be reported it would have taken be about 30 seconds to fix it, rather than 30 minutes.

In the spirit of improving this problem here is a patch that make ActiveRecord::ConnectionAdapters::SchemaStatements#initialize_schema_information return a better error message in this case, and possibly others. As it turns out it was actually quite easy to improve this error message. Rather than rescuing the attempt to create the table from all failures. Try to select from the tables first, if that fails then attempt to create the table but let any failure be raise up so that the user sees them. Here is a file that when added to RAILS-PROJECT-DIR/lib/tasks does the trick. Below are the entire contents of that file.

module ActiveRecord
  module ConnectionAdapters
    module SchemaStatements

      # Creates the schema_info table in preparation for using 
      # ActiveRecord::Migration. Obviously, this should not be 
      # called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
      def initialize_schema_information
        begin
          execute "SELECT COUNT(*) from #{ActiveRecord::Migrator.schema_info_table_name}"
        rescue ActiveRecord::StatementInvalid
          execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
          execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
        end
      end
    end
  end
end

I tried implementing that as a plugin but apparently plugins do not get loaded when executing the migrate rake task. That makes sense since plugins are really a Rails thing and not a Active record thing. Implementing it as a rake file meant that it gets loaded automagically before the migrate task is actually executed so the code changes are in place a the appropriate time.

This is yet another example of the power of open classes. The ability to fix bugs in the framework and libraries without having to physically patch the shipped source code of the framework or library is priceless.

Comments 13

  1. Omar Colon wrote:

    This saved me alot of headaches. Thanks!

    Posted 09 Aug 2006 at 6:46 pm
  2. Ryan wrote:

    Well written article, you also saved me a lot of time…and as I was reading, I thought to myself, Gee my rails user doesn’t have access to create tables in this database…sure enough that’s the issue. Thanks a ton.

    Posted 10 Aug 2006 at 4:29 pm
  3. Phil Crosby wrote:

    Clear description of the problem. For reference, here’s a line to grant the needed privileges

    grant create,drop on mydb_test.* to ‘myuser’@’localhost’ identified by ‘pass';

    Posted 18 Sep 2006 at 10:31 pm
  4. phil wrote:

    Thank you for your enlightenig articel. Tracing the same error message I applied your patch. But what would have saved me 30 minutes, is a check if the mysql-gem ist installed on the machine I’m working on, instead I got a misleading Lost connection to MySQL server during query: CREATE TABLE schema_info (version int(11))

    Posted 21 Sep 2006 at 1:52 pm
  5. Gareth wrote:

    Thanks – this had me really confused, until I discovered that someone had set the database up with the wrong permissions…

    A nicer error message would definitely have helped

    Posted 30 Oct 2006 at 5:54 am
  6. Ron Chaplin wrote:

    ||on 21 Sep 2006 at 1:52 pm phil
    ||
    ||Thank you for your enlightenig articel. Tracing the same error
    ||message I applied your patch. But what would have saved me 30
    ||minutes, is a check if the mysql-gem ist installed on the machine
    ||I’m working on, instead I got a misleading Lost connection to
    ||MySQL server during query: CREATE TABLE schema_info (version
    ||int(11))

    I also recieved the error message same as phil.

    Upon trying to install the mysql-gem upon my Ubuntu Machine, I did not realize that it was a win32 build. I used gem install mysql –debug.

    The Key being –debug

    After a bit more searching for Ubuntu Rails Mysql, I found that there is a lib for ruby to handle mysql.

    rake db:migrate worked flawlessly after that.
    A minor victory for me in the RonR Learning adventure!

    HTH Some1

    Ron Chaplin

    Posted 08 Nov 2006 at 12:11 am
  7. Vikas wrote:

    thanks a lot for posting this. i should have realized my rails user didn’t have create privileges! this saved me so much wasted time!

    Posted 02 Dec 2006 at 11:48 am
  8. Frank Lamontagne wrote:

    Thanks! That was my problem also.

    Posted 13 Jun 2007 at 12:42 pm
  9. Ciaran wrote:

    This post is still helping folks like me – I had this exact problem and sure enough my user didn’t have correct DB permissions. Cheers!

    Posted 24 Feb 2008 at 8:48 am
  10. Vlad wrote:

    Today I encountered the exact same issue while bootstrapping a Rails app using rails 2.0 .

    Posted 20 May 2008 at 9:05 am
  11. mtkd wrote:

    many thanks, fixed my issue

    Posted 31 Aug 2008 at 2:09 pm
  12. Fatima wrote:

    This file is not there any more, what should I call it? Does the name matter? I called it lib/tasks/fix.

    Still getting the same error.

    I have put the root username and password in the config/database.yml file so permissions should not be a problem.

    Posted 09 Sep 2009 at 6:34 pm
  13. Peter Williams wrote:

    Fatima, if permissions are not the problem then you are having a different problem than i was. I am not sure if this fix will work on modern versions of Rails. If you dropped it in and it did not change anything then it may not.

    Posted 16 Sep 2009 at 6:49 am