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.