0.0
No release in over 3 years
A simple gem to help you perform data migrations in your Rails app. Define the work and a verification step steps, if any of them fail, your migration will be rolled back.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

>= 7.1
 Project Readme

🛃 Data Customs

Warning

This project is in early development. The API may change before reaching version 1.0.0.

A simple gem to help you perform data migrations in your Rails app. The premise is simple: bundle the migration code along with a verification test to assert that the migration achieves the desired effect. If either the migration or the verification step fails, the entire migration is rolled back.

Can't I just test my rake task or migration script?

Great question. Yes, you should test your migration code (including the Data Customs migrations). The problem is that production data is always different from your test data, and in unexpected ways. This means that even if your tests pass, the migration might still fail when run in production.

Data Customs provides a safety net by ensuring that if the migration doesn't do what you expect it to do, the changes are rolled back. This way, you can investigate the issue and fix it without leaving your data in a half-migrated (or bad!) state.

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add data_customs

If bundler is not being used to manage dependencies, install the gem by executing:

gem install data_customs

Usage

Generating a data migration

You can create a new data migration by running:

rails generate data_migration MigrationName

That will create a file at db/data_migrations/migration_name.rb.

Writing a data migration

After generating a migration, implement the up method with the code that performs the data migration, and the verify! method with the code that asserts that the migration was successful.

class AddDefaultUsername < DataCustoms::Migration
  def up
    User.where.missing(:username).update_all(username: "guest")
  end

  def verify!
    if User.exists?(username: nil)
      raise "Some users still have no usernames!"
    end
  end
end

AddDefaultUsername.run # runs the migration and rolls back if necessary

Use any exception to indicate failure in the verify! method.

Tip

Include modules like RSpec::Matchers or Minitest::Assertions in your migration class to use your favorite assertions and matchers inside verify!.

If you need to pass arguments to the data migration, you can use an initializer and run will forward any arguments to it.

class AddDefaultUsername < DataCustoms::Migration
  def initialize(default_username "guest")
    @default_username = default_username
  end

  def up
    User.where.missing(:username).update_all(username: @default_username)
  end

  def verify!
    if User.exists?(username: nil)
      raise "Some users still have no usernames!"
    end
  end
end

AddDefaultUsername.run("anonymous")

Dealing with large datasets

The migration code runs inside a transaction, so be careful when dealing with large datasets, as it will block the database for the duration of the transaction.

Data Customs provides a few helpers to make working in batches easier and automatically throttles the migration to avoid overwhelming the database.

class AddDefaultUsername < DataCustoms::Migration
  def up
    batch(User.where.missing(:username)) do |relation|
      relation.update_all(username: "guest")
    end
  end
end

Or, if you need access to each individual record:

class AddDefaultUsername < DataCustoms::Migration
  def up
    find_each(User.where.missing(:username)) do |record|
      # do something with record
    end
  end
end

For both methods, you can configure the batch size and the pause between batches:

class LongMigration < DataCustoms::Migration
  def up
    batch(records, batch_size: 500, throttle_seconds: 0.1) do |relation|
      # ...
    end

    find_each(records, batch_size: 500, throttle_seconds: 0.1) do |record|
      # ...
    end
  end
end

Running a data migration in the command line

These migrations don't run automatically. You need to invoke them manually. Since they might take some time, you can choose the best time to run them.

If you want to run a data migration from the command line, you can use the data_customs:run task. It accepts the migration class name (either in PascalCase or snake_case) as an argument:

rails data_customs:run NAME=AddDefaultUsername
# or
rake data_customs:run NAME=add_default_username

If you need to pass arguments to the migration, you can do so by passing them as additional arguments to the task:

rails data_customs:run NAME=AddDefaultUsername ARGS="anonymous"

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/thoughtbot/data_customs.

This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

Open source templates are Copyright (c) thoughtbot, inc. It contains free software that may be redistributed under the terms specified in the LICENSE file.

Code of Conduct

Everyone interacting in the DataCustoms project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About thoughtbot

thoughtbot

This repo is maintained and funded by thoughtbot, inc. The names and logos for thoughtbot are trademarks of thoughtbot, inc.

We love open source software! See our other projects. We are available for hire.