Question

I'm writing SaaS model application. My application database consist of two logic parts:

  • application tables - such as user, roles...
  • user defined tables (he can generate them from ui level) that can be different for each application instance

All tables are created by rails migrations mechanism.

I would like to put user defined tables in another directory:

  • db/migrations - application tables
  • db/migrations/custom - tables generated by user

so i can do svn:ignore on db/migrations/custom, and when I do updates of my app on clients servers it would only update application tables migrations.

Is there any way to achieve this in rails?

Was it helpful?

Solution 3

@Vasily thank's for your response. After reading it and couple more questions from stackoverflow I came up with this solution:

Since I write my own generator to create user tables I included Rails::Generators::Migration in it so I can override next_migration_number method like this:

def self.next_migration_number(dirname)
 if ActiveRecord::Base.timestamped_migrations
   Time.now.utc.strftime("custom/%Y%m%d%H%M%S")
 else
   "custom/%.3d" % (current_migration_number(dirname) + 1)
 end
end

Now all migrations generated by user are created in db/migrations/custom directory.

Then I wrote normal rails migration that executes all migrations from db/migrations/custom directory:

class ExecuteCustomMigrations < ActiveRecord::Migration
   MIGRATIONS_PATH='db/migrate/custom'
   def self.up
     Dir["#{MIGRATIONS_PATH}/[0-9]*_*.rb"].
     sort.map{|filename|require filename}.flatten.
     each{|class_name| const_get(class_name).up}
   end

   def self.down
     Dir["#{MIGRATIONS_PATH}/[0-9]*_*.rb"].sort.reverse.
     map{|filename|require filename}.flatten.
     each{|class_name| const_get(class_name).down}
   end
end

After user creates custom table i call this migration with this code:

Rake::Task["db:migrate:redo"].execute("VERSION=20110108213453")

OTHER TIPS

Task rake db:migrate has a hard coded path to migrations. But you can create your own rake task. For example, create lib/tasks/custom_db_migrate.rake with the following contents:

namespace :db do
  task :custom_migrate => :environment do
    ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
    ActiveRecord::Migrator.migrate("db/migrate/custom", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
    Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
  end
end

Now you can run rake db:custom_migrate to run migrations which are located in db/migrate/custom. But it will not use migrations from the default path.

You might want to read the source code for the ActiveRecord migrations.

If you're using Sinatra and building your own rake task, you can do the following:

require './app'
require 'sinatra/activerecord/rake'

ActiveRecord::Migrator.migrations_paths = 'your/path/goes/here'

When you run rake -T, you'll get the db namespace:

rake db:create_migration  # create an ActiveRecord migration
rake db:migrate           # migrate the database (use version with VERSION=n)
rake db:rollback          # roll back the migration (use steps with STEP=n)

With rails 4 we can see that migration directories are stored in an array accessed by "db/migrate"

Code snipit from activerecord/lib/active_record/migration.rb

def migrations_paths
    @migrations_paths ||= ["db/migrate"]
    # just to not break things if someone uses: migrations_path = some_string
    Array(@migrations_paths) # Data stored in an array
end

So we can add to this array with config in environment.rb, as an example

Rails.application.configure do
    config.paths["db/migrate"] << %Q{db/migrations}
    config.paths["db/migrate"] << %Q{db/migrations.custom}
end

Also, I could not find this documented, but additional directories under db/migrate also get searched and executed.

e.g. I put groups of migrations into release directories

-db/migrate
    -3.0.0
       XXXXXcreate_user.rb
    -3.0.1
       XXXXXcreate_task.rb

This mechanism is also used to add engine migration directories Discussed here

Update for Rails 5/6;

Rails 5 recommends setting additional migration paths in your config/database.yml file. It's very easy, see this example;

development:
  migrations_paths:
  - "db/migrations"
  - "db/migrations/custom"

ActiveRecord::Migrator.migrations_path= will be deprecated in Rails 6.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top