41

I have multiple rails applications talking to the same backend and I'd like them to share some migrations.
I setup a rails engine (with enginex), I can share anything (controllers, views, models,...) but no migrations. I can't make it work !

I tried to create a file db/migrate/my_migration.rb but in my main application if I do :

  rake db:migrate

It doesn't load them.

After some googling it appears there was some recent work on this and it seems this has been merge to rails master. I'm with rails 3.0.3 do you see any way to make this work ?

Thanks !

rovermicrover
  • 1,453
  • 1
  • 15
  • 21
Mike
  • 5,165
  • 6
  • 35
  • 50
  • Here's a pretty good blog post on pre 3.1 engines just in case you can't use the newest toys. http://www.stubbleblog.com/index.php/2011/04/writing-rails-engines-getting-started/ – jacklin Nov 16 '12 at 20:43

6 Answers6

44

In rails 3.1, you can do it using this command, give that your engine name is example:

# Note that you append _engine to the name
rake example_engine:install:migrations
Nathan Long
  • 122,748
  • 97
  • 336
  • 451
Jais
  • 611
  • 7
  • 4
  • 3
    Cool, thanks Jais. Just a minor clarification: Under Rails 3.1, the first part ends in "_engine." For example, if your engine was called "gogo", you would run `rake gogo_engine:install:migrations`. – michaeldwp May 07 '12 at 23:25
35

What i do, is add an InstallGenerator that will add the migrations to the Rails site itself. It has not quite the same behavior as the one you mentioned, but for now, for me, it is good enough.

A small how-to:

First, create the folder lib\generators\<your-gem-name>\install and inside that folder create a file called install_generator.rb with the following code:

require 'rails/generators/migration'

module YourGemName
  module Generators
    class InstallGenerator < ::Rails::Generators::Base
      include Rails::Generators::Migration
      source_root File.expand_path('../templates', __FILE__)
      desc "add the migrations"

      def self.next_migration_number(path)
        unless @prev_migration_nr
          @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
        else
          @prev_migration_nr += 1
        end
        @prev_migration_nr.to_s
      end

      def copy_migrations
        migration_template "create_something.rb", "db/migrate/create_something.rb"
        migration_template "create_something_else.rb", "db/migrate/create_something_else.rb"
      end
    end
  end
end

and inside the lib/generators/<your-gem-name>/install/templates add your two files containing the migrations, e.g. take the one named create_something.rb :

class CreateAbilities < ActiveRecord::Migration
  def self.up
    create_table :abilities do |t|
      t.string  :name
      t.string  :description
      t.boolean :needs_extent      
      t.timestamps
    end
  end

  def self.down
    drop_table :abilities
  end
end

Then, when your gem is added to some app, you can just do

rails g <your_gem_name>:install

and that will add the migrations, and then you can just do rake db:migrate.

Hope this helps.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • Yes, this is what I've been doing with Rails Engines too, using the custom generator class to copy migrations, static assets, run other generators on the target app, copy template classes, and modify target app config files. – justsee Feb 18 '11 at 01:58
  • 6
    This won't be necessary for Rails 3.1. – Ryan Bigg Apr 26 '11 at 03:27
  • 2
    I'm curious how you'd handle updates to your plugin that required migration changes. – davemyron Jul 20 '11 at 22:27
  • Actually, that is not so hard at all. Add the new migration to the new gem-version, run the install-generator again: that will only install the new files, and the rails migration mechanism will handle the rest: only new migrations will be executed. – nathanvda Jul 21 '11 at 20:09
  • Since we are not on rails 3.1, we do things this way. – New Alexandria Jan 26 '12 at 18:35
  • @nathanvda , I'm trying the update you described above a couple years ago, but am getting "Another migration is already named X" on the first one and it fails after that. Ideas? – Jamon Holmgren Mar 21 '13 at 20:54
  • Euh, then I presume there already is a generator with name "InstallGenerator", for most gems that is pretty standard. So you either extend it, or you add a new generator (rename it). Does that help? – nathanvda Mar 22 '13 at 09:06
23

Under 3.1, you can share migrations, without installing them, by altering config/application.rb to do something like this:

# Our migrations live exclusively w/in the Commons project
config.paths['db/migrate'] = Commons::Engine.paths['db/migrate'].existent
Levi
  • 4,628
  • 1
  • 18
  • 15
  • 5
    This worked great for me in Rails 3.1.3. A far more preferable approach than the other answers. I tried going one step further and adding the config line directly to the Engine class itself inside my gem (one less thing to do when installing the gem in a new app). I know it's getting read when I run db:migrate as it throws errors if I have typos. However, once typos were fixed, `rake db:migrate` didn't do anything. – Jon Garvin Jan 12 '12 at 22:50
  • How did you do to avoid putting the engine db path in every project? y added this line `config.paths['db/migrate'] = paths['db/migrate'].existent` to the Engine main class, but nothing happeneded – JGutierrezC Jan 20 '21 at 21:33
22

As of Rails 3.1 looks like the solution is:

bundle exec rake railties:install:migrations

If you only want to copy from a specific railtie then:

bundle exec rake railties:install:migrations FROM=foo_engine

Note the name is whatever you gem is named plus _engine. So if the gem is "foo" then the name is foo_engine.

Eric Anderson
  • 3,692
  • 4
  • 31
  • 34
18

For rails 4 use:

   initializer :append_migrations do |app|
      unless app.root.to_s.match root.to_s
        config.paths["db/migrate"].expanded.each do |expanded_path|
          app.config.paths["db/migrate"] << expanded_path
        end
      end
    end

https://content.pivotal.io/blog/leave-your-migrations-in-your-rails-engines

Mauricio Pasquier Juan
  • 2,265
  • 3
  • 27
  • 39
montrealmike
  • 11,433
  • 10
  • 64
  • 86
  • 2
    How do you handle this in engine development, where you need to run tasks in the context of the `test/dummy` app, in which `app.root` always does match `root`? (Another commenter raised the same question in that Pivotal post, but didn't get an answer.) – David Moles Feb 20 '16 at 00:18
  • @DavidMoles I sometimes just make another rails app not in test/dummy that is the same exact thing but is separate from the gem – Matthias Lee Apr 07 '22 at 02:09
13

To go off of Levi's answer you could also do something like this in your engine file in the actually engine, instead of the application.

So in lib/commons/engine.rb

module Commons
  class Engine < Rails::Engine
    initializer "commons.load_app_instance_data" do |app|
      Commons.setup do |config|
        config.app_root = app.root
      end
      app.class.configure do 
        #Pull in all the migrations from Commons to the application
        config.paths['db/migrate'] += Commons::Engine.paths['db/migrate'].existent
      end
    end
    initializer "commons.load_static_assets" do |app|
      app.middleware.use ::ActionDispatch::Static, "#{root}/public"
    end
  end
end

Edit: Be careful though to not mess up people migration history after doing this, make sure you add a new migration if a change is needed, otherwise you might force someone to do some ugly rollbacks.

rovermicrover
  • 1,453
  • 1
  • 15
  • 21