sequel-data-migrate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 16896608fde97a13bbe21af4275a06a3229f57c215719b2a569a9eb974ebdf89
4
+ data.tar.gz: 453e7f4a1afd9ed0bbd9ed3398858b366f81285b57ee5dd3e6decd552c2fd852
5
+ SHA512:
6
+ metadata.gz: eac60cb9d571d250198ed6476b4a049af93652f5a00fc5b84ab02e534d4e0a78f60dad9b703d5d3dbf02c5e606d53400193c5b65460d83573a4dac81ebca5f54
7
+ data.tar.gz: e420debfc7d44f438ee5a5aa7a2447a952ebc02da7dadb2e1f5960d2cae103be93c68ba4d20b441319b5a2f3fbc970ccae5890dc647c2b5eba5596be5908f94c
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.7
6
+ NewCops: enable
7
+
8
+ Layout/LineLength:
9
+ Max: 120
10
+
11
+ Metrics/AbcSize:
12
+ Max: 50
13
+ Metrics/MethodLength:
14
+ Max: 20
15
+
16
+ RSpec/ExampleLength:
17
+ Max: 20
18
+ RSpec/MultipleExpectations:
19
+ Enabled: false
20
+
21
+ Style/Documentation:
22
+ Enabled: false
23
+ Style/StringLiterals:
24
+ Enabled: true
25
+ EnforcedStyle: double_quotes
26
+ Style/StringLiteralsInInterpolation:
27
+ Enabled: true
28
+ EnforcedStyle: double_quotes
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-04-11
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sequel-data-migrate.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rbs"
10
+ gem "sqlite3"
11
+
12
+ gem "rspec"
13
+
14
+ gem "rubocop"
15
+ gem "rubocop-rspec"
16
+
17
+ gem "byebug"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Hieu Nguyen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # SequelData::Migrate
2
+
3
+ Run data migrations along side schema migrations for Sequel. Inspired by
4
+ https://github.com/ilyakatz/data-migrate.
5
+
6
+ Data migrations are stored in db/data. They act like schema migrations,
7
+ except they should be reserved for data migrations.
8
+ For instance, if you realize you need to titleize all your titles, this is the place to do it.
9
+
10
+ The code is ported from Sequel migration extension.
11
+
12
+ ## Installation
13
+
14
+ Install the gem and add to the application's Gemfile by executing:
15
+
16
+ $ bundle add sequel-data-migrate
17
+
18
+ If bundler is not being used to manage dependencies, install the gem by executing:
19
+
20
+ $ gem install sequel-data-migrate
21
+
22
+ ## Usage
23
+
24
+ Configure your database:
25
+
26
+ ```ruby
27
+ SequelData::Migrate.configure do |config|
28
+ config.db_configuration.host = ENV.fetch('DATABASE_URL')
29
+ config.migration_path = 'db/data_migration' # default value is db/data
30
+ end
31
+ ```
32
+
33
+ Add the rake task to `Rakelib` file in your project:
34
+
35
+ ```ruby
36
+ require 'sequel_data/migrate/rake_task'
37
+ task 'data:migrate' => :environment
38
+ task 'data:rollback' => :environment
39
+ ```
40
+
41
+ Now you can migrate using `rake db:migrate` and rollback using `rake
42
+ db:rollback`.
43
+
44
+ To create new migration file, you can use `data:create_migration` rake task:
45
+
46
+ ```shell
47
+ bundle exec rake data:create_migration\[<file_name>\]
48
+ ```
49
+
50
+ ## Development
51
+
52
+ After checking out the repo, run `bin/setup` to install dependencies.
53
+ Then, run `rake spec` to run the tests.
54
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
55
+
56
+ To install this gem onto your local machine, run `bundle exec rake install`.
57
+ To release a new version, update the version number in `version.rb`,
58
+ and then run `bundle exec rake release`, which will create a git tag for the version,
59
+ push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kaligo/sequel-data-migrate.
64
+
65
+ ## License
66
+
67
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SequelData
4
+ module Migrate
5
+ class Error < StandardError; end
6
+
7
+ class ConfigurationError < Error; end
8
+
9
+ class MigrationError < Error; end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require 'fileutils'
2
+
3
+ module SequelData
4
+ module Migrate
5
+ class Generator
6
+ VERSION_FORMAT = "%Y%m%d%H%M%S"
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def create_migration(name)
13
+ version = Time.now.utc.strftime(VERSION_FORMAT)
14
+ file_name = "#{version}_#{name}.rb"
15
+ klass_name = name.split("_").map(&:capitalize).join
16
+ content = <<~CONTENT
17
+ class #{klass_name} < SequelData::Migrate::Migration
18
+ def up
19
+ end
20
+
21
+ def down
22
+ end
23
+ end
24
+ CONTENT
25
+
26
+ File.join(config.migration_path, file_name).tap do |full_path|
27
+ FileUtils.mkdir_p(config.migration_path)
28
+ File.write(full_path, content)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :config
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SequelData
4
+ module Migrate
5
+ class Migration
6
+ VALID_DIRECTION = %i[up down].freeze
7
+
8
+ # Returns the list of Migration descendants.
9
+ def self.descendants
10
+ @descendants ||= []
11
+ end
12
+
13
+ # Adds the new migration class to the list of Migration descendants.
14
+ def self.inherited(base)
15
+ descendants << base
16
+ super
17
+ end
18
+
19
+ def up
20
+ raise MigrationError, "No up method defined for migration #{self.class}"
21
+ end
22
+
23
+ def down
24
+ raise MigrationError, "No down method defined for migration #{self.class}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel"
4
+ require "sequel_data/migrate/migration"
5
+
6
+ module SequelData
7
+ module Migrate
8
+ class Migrator
9
+ DATE_TIME_PATTERN = "[0-9]" * 14 # YYYYMMDDHHMMSS
10
+ FILE_NAME_PATTERN = "#{DATE_TIME_PATTERN}_*.rb"
11
+ MUTEX = Mutex.new
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ def migrate
18
+ db = connect_database
19
+ dataset = ensure_table_exists(db)
20
+
21
+ already_migrated = dataset.select_map(column).to_set
22
+ migration_files = fetch_migration_files.reject { |file| already_migrated.include?(File.basename(file)) }.sort
23
+ migrations = fetch_migrations(migration_files)
24
+
25
+ migrations.zip(migration_files).each do |migration, file|
26
+ db.log_info("Begin applying migration file #{file}")
27
+ migration.new.up
28
+ set_migration_version(db, file)
29
+ db.log_info("Finished applying migration version #{file}")
30
+ end
31
+ end
32
+
33
+ def rollback(step = 1)
34
+ db = connect_database
35
+ dataset = ensure_table_exists(db)
36
+
37
+ already_migrated = dataset.select_map(column).to_set
38
+ migration_files = fetch_migration_files.select do |file|
39
+ already_migrated.include?(File.basename(file))
40
+ end.sort.reverse!
41
+ migrations = fetch_migrations(migration_files)
42
+
43
+ migrations.zip(migration_files).each do |migration, file|
44
+ step -= 1
45
+ break if step.negative?
46
+
47
+ db.log_info("Begin rolling back migration file #{file}")
48
+ migration.new.down
49
+ remove_migration_version(db, file)
50
+ db.log_info("Finished rolling back migration version #{file}")
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :config
57
+
58
+ def column
59
+ :version
60
+ end
61
+
62
+ def table
63
+ :data_migrations
64
+ end
65
+
66
+ def connect_database
67
+ raise ConfigurationError, "db_configuration is not set" if config.db_configuration.host.nil?
68
+
69
+ Sequel.connect(config.db_configuration.host)
70
+ end
71
+
72
+ def ensure_table_exists(db)
73
+ # we need to set this so that it can be used within create_table? block
74
+ c = column
75
+
76
+ db.from(table).tap do |dataset|
77
+ db.create_table?(table) do
78
+ String c, null: false, primary: true
79
+ end
80
+
81
+ unless dataset.columns.include?(c)
82
+ db.alter_table(table) do
83
+ add_column c, String, null: false, primary: true
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def fetch_migration_files
90
+ Dir.glob("#{config.migration_path}/#{FILE_NAME_PATTERN}")
91
+ end
92
+
93
+ # Load the migration file, raising an exception if the file does not define
94
+ # a single migration.
95
+ def load_migration_file(file)
96
+ MUTEX.synchronize do
97
+ length = Migration.descendants.length
98
+
99
+ load(file)
100
+
101
+ if length != Migration.descendants.length - 1
102
+ raise MigrationError, "Migration file #{file.inspect} not containing a single migration detected"
103
+ end
104
+
105
+ klass = Migration.descendants.pop
106
+ if klass.is_a?(Class) && !klass.name.to_s.empty? && Object.const_defined?(klass.name)
107
+ Object.send(:remove_const, klass.name)
108
+ end
109
+
110
+ klass
111
+ end
112
+ end
113
+
114
+ # Returns a list of migration classes filtered for the migration range and
115
+ # ordered according to the migration direction.
116
+ def fetch_migrations(migration_files)
117
+ migration_files.map { |file| load_migration_file(file) }
118
+ end
119
+
120
+ def set_migration_version(db, file)
121
+ db.from(table).insert(column => File.basename(file))
122
+ end
123
+
124
+ def remove_migration_version(db, file)
125
+ db.from(table).where(column => File.basename(file)).delete
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+ load "sequel_data/migrate/tasks/data.rake"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel_data/migrate"
4
+
5
+ namespace :data do
6
+ desc "Run data migrations"
7
+ task :migrate do
8
+ SequelData::Migrate.migrate
9
+ end
10
+
11
+ desc "Rollback data migrations"
12
+ task :rollback, %i[step] do |_, args|
13
+ SequelData::Migrate.rollback(args.fetch(:step, 1).to_i)
14
+ end
15
+
16
+ desc "Create a migration (parameters: NAME)"
17
+ task :create_migration, %i[name] do |_, args|
18
+ name = args[:name]
19
+
20
+ if name.nil?
21
+ puts "No NAME specified. Example usage:
22
+ `rake db:create_migration[backfill_users_email]`"
23
+ exit
24
+ end
25
+
26
+ path = SequelData::Migrate.create_migration(name)
27
+
28
+ puts "<= migration file created #{path}"
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SequelData
4
+ module Migrate
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-configurable"
4
+ require "sequel_data/migrate/version"
5
+ require "sequel_data/migrate/errors"
6
+ require "sequel_data/migrate/migrator"
7
+ require "sequel_data/migrate/generator"
8
+
9
+ module SequelData
10
+ module Migrate
11
+ extend Dry::Configurable
12
+
13
+ setting :db_configuration do
14
+ setting :host
15
+ end
16
+ setting :migration_path, default: "db/data"
17
+
18
+ def self.migrate
19
+ Migrator.new(config).migrate
20
+ end
21
+
22
+ def self.rollback(step = 1)
23
+ Migrator.new(config).rollback(step)
24
+ end
25
+
26
+ def self.create_migration(name)
27
+ Generator.new(config).create_migration(name)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ # TypeProf 0.21.2
2
+
3
+ # Global variables
4
+ $trace: false
5
+
6
+ # Classes
7
+ module SequelData
8
+ module Migrate
9
+ VERSION: String
10
+
11
+ def self.migrate: -> untyped
12
+ def self.rollback: (Integer? step) -> untyped
13
+ def self.create_migration: (String name) -> untyped
14
+
15
+ class Error < StandardError
16
+ end
17
+
18
+ class ConfigurationError < Error
19
+ end
20
+
21
+ class MigrationError < Error
22
+ end
23
+
24
+ class Generator
25
+ VERSION_FORMAT: String
26
+
27
+ def initialize: (untyped config) -> void
28
+ def create_migration: (String name) -> String
29
+
30
+ private
31
+ attr_reader config: untyped
32
+ end
33
+
34
+ class Migration
35
+ VALID_DIRECTION: [:up, :down]
36
+ self.@descendants: Array[untyped]
37
+
38
+ def self.descendants: -> Array[untyped]
39
+ def self.inherited: (untyped base) -> untyped
40
+ def up: -> untyped
41
+ def down: -> untyped
42
+ end
43
+
44
+ class Migrator
45
+ DATE_TIME_PATTERN: String
46
+ FILE_NAME_PATTERN: String
47
+ MUTEX: Thread::Mutex
48
+
49
+ def initialize: (untyped config) -> void
50
+ def migrate: -> untyped
51
+ def rollback: (?Integer step) -> untyped
52
+
53
+ private
54
+ attr_reader config: untyped
55
+ def column: -> :version
56
+ def table: -> :data_migrations
57
+ def connect_database: -> untyped
58
+ def ensure_table_exists: (untyped db) -> untyped
59
+ def fetch_migration_files: -> Array[String]
60
+ def load_migration_file: (String file) -> untyped
61
+ def fetch_migrations: (Array[String] migration_files) -> Array[untyped]
62
+ def set_migration_version: (untyped db, String? file) -> untyped
63
+ def remove_migration_version: (untyped db, String? file) -> untyped
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sequel-data-migrate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hieu Nguyen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-04-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-configurable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.13.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sequel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 4.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 4.0.0
41
+ description: Migrate data with Sequel, based on Sequel migration extension
42
+ email:
43
+ - hieu.nguyen@ascendaloyalty.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rspec"
49
+ - ".rubocop.yml"
50
+ - ".ruby-version"
51
+ - CHANGELOG.md
52
+ - Gemfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - lib/sequel_data/migrate.rb
57
+ - lib/sequel_data/migrate/errors.rb
58
+ - lib/sequel_data/migrate/generator.rb
59
+ - lib/sequel_data/migrate/migration.rb
60
+ - lib/sequel_data/migrate/migrator.rb
61
+ - lib/sequel_data/migrate/rake_task.rb
62
+ - lib/sequel_data/migrate/tasks/data.rake
63
+ - lib/sequel_data/migrate/version.rb
64
+ - sig/sequel_data/migrate.rbs
65
+ homepage: https://github.com/kaligo/sequel-data-migrate
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ homepage_uri: https://github.com/kaligo/sequel-data-migrate
70
+ changelog_uri: https://github.com/kaligo/sequel-data-migrate/blob/master/CHANGELOG.md
71
+ rubygems_mfa_required: 'true'
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 2.7.0
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.3.7
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Migrate data with Sequel
91
+ test_files: []