anchor_migrations 0.1.5

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 886e3d44b9b0a3bc3147c4f4eca63e86047a9cb83bd8f69d54a7912b8e922608
4
+ data.tar.gz: ceb5f6cb450ff6971f12dc3d663d4ab002b35d7564db793842b3198e86cead7b
5
+ SHA512:
6
+ metadata.gz: 00b8f6345f4ffe5ebb17d2df959696a408e6044109f7f185e6e525b515e9390fa0ac25b8dbb4a7a7969eb5795e352904ba2f32299a5e4cfbc60708153485f235
7
+ data.tar.gz: e22efbb42d0c23d7cadeed1a06212c49fe6603564d249e05b3aa3e0cf83fb328f5a3a1812c3c517c86fb6c80470ff8b222de026f539d4d77b085f5c16aa6bed6
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.3
3
+ Exclude:
4
+ - 'db/migrate/**/*'
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Metrics/MethodLength:
13
+ Max: 40
14
+
15
+ Metrics/AbcSize:
16
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.5] - 2025-07-11
4
+
5
+ Anchors aweigh! This is a brand new gem with very limited usage. It's in an experimental status for the time being.
6
+
7
+ - 0.1.x to version preceding the one above were yanked due to bugs
8
+ - Initial release, tested end to end in a Rails app
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Andrew Atkinson
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,175 @@
1
+ # ⚓ Anchor Migrations
2
+ Anchor Migrations are SQL DDL, non-blocking, idempotent, and augment the normal ORM migrations process.
3
+
4
+ ## Commands
5
+ ```sh
6
+ anchor init # initialize directories
7
+ anchor generate # generate an empty versioned .sql file, to be filled in
8
+ anchor lint # safety-lint all .sql files using Squawk
9
+ anchor backfill # Backfill a Rails migration from the SQL
10
+ anchor migrate # Run the Anchor Migration DDL
11
+ ```
12
+
13
+ ## Installation
14
+ Add `anchor_migrations` to your `development` group Gemfile
15
+ ```rb
16
+ group :development do
17
+ gem 'anchor_migrations'
18
+ end
19
+ ```
20
+ Then run `bundle install`.
21
+
22
+ ## Preconditions
23
+ Anchor Migrations are restricted and opinionated for now, expecting a few things:
24
+ - Postgres only, 13+
25
+ - `DATABASE_URL` environment variable is set to the database to migrate (e.g. production), and is reachable, in order to apply migrations
26
+ - `psql` client accessible in PATH
27
+ - [Squawk](https://squawkhq.com) executable ([Quick Start documentation](https://squawkhq.com/docs/)) accessible in PATH
28
+
29
+ ## Safety linting and lock_timeout
30
+ Squawk is used on SQL migrations to check for unsafe operations. For example, creating an index or dropping an index without using CONCURRENTLY is detected by Squak. Anchor Migrations will require safety-linted SQL, although right now it’s up to the developer to run `anchor lint` in their workflow.
31
+
32
+ When Anchor Migration SQL is ready to apply, a psql client connection is used for that. By default a 2 second `lock_timeout`[^docs] is set.
33
+
34
+ ## What problems do Anchor migrations solve?
35
+ 1. Anchor Migrations are an additional mechanism to release safe DDL changes that don’t have code dependencies, while keeping all databases in sync using ORM migrations.
36
+
37
+ Anchor Migrations are a process for organizations not using Trunk Based Development[^tbd] or frequent releases, to allow safe DDL to get released more frequently.
38
+
39
+ Because Anchor Migrations generate the ORM (Active Record) migration *from* the SQL, Rails migrations stay in sync.
40
+
41
+ ## Example Anchor Migration SQL
42
+ By default, Anchor Migrations are stored in `db/anchor_migrations` as `.sql` files:
43
+ ```sql
44
+ -- anchor_migrations/20250623173850_create_index_tbl_col.sql
45
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
46
+ idx_trips_created_at ON trips (created_at);
47
+ ```
48
+
49
+ Squawk runs on the SQL file above for "safety linting", looking for unsafe patterns.
50
+
51
+ Run it using anchor migrations with the `lint` command:
52
+ ```sh
53
+ ~/app ➜ bundle exec anchor lint
54
+
55
+ Found 0 issues in 1 file 🎉
56
+ ```
57
+
58
+ ## Example ORM (Active Record) Migration
59
+ This Rails migration was generated from the Anchor Migration SQL file above.
60
+
61
+ For this example, Strong Migrations is used, so the `safety_assured` block it expects is added to the Rails migration.
62
+ ```rb
63
+ #
64
+ # ################################################
65
+ # DO NOT EDIT, generated by Anchor Migrations
66
+ # Version: 20250623173850
67
+ # File: anchor_migrations/20250623173850_anchor_migration.sql
68
+ # ################################################
69
+ #
70
+ class CreateIndexIdxTripsCreatedAt < ActiveRecord::Migration[7.2]
71
+ disable_ddl_transaction!
72
+
73
+ def change
74
+ safety_assured do
75
+ execute <<-SQL
76
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_trips_created_at ON trips (created_at);
77
+ SQL
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ ## Configuration
84
+ Currently, limited configuration is supported.
85
+
86
+ Anchor Migrations supports generating Strong Migrations-compatible Active Record migrations:
87
+ ```rb
88
+ # config/initializers/anchor_migrations.rb
89
+ AnchorMigrations.configure do |config|
90
+ config.use_strong_migrations = true
91
+ end
92
+ ```
93
+
94
+ ## Example PR Workflow
95
+ For a PR, add:
96
+ 1. The Anchor Migration SQL file.
97
+ 1. The normal Rails migration files: Run `db:migrate` like normal to apply the migration. The developer submits the migration file and the diff to `db/structure.sql` or `db/schema.rb` like they normally would.
98
+ 1. Get an "approval" describing your plans to run `bundle exec anchor migrate`. The approval can be a comment in the PR from a team member.
99
+ 1. With an approval, run `anchor migrate` and capture the output (see below). Once applied, move the SQL migration into `db/anchor_migrations/applied` and update your PR.
100
+ 1. Get PR approval and merge it in. The Rails migration "backfills" the DDL, applying it wherever it's needed.
101
+
102
+ Example output of `bundle exec anchor migrate`:
103
+ ```
104
+ Applying Version: 20250623173850
105
+ CREATE INDEX CONCURRENTLY IF NOT EXISTS
106
+ idx_trips_created_at ON trips (created_at);
107
+ STDOUT:
108
+ CREATE INDEX
109
+ STDERR:
110
+ Success!
111
+ Applied Version: 20250623173850
112
+ Exit code: 0
113
+ ```
114
+ The idempotent Rails migration applies anywhere it's needed and the Rails app is deployed. Other developer databases, lower environments, CI, etc. When it reaches production, the Anchor Migration SQL DDL was already applied, so nothing happens. The Rails migration is idempotent.
115
+
116
+ ## Good uses of Anchor Migrations
117
+ ### Query support, data integrity, data quality
118
+ Indexes (and eventually constraints) that support query performance or data integrity, but have no code dependencies, can be changed at a faster cadence, while keeping everything consistent.
119
+
120
+ Indexes and constraints improve performance and data quality, and arguably shouldn’t be "blocked" by needing to wait for ORM migrations.
121
+
122
+ ### Long running DDL changes
123
+ On large tables, creating indexes concurrently can take a long time. It's nice to perform that during a low activity period, requiring control over the timing, which isn't always possible with ORM migrations.
124
+
125
+ ## Anchor Migrations Properties
126
+ ### Idempotent
127
+ Anchor Migrations in SQL must be written using idempotent tactics like `IF NOT EXISTS`.
128
+
129
+ This allows the SQL to be the backfill source for an Active Record migration, which is then also idempotent.
130
+
131
+ ### Restricted DDL: What DDL is supported for Anchor Migrations?
132
+ Only non-blocking, idempotent DDL is supported. This list is restricted heavily now although additional DDL types may be added in the future:
133
+ 1. `CREATE INDEX CONCURRENTLY IF NOT EXISTS`
134
+ 1. `DROP INDEX CONCURRENTLY IF EXISTS` (Postgres 13+)
135
+
136
+ Roadmap operations, future gem releases:
137
+ 1. `ALTER TABLE ALTER COLUMN IF NOT EXISTS` (only `NULL` values)
138
+ 1. Add check constraint, initially not valid
139
+
140
+ ### Uses psql
141
+ For now, Anchor Migrations assumes you're using psql to migrate, and that psql can connect to your target instance.
142
+
143
+ ### What’s out of scope for Anchor Migrations?
144
+ Anchor Migrations are non-blocking and idempotent.
145
+
146
+ For destructive operations like table drops, column drops, etc. with code dependencies, Anchor Migrations are not appropriate.
147
+
148
+ That's because application code references need to be removed first.
149
+
150
+ Use Strong Migrations or similar to help guide that process, and use regular Rails migrations.
151
+
152
+ Some of those destructive operations are:
153
+ 1. `DROP TABLE`
154
+ 1. Adding non-nullable column, or a column with a default value
155
+ 1. Dropping constraints
156
+ 1. Adding initially valid constraints
157
+ 1. Add indexes without using concurrently
158
+
159
+ [^docs]: <https://www.postgresql.org/docs/current/runtime-config-client.html>
160
+ [^tbd]: <https://trunkbaseddevelopment.com>
161
+
162
+ ## Building and Testing
163
+ ```sh
164
+ gem build anchor_migrations.gemspec
165
+ gem install ./anchor_migrations-0.1.0.gem
166
+ bundle exec rake test
167
+ ```
168
+
169
+ ## Testing Integration in Rails
170
+ Add to the project's Gemfile, then run `bundle`.
171
+
172
+ Once installed, test that it works by running:
173
+ ```sh
174
+ bundle exec anchor help
175
+ ```
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ # TODO: GitHub Workflow is hanging, trying to disable the RuboCop portion
9
+ # require "rubocop/rake_task"
10
+ #
11
+ # RuboCop::RakeTask.new
12
+
13
+ # task default: %i[test rubocop]
14
+ task default: %i[test]
data/exe/anchor ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "anchor_migrations"
5
+ require "anchor_migrations/cli"
6
+
7
+ AnchorMigrations::CLI.start(ARGV)
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "uri"
5
+ require "open3"
6
+
7
+ module AnchorMigrations
8
+ # Process command line arguments
9
+ class CLI
10
+ def self.start(args)
11
+ command = args.shift
12
+
13
+ case command
14
+ when "help"
15
+ new.help
16
+ when "init"
17
+ new.init
18
+ when "generate"
19
+ new.generate
20
+ when "lint"
21
+ new.lint
22
+ when "backfill"
23
+ new.generate_rails_migration
24
+ when "migrate"
25
+ new.migrate
26
+ else
27
+ new.help
28
+ end
29
+ end
30
+
31
+ def init
32
+ puts "Initializing anchor migrations structure..."
33
+
34
+ folders = [
35
+ AnchorMigrations::DEFAULT_DIR,
36
+ "#{AnchorMigrations::DEFAULT_DIR}/applied",
37
+ "config/initializers",
38
+ "db/migrate"
39
+ ]
40
+
41
+ folders.each do |folder|
42
+ if Dir.exist?(folder)
43
+ puts "Directory #{folder} already exists"
44
+ else
45
+ FileUtils.mkdir_p(folder)
46
+ puts "Created directory #{folder}"
47
+ end
48
+ end
49
+
50
+ puts "Checking for Squawk"
51
+ check_for_squawk
52
+
53
+ puts "Adding initializer"
54
+ InitializerGenerator.new.generate
55
+
56
+ puts "Anchor migrations structure initialized."
57
+ end
58
+
59
+ def generate
60
+ # TODO: accept file name argument
61
+ Generator.new.generate
62
+ end
63
+
64
+ def lint
65
+ unless Dir.exist?(AnchorMigrations::DEFAULT_DIR)
66
+ abort "Error: '#{AnchorMigrations::DEFAULT_DIR}' not found. Did you run anchor init?"
67
+ end
68
+ check_for_squawk
69
+ system("squawk lint #{AnchorMigrations::DEFAULT_DIR}/*.sql")
70
+ end
71
+
72
+ def migrate
73
+ # TODO: Only supporting one file for now. Expecting files to be moved into "applied" directory.
74
+ anchor_migration_file = Dir["#{AnchorMigrations::DEFAULT_DIR}/*.sql"].max
75
+ version = File.basename(anchor_migration_file).split("_").first
76
+ sql_ddl = File.read(anchor_migration_file)
77
+ cleaned_sql = AnchorMigrations::Utility.cleaned_sql(sql_ddl)
78
+ puts "Applying Version: #{version}"
79
+ puts cleaned_sql
80
+
81
+ if !ENV["DATABASE_URL"]
82
+ puts "DATABASE_URL must be set...exiting"
83
+ exit 1
84
+ else
85
+ base_url = URI.parse(ENV["DATABASE_URL"])
86
+ params = {
87
+ options: "-c lock_timeout=2s"
88
+ }
89
+ encoded = URI.encode_www_form(params)
90
+ encoded = encoded.gsub("+", "%20") # remove "+" for Postgres
91
+ conn_string = "#{base_url}?#{encoded}"
92
+ command = %(
93
+ psql #{conn_string} \
94
+ -c '#{cleaned_sql}'
95
+ )
96
+ stdout, stderr, status = Open3.capture3(command)
97
+ puts "STDOUT:\n#{stdout}"
98
+ puts "STDERR:\n#{stderr}"
99
+ if status.success?
100
+ puts "Success!"
101
+ puts "Applied Version: #{version}"
102
+ end
103
+ puts "Exit code: #{status.exitstatus}"
104
+ end
105
+ end
106
+
107
+ def generate_rails_migration
108
+ RailsMigrationGenerator.new.generate
109
+ end
110
+
111
+ def help
112
+ puts "Available commands: init, generate, lint, backfill, migrate"
113
+ end
114
+
115
+ private
116
+
117
+ def check_for_squawk
118
+ if !system("which squawk > /dev/null 2>&1")
119
+ abort <<~MSG
120
+ "Error: 'squawk' command not found in PATH."
121
+ Is it installed? https://squawkhq.com/docs/
122
+ MSG
123
+ else
124
+ puts "Squawk found."
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ # Configuration for gem
5
+ class Configuration
6
+ attr_accessor :use_strong_migrations
7
+
8
+ def initialize
9
+ @use_strong_migrations = false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ # Generate Anchor Migrations SQL files
5
+ class Generator
6
+ def anchor_migrations_dir
7
+ File.join(AnchorMigrations::DEFAULT_DIR)
8
+ end
9
+
10
+ def generate
11
+ output_file = "#{anchor_migrations_dir}/#{rails_style_timestamp}_anchor_migration.sql"
12
+ File.write(output_file, sql_migration_template)
13
+ puts "Wrote file: #{output_file}"
14
+ puts File.read(output_file)
15
+ end
16
+
17
+ def rails_style_timestamp
18
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
19
+ end
20
+
21
+ def sql_migration_template
22
+ <<~SQL_TEMPLATE.strip
23
+ -- Generated by anchor_migrations #{VERSION}
24
+ --
25
+ -- Examples:
26
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS
27
+ -- idx_tbl_col ON tbl (col);
28
+ --
29
+ -- DROP INDEX CONCURRENTLY IF EXISTS
30
+ -- idx_tbl_col;
31
+ SQL_TEMPLATE
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ # Create a Rails migration from Anchor Migration SQL
5
+ class InitializerGenerator
6
+ def generate
7
+ filename = "anchor_migrations.rb"
8
+ file = "config/initializers/#{filename}"
9
+ return if File.exist?(file)
10
+
11
+ File.write(file, initalizer_template)
12
+ puts "Wrote file: #{file}"
13
+ puts File.read(file)
14
+ end
15
+
16
+ def initalizer_template
17
+ <<~TEMPLATE.strip
18
+ AnchorMigrations.configure do |config|
19
+ config.use_strong_migrations = false
20
+ end
21
+ TEMPLATE
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ # Load the Rails environment
5
+ module RailsLoader
6
+ def self.load_rails!
7
+ env_path = File.expand_path("config/environment", Dir.pwd)
8
+ require "#{env_path}.rb" if File.exist?("#{env_path}.rb")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ #
5
+ # Create a Rails migration from Anchor Migration SQL
6
+ # Expects Anchor Migrations to be in db/anchor_migrations
7
+ # Expects to generate Rails Migrations into db/migrate
8
+ # Expects to parse a Rails-style timestamp from beginning of Anchor Migrations file
9
+ #
10
+ class RailsMigrationGenerator
11
+ def initialize(anchor_migrations_dir: AnchorMigrations::DEFAULT_DIR)
12
+ if @anchor_migration_file = Dir["#{anchor_migrations_dir}/*.sql"].max
13
+ @migration_version = File.basename(@anchor_migration_file).split("_").first
14
+ sql_file_content = File.read(@anchor_migration_file)
15
+ @cleaned_sql_ddl = AnchorMigrations::Utility.cleaned_sql(sql_file_content)
16
+ build_file_name_and_class_name
17
+ AnchorMigrations::RailsLoader.load_rails!
18
+ else
19
+ abort "Can't find dir #{anchor_migrations_dir} or file #{Dir["#{anchor_migrations_dir}/*.sql"]}"
20
+ end
21
+ end
22
+
23
+ def generate(write_file: true)
24
+ output_file = "#{AnchorMigrations::RAILS_MIG_DIR}/#{@migration_version}_#{@migration_file_name_no_version}"
25
+ migration_content = rails_generate_migration_code
26
+ if write_file
27
+ File.write(output_file, migration_content)
28
+ puts "Wrote file: #{output_file}"
29
+ puts File.read(output_file)
30
+ end
31
+ migration_content # for tests
32
+ end
33
+
34
+ private
35
+
36
+ def rails_version_major_minor
37
+ if defined?(Rails) && defined?(Rails::VERSION)
38
+ major = Rails::VERSION::MAJOR
39
+ minor = Rails::VERSION::MINOR
40
+ "#{major}.#{minor}"
41
+ else
42
+ "X.Y" # can't load Rails, this is only for testing outside Rails
43
+ end
44
+ end
45
+
46
+ def build_file_name_and_class_name
47
+ # Strip out the Rails-style version number
48
+ @migration_version = File.basename(@anchor_migration_file).split("_").first
49
+
50
+ # Break up by underscore, and join together with underscore, for only the basename
51
+ path = File.basename(@anchor_migration_file)
52
+ .split("_")[1..-1].join("_")
53
+
54
+ # Strip out the extension (.sql)
55
+ filename_base = File.basename(path, File.extname(path))
56
+
57
+ # Create a capitalized words version for the migration class name
58
+ @migration_class_name = filename_base.split("_")
59
+ .map(&:capitalize).join
60
+
61
+ # Create a filename with .rb extension
62
+ @migration_file_name_no_version = "#{filename_base}.rb"
63
+ end
64
+
65
+ def migration_change_method_body
66
+ if AnchorMigrations.configuration.use_strong_migrations
67
+ <<-TEMPL
68
+ safety_assured do
69
+ execute <<-SQL
70
+ #{@cleaned_sql_ddl}
71
+ SQL
72
+ end
73
+ TEMPL
74
+ else
75
+ <<-TEMPL
76
+ execute <<-SQL
77
+ #{@cleaned_sql_ddl}
78
+ SQL
79
+ TEMPL
80
+ end
81
+ end
82
+
83
+ # Assume it's a concurrently operation for now, disable_ddl_transaction!
84
+ def rails_generate_migration_code
85
+ <<~MIG_TEMPLATE.strip
86
+ #
87
+ # ################################################
88
+ # DO NOT EDIT, generated by Anchor Migrations
89
+ # Version: #{@migration_version}
90
+ # Source File: #{@anchor_migration_file}
91
+ # Target File: #{AnchorMigrations::RAILS_MIG_DIR}/#{@migration_version}_#{@migration_file_name_no_version}
92
+ # ################################################
93
+ #
94
+ class #{@migration_class_name} < ActiveRecord::Migration[#{rails_version_major_minor}]
95
+ disable_ddl_transaction!
96
+
97
+ def change
98
+ #{migration_change_method_body}
99
+ end
100
+ end
101
+ MIG_TEMPLATE
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie" if defined?(Rails)
4
+
5
+ module AnchorMigrations
6
+ # Rails integration
7
+ class Railtie < Rails::Railtie
8
+ initializer "anchor_migrations.configure" do
9
+ AnchorMigrations.configure do |config|
10
+ config.use_strong_migrations ||= false
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ # Utility functions
5
+ module Utility
6
+ # Strip out SQL comments that start with
7
+ # double hyphen "--"
8
+ def self.cleaned_sql(input_sql)
9
+ clean_lines = []
10
+ input_sql.lines.each do |line|
11
+ clean_lines << line unless line =~ /^\s*--/
12
+ end
13
+ clean_lines.join
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnchorMigrations
4
+ VERSION = "0.1.5"
5
+ DEFAULT_DIR = "db/anchor_migrations"
6
+ RAILS_MIG_DIR = "db/migrate"
7
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "anchor_migrations/configuration"
4
+ require_relative "anchor_migrations/rails_loader"
5
+ require_relative "anchor_migrations/utility"
6
+ require_relative "anchor_migrations/version"
7
+ require_relative "anchor_migrations/generator"
8
+ require_relative "anchor_migrations/rails_migration_generator"
9
+ require_relative "anchor_migrations/initializer_generator"
10
+ require_relative "anchor_migrations/cli"
11
+ require_relative "anchor_migrations/railtie" if defined?(Rails)
12
+
13
+ module AnchorMigrations
14
+ class Error < StandardError; end
15
+
16
+ class << self
17
+ attr_writer :configuration
18
+
19
+ def configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def configure
24
+ yield(configuration)
25
+ end
26
+
27
+ def reset
28
+ @configuration = Configuration.new
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ module AnchorMigrations
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: anchor_migrations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Atkinson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Description
14
+ email:
15
+ - andyatkinson@gmail.com
16
+ executables:
17
+ - anchor
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".rubocop.yml"
22
+ - CHANGELOG.md
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - exe/anchor
27
+ - lib/anchor_migrations.rb
28
+ - lib/anchor_migrations/cli.rb
29
+ - lib/anchor_migrations/configuration.rb
30
+ - lib/anchor_migrations/generator.rb
31
+ - lib/anchor_migrations/initializer_generator.rb
32
+ - lib/anchor_migrations/rails_loader.rb
33
+ - lib/anchor_migrations/rails_migration_generator.rb
34
+ - lib/anchor_migrations/railtie.rb
35
+ - lib/anchor_migrations/utility.rb
36
+ - lib/anchor_migrations/version.rb
37
+ - sig/anchor_migrations.rbs
38
+ homepage: https://github.com/andyatkinson/anchor_migrations
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ homepage_uri: https://github.com/andyatkinson/anchor_migrations
43
+ source_code_uri: https://github.com/andyatkinson/anchor_migrations
44
+ changelog_uri: https://github.com/andyatkinson/anchor_migrations/blob/main/CHANGELOG.md
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.7.8
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.6
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Anchor migrations
64
+ test_files: []