dekiru-data_migration 0.2.0 → 1.1.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: 342d506a7d85aa5b54c2023c5bdd2f13d7ccc968e562bde836571461e67541c8
4
+ data.tar.gz: a30d3c9877ac51fac63dd7a0d69333750e453cd5020ae398b1361428d1185a68
5
5
  SHA512:
6
- metadata.gz: d116ce6bb08682d5d23c3942b06e8f27dc898a4593d6520efb89c0d5dfa319ad317dec6adc2ef8816dd60fb39ce7f5f058762e0e48cdfc3e716601b3f54e039f
7
- data.tar.gz: 5c6d29334eb32a2debd4d44e2c445431a5d6632de181a8cc05403dcb613264e66d1bef53755d3cfaaa1486d0d16c5bb8d5cb049250e97046229e5b269c43ea6d
6
+ metadata.gz: 19478e294f25f04c5757bd1118f9786bd285b1fbb802a9f91aed81db88fb37503c03ef954c30c6e3d1a7c4476c32a6378c499092a68f786ff5dfb17a4903f2d8
7
+ data.tar.gz: bc354a7e66c1960dde53c49ded487dd4ad4a8b9d6cd0e1852f82c8b94f20bdb08f6565329ee751de9622cefd984fa9f3a7771b9feeb71d945e9aa144874d0049
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
@@ -213,3 +290,21 @@ Executes `find_each` with a progress bar for ActiveRecord scopes.
213
290
 
214
291
  ### `each_with_progress(enum, options = {}, &block)`
215
292
  Executes processing with a progress bar for any Enumerable objects.
293
+
294
+ ## Agent Skills
295
+
296
+ This repository provides an agent skill for creating data migration scripts.
297
+
298
+ ### Available Skills
299
+
300
+ #### `data-migration-script`
301
+
302
+ Automatically creates data migration and deletion scripts using the `dekiru-data_migration` gem.
303
+
304
+ **Triggers**: The agent will use this skill when you ask to "create a data migration script", "create a script to delete unnecessary records", or similar requests involving DB operations via scripts (bulk updates, deleting orphaned records, deleting ActiveStorage files, etc.).
305
+
306
+ **Install**:
307
+
308
+ ```bash
309
+ gh skill install SonicGarden/dekiru-data_migration
310
+ ```
@@ -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.1.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
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: data-migration-script
3
+ description: Skill for creating data migration and deletion scripts using the dekiru-data_migration gem in the social-apartment project. Always use this skill when asked to "create a script using dekiru-data_migration", "create a data migration script", "create a script to delete unnecessary records", or "turn this into a script". Use proactively for DB operations via scripts such as deleting orphaned records after feature removal or code changes, bulk data updates, and deleting ActiveStorage files.
4
+ license: MIT
5
+ ---
6
+
7
+ # Data Migration Script Creation
8
+
9
+ Create data migration and deletion scripts using the `dekiru-data_migration` gem.
10
+
11
+ ## Preliminary Research
12
+
13
+ Before writing a script, confirm the following:
14
+
15
+ 1. **Understand the changes**: Review related Issues and PRs to understand what was deleted or changed
16
+ 2. **Identify target records**: Check the schema (`db/schema.rb`) and current model code to identify the conditions for records that need to be deleted or updated
17
+
18
+ ## Script Creation Steps
19
+
20
+ ### 1. Generate a file with the generator
21
+
22
+ ```bash
23
+ bin/rails generate maintenance_script <PascalCaseName>
24
+ ```
25
+
26
+ - `<PascalCaseName>` is the name describing the operation in PascalCase (e.g., `DeleteDeliveryNotificationImages`)
27
+ - Generated file: `scripts/YYYYMMDD_delete_delivery_notification_images.rb`
28
+ - Today's date (8 digits) is automatically appended to the class name
29
+
30
+ ### 2. Edit the generated file
31
+
32
+ Implement `migration_targets` and `migrate_record` in the generated file.
33
+
34
+ ### Common Operation Patterns
35
+
36
+ **Deleting ActiveStorage attachments**:
37
+ ```ruby
38
+ def migration_targets
39
+ ActiveStorage::Attachment.where(record_type: 'ModelName', name: 'attachment_name')
40
+ end
41
+
42
+ def migrate_record(record)
43
+ record.purge # synchronously delete attachment and blob
44
+ end
45
+ ```
46
+
47
+ **Updating record attributes**:
48
+ ```ruby
49
+ def migrate_record(record)
50
+ record.update!(attribute: new_value)
51
+ end
52
+ ```
53
+
54
+ **Deleting records**:
55
+ ```ruby
56
+ def migrate_record(record)
57
+ record.destroy!
58
+ end
59
+ ```
60
+
61
+ **Conditional skip**:
62
+ ```ruby
63
+ def migrate_record(record)
64
+ return if record.some_condition?
65
+ record.update!(...)
66
+ end
67
+ ```
68
+
69
+ ## Execution and Verification Commands
70
+
71
+ ```bash
72
+ # Check target record count (before execution)
73
+ bin/rails runner "p TargetModel.where(...).count"
74
+
75
+ # Run the script
76
+ bin/rails runner scripts/YYYYMMDD_description.rb
77
+
78
+ # Verify after execution
79
+ bin/rails runner "p TargetModel.where(...).count"
80
+ ```
81
+
82
+ ## Notes
83
+
84
+ - `purge` deletes synchronously (immediately removes from storage). Use `purge_later` for async deletion
85
+ - `migration_targets` must return an ActiveRecord relation (processed in batches via `find_each`)
86
+ - Always verify the target record count before running in production
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.1.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: 2026-05-04 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,7 +63,9 @@ 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
68
+ - skills/data-migration-script/SKILL.md
66
69
  homepage: https://github.com/SonicGarden/dekiru-data_migration
67
70
  licenses:
68
71
  - MIT