Rails, and ActiveRecord in particular, is pretty possessive in how it handles your database. But once in a while you need to play nice with strangers as well. So let’s look at a situation where we must develop on top of an existing schema with existing data in Rails.
As usual we have our own tables we develop and create ourselves through migrations. And in production there are already pre-existing tables with data that we must use in our code as well.
Once we do
db:schema:load, all the tables mentioned in
db/schema.rb will be dropped if they exist and then recreated.
So we can’t really do a schema load in production if we have pre-existing tables in our
We could avoid mentioning pre-existing tables in migrations, but once you invoke
db:migrate it dumps the current schema into
If we keep the tables we do not own from
db/schema.rb it becomes tricky to set up the test database, since it does
db:schema:load for the test environment.
My initial approach was to check out what features
And luckily there is an
ignored_tables class attribute accessor defined on
SchemaDumper which does exactly what it says: it ignores
specified tables while doing schema dumps.
So to keep pre-existing tables out of
db/schema.rb we need to add them to
For that I created an initializer called
Just to be sure we can do
rake db:schema:dump and check whether
db/schema.rb does not mention the ignored tables.
This means we can safely load our own schema in production to bootstrap things
(afterwards migrations are fine).
But our tests still can’t run. The test environment has no knowledge of the pre-existing tables we might need.
I chose to make a separate schema file for them, and load it separately.
Let’s call it
Of course writing out schema definitions by hand is tedious, I exported schema definitions from production,
ran them locally, dumped the schema with
ignored_tables temporarily empty,
then moved the definitions out of
To load this we need a new rake task. So let’s do
rails g task load_foreign_schema and define it like so:
Now we need to hook this up with
db:test:prepare, so we add this to
Now once we run
rake spec it will as usual invoke
db:schema:load, which in turn will invoke
our test environment will have everything it needs.
For development we can run
db:schema:load_foreign manually (not so elegant).
Tests are taken care of automatically by hooking into
db:schema:load can safely be run on production, and migrations used afterwards.
All the pre-existing tables are added to
db/foreign_schema.rb and appended to
By doing this we insure that
db:schema:load can be run in production without fear of dropping tables that do not belongs to us
(assuming there are no table name collisions).