dekiru-data_migration 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2aedd5e4be5d28fb840f19ea8599d4398345b4ae861410719d550aa4342a55b
4
- data.tar.gz: a36038e89f49f415b58556f190a1199882b58628df39671f6ca56dfc5ed57885
3
+ metadata.gz: c5428a8cf5e2a02ff63e8b5a7d3dd2703cf471f60a7bf1dfdd289418a93f1bca
4
+ data.tar.gz: d0b1afb0f2d5bad6112849c8f046e90c309a19eddde473bde1cf179e8e2a395a
5
5
  SHA512:
6
- metadata.gz: d116ce6bb08682d5d23c3942b06e8f27dc898a4593d6520efb89c0d5dfa319ad317dec6adc2ef8816dd60fb39ce7f5f058762e0e48cdfc3e716601b3f54e039f
7
- data.tar.gz: 5c6d29334eb32a2debd4d44e2c445431a5d6632de181a8cc05403dcb613264e66d1bef53755d3cfaaa1486d0d16c5bb8d5cb049250e97046229e5b269c43ea6d
6
+ metadata.gz: 30e87b497f402579cac1d212f0cc9e12ba4bf8bcc84166de33dbbf9619f9da734c5d468d115801db0b326cffc7dfe09e4ca21b1b7e2c818ecf4848e18bf7c894
7
+ data.tar.gz: f5a5b31881fe7485d18ffea31950d70b4a5a05a7f5defd44197ec87b78c454a8db707be376825fc28a010c9f95d0c9584beb50f14965cc508fa211602d4bd226
data/README.md CHANGED
@@ -50,6 +50,60 @@ Dekiru::DataMigration::Operator.execute('Grant admin privileges to users') do
50
50
  end
51
51
  ```
52
52
 
53
+ ## Data Migration Class (Recommended)
54
+
55
+ You can also define migration logic as a class, which makes testing easier:
56
+
57
+ ```ruby
58
+ # scripts/20230118_demo_migration.rb
59
+ class DemoMigration < Dekiru::DataMigration::Migration
60
+ def migration_targets
61
+ User.where("email LIKE '%sonicgarden%'").where(admin: false)
62
+ end
63
+
64
+ def migrate_record(user)
65
+ user.update!(admin: true)
66
+ end
67
+
68
+ def migrate
69
+ super
70
+ log "Updated user count: #{User.where(admin: true).count}"
71
+ end
72
+ end
73
+
74
+ DemoMigration.run
75
+ ```
76
+
77
+ ### Testing Migration Classes
78
+
79
+ The class-based approach makes it easy to write unit tests:
80
+
81
+ ```ruby
82
+ # spec/migrations/demo_migration_spec.rb
83
+ RSpec.describe DemoMigration do
84
+ let(:migration) { described_class.new }
85
+
86
+ describe '#migration_targets' do
87
+ it 'returns correct migration targets' do
88
+ create_list(:user, 3, email: 'test@sonicgarden.jp', admin: false)
89
+ create_list(:user, 2, email: 'other@example.com', admin: false)
90
+
91
+ targets = migration.migration_targets
92
+ expect(targets.count).to eq(3)
93
+ expect(targets.all? { |u| u.email.include?('sonicgarden') }).to be true
94
+ end
95
+ end
96
+
97
+ describe '#migrate_record' do
98
+ it 'updates user to admin' do
99
+ user = create(:user, admin: false)
100
+ expect { migration.migrate_record(user) }
101
+ .to change { user.reload.admin }.from(false).to(true)
102
+ end
103
+ end
104
+ end
105
+ ```
106
+
53
107
  Execution result:
54
108
  ```
55
109
  $ bin/rails r scripts/demo.rb
@@ -98,7 +152,7 @@ Are you sure to commit? (yes/no) > yes
98
152
 
99
153
  ## Generating Maintenance Scripts
100
154
 
101
- You can generate maintenance scripts that use `Dekiru::DataMigration::Operator` with the generator. The filename will be prefixed with the execution date.
155
+ You can generate maintenance scripts that use `Dekiru::DataMigration::Migration` with the generator. The filename will be prefixed with the execution date.
102
156
 
103
157
  ```bash
104
158
  $ bin/rails g maintenance_script demo_migration
@@ -109,6 +163,29 @@ Generated file example:
109
163
  # scripts/20230118_demo_migration.rb
110
164
  # frozen_string_literal: true
111
165
 
166
+ class DemoMigration < Dekiru::DataMigration::Migration
167
+ def migration_targets
168
+ # 移行対象を返すActiveRecord::Relationを定義
169
+ # 例: User.where(some_condition: true)
170
+ raise NotImplementedError, 'migration_targets method must be implemented'
171
+ end
172
+
173
+ def migrate_record(record)
174
+ # 個別レコードの更新処理を定義
175
+ # 例: record.update!(some_attribute: new_value)
176
+ raise NotImplementedError, 'migrate_record method must be implemented'
177
+ end
178
+ end
179
+
180
+ DemoMigration.run
181
+ ```
182
+
183
+ ### Legacy Block-based Approach
184
+
185
+ For backward compatibility, you can still use the block-based approach:
186
+
187
+ ```ruby
188
+ # scripts/legacy_demo.rb
112
189
  Dekiru::DataMigration::Operator.execute('demo_migration') do
113
190
  # write here
114
191
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dekiru
4
+ module DataMigration
5
+ # Base class for data migration with testable method separation
6
+ class Migration
7
+ def self.run(options = {})
8
+ migration = new
9
+ title = migration.title
10
+
11
+ Operator.execute(title, options) do
12
+ migration.instance_variable_set(:@operator_context, self)
13
+ migration.migrate
14
+ end
15
+ end
16
+
17
+ def title
18
+ self.class.name.demodulize.underscore.humanize
19
+ end
20
+
21
+ def migrate
22
+ targets = migration_targets
23
+
24
+ log "Target count: #{targets.count}"
25
+ confirm?
26
+
27
+ find_each_with_progress(targets) do |record|
28
+ migrate_record(record)
29
+ end
30
+
31
+ log "Migration completed"
32
+ end
33
+
34
+ def migration_targets
35
+ raise NotImplementedError, "#{self.class}#migration_targets must be implemented"
36
+ end
37
+
38
+ def migrate_record(record)
39
+ raise NotImplementedError, "#{self.class}#migrate_record must be implemented"
40
+ end
41
+
42
+ private
43
+
44
+ def confirm?
45
+ if @operator_context
46
+ @operator_context.send(:confirm?)
47
+ else
48
+ # Default behavior during test (no confirmation)
49
+ puts "Confirmation skipped in test mode"
50
+ end
51
+ end
52
+
53
+ def log(message)
54
+ if @operator_context
55
+ @operator_context.send(:log, message)
56
+ else
57
+ # Default behavior during test
58
+ puts message
59
+ end
60
+ end
61
+
62
+ def find_each_with_progress(scope, options = {}, &block)
63
+ if @operator_context
64
+ @operator_context.send(:find_each_with_progress, scope, options, &block)
65
+ else
66
+ # Default behavior during test (no progress bar)
67
+ scope.find_each(&block)
68
+ end
69
+ end
70
+
71
+ def each_with_progress(enum, options = {}, &block)
72
+ if @operator_context
73
+ @operator_context.send(:each_with_progress, enum, options, &block)
74
+ else
75
+ # Default behavior during test (no progress bar)
76
+ enum.each(&block)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dekiru
4
4
  module DataMigration
5
- VERSION = "0.2.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "data_migration/version"
4
4
  require_relative "data_migration/transaction_provider"
5
+ require_relative "data_migration/migration"
5
6
  require_relative "data_migration_operator"
6
7
 
7
8
  module Dekiru
@@ -14,8 +14,11 @@ class MaintenanceScriptGenerator < Rails::Generators::NamedBase
14
14
  source_root File.expand_path("templates", __dir__)
15
15
 
16
16
  def copy_maintenance_script_file
17
+ @filename_date = filename_date
18
+ @class_name = "#{name.classify}#{@filename_date}"
19
+
17
20
  template "maintenance_script.rb.erb",
18
- "#{Dekiru::DataMigration.configuration.maintenance_script_directory}/#{filename_date}_#{file_name}.rb"
21
+ "#{Dekiru::DataMigration.configuration.maintenance_script_directory}/#{@filename_date}_#{file_name}.rb"
19
22
  end
20
23
 
21
24
  private
@@ -2,6 +2,20 @@
2
2
 
3
3
  using Dekiru::DataMigration::DangerousMethodGuard
4
4
 
5
- Dekiru::DataMigration::Operator.execute('<%= @name %>') do
6
- # write here
5
+ class <%= @class_name %> < Dekiru::DataMigration::Migration
6
+ def title
7
+ '<%= @name %>'
8
+ end
9
+
10
+ def migration_targets
11
+ # Define ActiveRecord::Relation that returns the target migration
12
+ # User.where(some_condition: true)
13
+ end
14
+
15
+ def migrate_record(record)
16
+ # Define the update process for individual records
17
+ # user.update!(some_attribute: new_value)
18
+ end
7
19
  end
20
+
21
+ <%= @class_name %>.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,23 @@
1
+ module Dekiru
2
+ module DataMigration
3
+ class Migration
4
+ def self.run: (?Hash[Symbol, untyped] options) -> bool
5
+
6
+ def title: () -> String
7
+
8
+ def migrate: () -> void
9
+
10
+ def migration_targets: () -> untyped
11
+
12
+ def migrate_record: (untyped record) -> void
13
+
14
+ private
15
+
16
+ def log: (String message) -> void
17
+
18
+ def find_each_with_progress: (untyped scope, ?Hash[Symbol, untyped] options) { (untyped) -> void } -> void
19
+
20
+ def each_with_progress: [T] (Enumerable[T] enum, ?Hash[Symbol, untyped] options) { (T) -> void } -> void
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dekiru-data_migration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SonicGarden
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-24 00:00:00.000000000 Z
10
+ date: 2025-06-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -54,6 +54,7 @@ files:
54
54
  - Rakefile
55
55
  - lib/dekiru/data_migration.rb
56
56
  - lib/dekiru/data_migration/dangerous_method_guard.rb
57
+ - lib/dekiru/data_migration/migration.rb
57
58
  - lib/dekiru/data_migration/operator.rb
58
59
  - lib/dekiru/data_migration/transaction_provider.rb
59
60
  - lib/dekiru/data_migration/version.rb
@@ -62,6 +63,7 @@ files:
62
63
  - lib/generators/maintenance_script/maintenance_script_generator.rb
63
64
  - lib/generators/maintenance_script/templates/maintenance_script.rb.erb
64
65
  - sig/dekiru/data_migration.rbs
66
+ - sig/dekiru/data_migration/migration.rbs
65
67
  - sig/dekiru/data_migration/operator.rbs
66
68
  homepage: https://github.com/SonicGarden/dekiru-data_migration
67
69
  licenses: