Adrian Serafin
Adrian Serafin

Reputation: 7715

Rails: How to create migration in subdirectory with Rails?

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

All tables are created by rails migrations mechanism.

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

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?

Upvotes: 6

Views: 6314

Answers (5)

theallseeingguy
theallseeingguy

Reputation: 71

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.

Upvotes: 4

Dave Benson
Dave Benson

Reputation: 56

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

Upvotes: 0

John Hamelink
John Hamelink

Reputation: 1086

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)

Upvotes: 4

Adrian Serafin
Adrian Serafin

Reputation: 7715

@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")

Upvotes: 2

Vasily Reys
Vasily Reys

Reputation: 596

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.

Upvotes: 7

Related Questions