6

I need to reuse the same method across many migrations. My goal is to avoid code duplication. I tried to do it as shown below, by putting the shared method into file lib/migration_helper.rb and using include MigrationHelper in the migrations that use the shared method.

Is there a more standard way of sharing code in different migrations?

In particular, I put the helper file into lib directory - is this the correct place?

## lib/migration_helper.rb

# Methods shared across migrations.

module MigrationHelper
  def my_shared_method
    # some shared code
  end
end
## db/migrate/do_something.rb

class DoSomething < ActiveRecord::Migration[5.2]
  include MigrationHelper
  # rubocop:disable Metrics/MethodLength
  def up
    # some code
    my_shared_method
  end
  # rubocop:enable Metrics/MethodLength

  def down
    # more code
    my_shared_method
  end

SEE ALSO:

I got a few ideas from these questions, but they do not fully answer my question:
Custom helper methods for Rails 3.2 Migrations
Rails share code between migrations (aka concerns)
Accessing custom helper methods in rails 3 migrations

This repo has examples of a much more complex version of what I want, with a whole hierarchy of helpers. I need a simpler solution:
https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/database/migration_helpers.rb
https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/db/migrate/20220808133824_add_timestamps_to_project_statistics.rb

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
  • 3
    A general pattern which most Ruby on Rails projects follow for organizing the mixins is to place them in a folder called "concerns" in whichever subdirectory the concern needs to be operated in. So I think, here you can place your mixins in `db/migrate/concerns` folder which seems like a good approach. There are other ways you can achieve the same, but as far "standard" goes, concerns are a pretty standard way of sharing code. – Ghouse Mohamed Jan 13 '23 at 15:45
  • 1
    @GhouseMohamed Thank you! Do you mean something like this: https://stackoverflow.com/a/38174009/967621 ? – Timur Shtatland Jan 13 '23 at 16:09
  • 1
    Yes, something like that – Ghouse Mohamed Jan 13 '23 at 16:15

1 Answers1

2

just tested, works in rails 7 (and probably earlier version)

What you could do is:

1 - create your file/class kinda wherever

  • app/lib/migration/something.rb
  • db/concerns/something.rb
  • ...
# db/concerns/create_column_alias.rb
module CreateColumnAlias
  def create_column_alias(*args)
    add_column(*args)
  end
end

2 - create an initializer to inject your new helper in the migrations classes (per this gist)

# initializers/extend_migration_with_custom_helpers.rb

require_relative "../../db/concerns/create_column_alias"

ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, CreateColumnAlias)

3 - profit.

>$ bin/rails g my_migration

# db/migrate/123345456_my_migration.rb
class MyMigration < ActiveRecord::Migration[7.0]
  def change
    create_column_alias :tasks, :done, :boolean
  end
end

edit

In case you don't want them to be included everywhere, you can skip the serializer and do

require_relative "../concerns/create_column_alias"

# db/migrate/123345456_my_migration.rb
class MyMigration < ActiveRecord::Migration[7.0]
  include CreateColumnAlias

  def change
    create_column_alias :tasks, :done, :boolean
  end
end

Though I suggest you not to do that and save you the trouble. It's ok to have all your helpers available at any time even if you don't use them, especially given this has 0 impacts on production rapidity (only the deployment part, and it's super minimal like if you have 100 helpers you will lose only a few seconds)

Nathan Gouy
  • 1,072
  • 9
  • 19
  • How can I control which migrations use which concern? I need to share the code across many, but not all, migrations. To accomplish this, in my question, I use `include MigrationHelper`. In [this answer](https://stackoverflow.com/a/38174009/967621), it is done also with `include` for the concern inside the migration code. – Timur Shtatland Jan 17 '23 at 20:47
  • 1
    edited my response Though I'm not sure why you would trouble yourself not having that once and for all – Nathan Gouy Jan 17 '23 at 22:56
  • 1
    Now I realize my response is now pretty much the same thing you ended up with in your original code anyway. Long story short there are 3 places where you could put this kind of things ------ /app/lib/* -> PRO is that it's autoloded ------ /lib/* -> not autoloaded but PRO more separated from your application code (like if you were using a gem or else) ----- /db/concerns/* -> It follows a certain standard that you can find for models or controllers that is widespread (we used it very often here) – Nathan Gouy Jan 18 '23 at 14:43
  • 1
    Probably it will be more elegant if autoload folder with such modules – mechnicov Jan 18 '23 at 23:38