request_migrations 1.0.1 → 1.1.1
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +101 -44
- data/lib/request_migrations/configuration.rb +3 -3
- data/lib/request_migrations/gem.rb +1 -1
- data/lib/request_migrations/migration.rb +3 -3
- data/lib/request_migrations/migrator.rb +2 -2
- data/lib/request_migrations/railtie.rb +2 -0
- data/lib/request_migrations/testing.rb +24 -0
- data/lib/request_migrations.rb +15 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '091e4436fcd439967859301cc299fccaf6081f5da326ed79813e7cbce3f4d86c'
|
4
|
+
data.tar.gz: 4be2f76ade4958a48794ee62e6184c964f747f6c59a44a12ef20dcbbcc8c32a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2a22c88034bd0a67f34849909d85686267a4fd2a62dfbcfcb963766f678397d141a628608b63e0549581fbd9e02184a938619a269c81e44bd1a094d2febe702
|
7
|
+
data.tar.gz: d92878fd3a359c744c4cc9354325a5fea7d89ca81e9c3aa2718bd033a8b7ed8f7840f8bce0ada3f170dfb8f3a1ea9f1362cff8b08effafee53ffc0b5b59911c4
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -3,11 +3,13 @@
|
|
3
3
|
[](https://badge.fury.io/rb/request_migrations)
|
4
4
|
|
5
5
|
**Make breaking API changes without breaking things!** Use `request_migrations` to craft
|
6
|
-
backwards-compatible migrations for API requests, responses, and more.
|
7
|
-
|
8
|
-
requests per day.
|
6
|
+
backwards-compatible migrations for API requests, responses, and more. Read [the blog
|
7
|
+
post](https://keygen.sh/blog/breaking-things-without-breaking-things/).
|
9
8
|
|
10
|
-
|
9
|
+
This gem was extracted from [Keygen](https://keygen.sh) and is being used in production
|
10
|
+
to serve millions of API requests per day.
|
11
|
+
|
12
|
+

|
11
13
|
|
12
14
|
Sponsored by:
|
13
15
|
|
@@ -15,6 +17,24 @@ Sponsored by:
|
|
15
17
|
|
16
18
|
_A software licensing and distribution API built for developers._
|
17
19
|
|
20
|
+
Links:
|
21
|
+
|
22
|
+
- [Installing request_migrations](#installation)
|
23
|
+
- [Supported Ruby versions](#supported-rubies)
|
24
|
+
- [RubyDoc](#documentation)
|
25
|
+
- [Usage](#usage)
|
26
|
+
- [Response migrations](#response-migrations)
|
27
|
+
- [Request migrations](#request-migrations)
|
28
|
+
- [Data migrations](#data-migrations)
|
29
|
+
- [Routing constraints](#routing-constraints)
|
30
|
+
- [Configuration](#configuration)
|
31
|
+
- [Version formats](#version-formats)
|
32
|
+
- [Testing](#testing)
|
33
|
+
- [Tips and tricks](#tips-and-tricks)
|
34
|
+
- [Credits](#credits)
|
35
|
+
- [Contributing](#contributing)
|
36
|
+
- [License](#license)
|
37
|
+
|
18
38
|
## Installation
|
19
39
|
|
20
40
|
Add this line to your application's `Gemfile`:
|
@@ -37,8 +57,9 @@ $ gem install request_migrations
|
|
37
57
|
|
38
58
|
## Supported Rubies
|
39
59
|
|
40
|
-
|
41
|
-
version. Ruby 3 provides a lot of great features, like better pattern matching
|
60
|
+
**`request_migrations` supports Ruby 3.1 and above.** We encourage you to upgrade if you're on an older
|
61
|
+
version. Ruby 3 provides a lot of great features, like better pattern matching and a new shorthand
|
62
|
+
hash syntax.
|
42
63
|
|
43
64
|
## Documentation
|
44
65
|
|
@@ -50,7 +71,7 @@ _We're working on improving the docs._
|
|
50
71
|
|
51
72
|
- Define migrations for migrating a response between versions.
|
52
73
|
- Define migrations for migrating a request between versions.
|
53
|
-
- Define migrations for applying
|
74
|
+
- Define migrations for applying data migrations.
|
54
75
|
- Define version-based routing constraints.
|
55
76
|
- It's fast.
|
56
77
|
|
@@ -122,15 +143,15 @@ end
|
|
122
143
|
|
123
144
|
As you can see, with pattern matching, it makes creating migrations for certain
|
124
145
|
resources simple. Here, we've defined a migration that only runs for the `users#show`
|
125
|
-
|
126
|
-
|
146
|
+
resource, and only when the response is successful. In addition, the data is
|
147
|
+
only migrated when the response body contains a user.
|
127
148
|
|
128
149
|
Next, we'll need to configure `request_migrations` via an initializer under
|
129
150
|
`initializers/request_migrations.rb`:
|
130
151
|
|
131
152
|
```ruby
|
132
153
|
RequestMigrations.configure do |config|
|
133
|
-
# Define a resolver to determine the
|
154
|
+
# Define a resolver to determine the target version. Here, you can perform
|
134
155
|
# a lookup on the current user using request parameters, or simply use
|
135
156
|
# a header like we are here, defaulting to the latest version.
|
136
157
|
config.request_version_resolver = -> request {
|
@@ -189,7 +210,12 @@ end
|
|
189
210
|
|
190
211
|
The `response` method accepts an `:if` keyword, which should be a lambda
|
191
212
|
that evaluates to a boolean, which determines whether or not the migration
|
192
|
-
should be applied.
|
213
|
+
should be applied. An `ActionDispatch::Response` will be yielded, the
|
214
|
+
current response (calls `controller#response`).
|
215
|
+
|
216
|
+
The gem makes no assumption on a response's content type or what the migration
|
217
|
+
will do. You could, for example, migrate the response body, or mutate the
|
218
|
+
headers, or even change the response's status code.
|
193
219
|
|
194
220
|
### Request migrations
|
195
221
|
|
@@ -209,19 +235,25 @@ end
|
|
209
235
|
|
210
236
|
The `request` method accepts an `:if` keyword, which should be a lambda
|
211
237
|
that evaluates to a boolean, which determines whether or not the migration
|
212
|
-
should be applied.
|
238
|
+
should be applied. An `ActionDispatch::Request` object will be yielded,
|
239
|
+
the current request (calls `controller#request`).
|
240
|
+
|
241
|
+
Again, like with response migrations, the gem makes no assumption on what
|
242
|
+
a migration does. A migration could mutate a request's params, or mutate
|
243
|
+
headers. It's up to you, all it does is provide the request.
|
244
|
+
|
245
|
+
Request migrations should [avoid using the `migrate` method](#avoid-migrate-for-request-migrations).
|
213
246
|
|
214
|
-
###
|
247
|
+
### Data migrations
|
215
248
|
|
216
249
|
In our first scenario, where we combined our user's name attributes, we defined
|
217
250
|
our migration using the `migrate` class method. At this point, you may be wondering
|
218
251
|
why we did that, since we didn't use that method for the 2 previous request and
|
219
252
|
response migrations above.
|
220
253
|
|
221
|
-
Well, it comes down to support for
|
222
|
-
|
223
|
-
|
224
|
-
Let's go back to our first example, `CombineNamesForUserMigration`.
|
254
|
+
Well, it comes down to support for data migrations (as well as offering a nice
|
255
|
+
interface for pattern matching inputs). Let's go back to our first example,
|
256
|
+
`CombineNamesForUserMigration`.
|
225
257
|
|
226
258
|
```ruby
|
227
259
|
class CombineNamesForUserMigration < RequestMigrations::Migration
|
@@ -251,7 +283,7 @@ end
|
|
251
283
|
```
|
252
284
|
|
253
285
|
What if we had [a webhook system](https://keygen.sh/blog/how-to-build-a-webhook-system-in-rails-using-sidekiq/)
|
254
|
-
that we also needed to apply these migrations to? Well, we can use a
|
286
|
+
that we also needed to apply these migrations to? Well, we can use a data migration
|
255
287
|
here, via the `Migrator` class:
|
256
288
|
|
257
289
|
```ruby
|
@@ -277,17 +309,20 @@ class WebhookWorker
|
|
277
309
|
end
|
278
310
|
```
|
279
311
|
|
280
|
-
|
281
|
-
|
282
|
-
|
312
|
+
This will apply the block defined in `migrate` onto our data. With that,
|
313
|
+
we've successfully applied a migration to both our API responses, as well
|
314
|
+
as to the webhook events we send. In this case, if our event data matches
|
315
|
+
our expected data shape, e.g. `type: 'user'`, then the migration will
|
316
|
+
be applied.
|
283
317
|
|
284
|
-
In addition to
|
318
|
+
In addition to data migrations, this allows for easier [testing](#testing).
|
285
319
|
|
286
320
|
### Routing constraints
|
287
321
|
|
288
322
|
When you want to encourage API clients to upgrade, you can utilize a routing `version_constraint`
|
289
|
-
to define routes only available for certain versions.
|
290
|
-
|
323
|
+
to define routes only available for certain versions.
|
324
|
+
|
325
|
+
You can also utilize routing constraints to remove an API endpoint entirely.
|
291
326
|
|
292
327
|
```ruby
|
293
328
|
Rails.application.routes.draw do
|
@@ -305,13 +340,13 @@ Rails.application.routes.draw do
|
|
305
340
|
end
|
306
341
|
```
|
307
342
|
|
308
|
-
Currently, routing constraints only work for the `:semver` version format.
|
343
|
+
Currently, routing constraints only work for the `:semver` version format. (PRs welcome!)
|
309
344
|
|
310
345
|
### Configuration
|
311
346
|
|
312
347
|
```ruby
|
313
348
|
RequestMigrations.configure do |config|
|
314
|
-
# Define a resolver to determine the
|
349
|
+
# Define a resolver to determine the target version. Here, you can perform
|
315
350
|
# a lookup on the current user using request parameters, or simply use
|
316
351
|
# a header like we are here, defaulting to the latest version.
|
317
352
|
config.request_version_resolver = -> request {
|
@@ -348,17 +383,19 @@ end
|
|
348
383
|
By default, `request_migrations` uses a `:semver` version format, but it can be configured
|
349
384
|
to instead use one of the following, set via `config.version_format=`.
|
350
385
|
|
351
|
-
| Format |
|
352
|
-
|
353
|
-
| `:semver` | Use semantic versions, e.g. `1.0`, `1.1
|
354
|
-
| `:date` | Use date versions, e.g. `2020-09-02`, `2021-01-01`.
|
355
|
-
| `:integer` | Use integer versions, e.g. `1`, `2`, and `3`.
|
356
|
-
| `:float` | Use float versions, e.g. `1.0`, `1.1`, and `2.0`.
|
357
|
-
| `:string` | Use string versions, e.g. `a`, `b`, and `z`.
|
386
|
+
| Format | |
|
387
|
+
|:-----------|:-----------------------------------------------------|
|
388
|
+
| `:semver` | Use semantic versions, e.g. `1.0`, `1.1`, and `2.0`. |
|
389
|
+
| `:date` | Use date versions, e.g. `2020-09-02`, `2021-01-01`. |
|
390
|
+
| `:integer` | Use integer versions, e.g. `1`, `2`, and `3`. |
|
391
|
+
| `:float` | Use float versions, e.g. `1.0`, `1.1`, and `2.0`. |
|
392
|
+
| `:string` | Use string versions, e.g. `a`, `b`, and `z`. |
|
393
|
+
|
394
|
+
All versions will be sorted according to the format's type.
|
358
395
|
|
359
396
|
## Testing
|
360
397
|
|
361
|
-
Using
|
398
|
+
Using data migrations allows for easier testing of migrations. For example, using Rspec:
|
362
399
|
|
363
400
|
```ruby
|
364
401
|
describe CombineNamesForUserMigration do
|
@@ -388,9 +425,29 @@ describe CombineNamesForUserMigration do
|
|
388
425
|
end
|
389
426
|
```
|
390
427
|
|
428
|
+
To avoid polluting the global configuration, you can use `RequestMigrations::Testing`
|
429
|
+
within your application's `spec/rails_helper.rb`, or a similar spec helper:
|
430
|
+
|
431
|
+
```ruby
|
432
|
+
require 'request_migrations/testing'
|
433
|
+
|
434
|
+
Rspec.configure do |config|
|
435
|
+
config.before :each do
|
436
|
+
RequestMigrations::Testing.setup!
|
437
|
+
end
|
438
|
+
|
439
|
+
config.after :each do
|
440
|
+
RequestMigrations::Testing.teardown!
|
441
|
+
end
|
442
|
+
end
|
443
|
+
```
|
444
|
+
|
445
|
+
This will setup a new test configuration, and then restore the previous global configuration
|
446
|
+
after each spec.
|
447
|
+
|
391
448
|
## Tips and tricks
|
392
449
|
|
393
|
-
Over the years, we're learned a thing or two about
|
450
|
+
Over the years, we're learned a thing or two about versioning an API. We'll share tips here.
|
394
451
|
|
395
452
|
### Use pattern matching
|
396
453
|
|
@@ -544,14 +601,14 @@ end
|
|
544
601
|
|
545
602
|
### Avoid migrate for request migrations
|
546
603
|
|
547
|
-
Avoid using `migrate` for request migrations. If you do,
|
548
|
-
will apply the request migrations
|
549
|
-
response migration. Instead, keep all request migration logic,
|
550
|
-
inside of the `request` block.
|
604
|
+
Avoid using `migrate` for request migrations. If you do, then data migrations, e.g. for
|
605
|
+
webhooks, will attempt to apply the request migrations. This may erroneously produce bad
|
606
|
+
output, or even undo a response migration. Instead, keep all request migration logic,
|
607
|
+
e.g. transforming params, inside of the `request` block.
|
551
608
|
|
552
609
|
```ruby
|
553
610
|
class SomeMigration < RequestMigrations::Migration
|
554
|
-
# Bad (side-effects for
|
611
|
+
# Bad (side-effects for data migrations)
|
555
612
|
migrate do |params|
|
556
613
|
params[:foo] = params.delete(:bar)
|
557
614
|
end
|
@@ -570,8 +627,8 @@ end
|
|
570
627
|
### Avoid routing contraints
|
571
628
|
|
572
629
|
Avoid using routing version constraints that remove functionality. They can be a headache
|
573
|
-
during upgrades. Consider only making _additive_ changes.
|
574
|
-
or deprecated endpoints to limit any new usage.
|
630
|
+
during upgrades. Consider only making _additive_ changes. Instead, consider removing or
|
631
|
+
hiding the documenation for old or deprecated endpoints, to limit any new usage.
|
575
632
|
|
576
633
|
```ruby
|
577
634
|
Rails.application.routes.draw do
|
@@ -591,7 +648,7 @@ end
|
|
591
648
|
|
592
649
|
### Avoid n+1s
|
593
650
|
|
594
|
-
Avoid introducing n+1 queries in your
|
651
|
+
Avoid introducing n+1 queries in your migrations. Try to utilize the current data you have
|
595
652
|
to perform more meaningful queries, returning only the data needed for the migration.
|
596
653
|
|
597
654
|
```ruby
|
@@ -642,7 +699,7 @@ end
|
|
642
699
|
```
|
643
700
|
|
644
701
|
Instead of potentially tens or hundreds of queries, we make a single purposeful query
|
645
|
-
to get the data we need to complete the migration.
|
702
|
+
to get the data we need in order to complete the migration.
|
646
703
|
|
647
704
|
---
|
648
705
|
|
@@ -26,13 +26,13 @@ module RequestMigrations
|
|
26
26
|
##
|
27
27
|
# current_version defines the latest version.
|
28
28
|
#
|
29
|
-
# @return [String, Integer, Float] the current version.
|
29
|
+
# @return [String, Integer, Float, nil] the current version.
|
30
30
|
config_accessor(:current_version) { nil }
|
31
31
|
|
32
32
|
##
|
33
33
|
# versions defines past versions and their migrations.
|
34
34
|
#
|
35
|
-
# @return [Hash] past versions.
|
36
|
-
config_accessor(:versions) {
|
35
|
+
# @return [Hash<String, Array<Symbol, String, Class>>] past versions.
|
36
|
+
config_accessor(:versions) { {} }
|
37
37
|
end
|
38
38
|
end
|
@@ -77,7 +77,7 @@ module RequestMigrations
|
|
77
77
|
#
|
78
78
|
# @param if [Proc] the proc which determines if the migration should run.
|
79
79
|
#
|
80
|
-
# @yield [
|
80
|
+
# @yield [ActionDispatch::Request] the current request.
|
81
81
|
def request(if: nil, &block)
|
82
82
|
self.request_blocks << ConditionalBlock.new(if:, &block)
|
83
83
|
end
|
@@ -87,7 +87,7 @@ module RequestMigrations
|
|
87
87
|
#
|
88
88
|
# @param if [Proc] the proc which determines if the migration should run.
|
89
89
|
#
|
90
|
-
# @yield [
|
90
|
+
# @yield [Any] the provided data.
|
91
91
|
def migrate(if: nil, &block)
|
92
92
|
self.migration_blocks << ConditionalBlock.new(if:, &block)
|
93
93
|
end
|
@@ -97,7 +97,7 @@ module RequestMigrations
|
|
97
97
|
#
|
98
98
|
# @param if [Proc] the proc which determines if the migration should run.
|
99
99
|
#
|
100
|
-
# @yield [
|
100
|
+
# @yield [ActionDispatch::Response] the current response.
|
101
101
|
def response(if: nil, &block)
|
102
102
|
self.response_blocks << ConditionalBlock.new(if:, &block)
|
103
103
|
end
|
@@ -15,9 +15,9 @@ module RequestMigrations
|
|
15
15
|
##
|
16
16
|
# migrate! attempts to apply all matching migrations on data.
|
17
17
|
#
|
18
|
-
# @param data [
|
18
|
+
# @param data [Any] the data to be migrated.
|
19
19
|
#
|
20
|
-
# @return [
|
20
|
+
# @return [void]
|
21
21
|
def migrate!(data:)
|
22
22
|
logger.debug { "Migrating from #{current_version} to #{target_version} (#{migrations.size} potential migrations)" }
|
23
23
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RequestMigrations
|
4
|
+
class Testing
|
5
|
+
@@config = RequestMigrations::Configuration.new
|
6
|
+
|
7
|
+
##
|
8
|
+
# setup! stores the original config and replaces it with a clone for testing.
|
9
|
+
def self.setup!
|
10
|
+
@@config = RequestMigrations.config
|
11
|
+
|
12
|
+
RequestMigrations.reset!
|
13
|
+
RequestMigrations.configure do |config|
|
14
|
+
@@config.config.each { |(k, v)| config.config[k] = v }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# teardown! restores the original config.
|
20
|
+
def self.teardown!
|
21
|
+
RequestMigrations.config = @@config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/request_migrations.rb
CHANGED
@@ -43,6 +43,21 @@ module RequestMigrations
|
|
43
43
|
# config returns the current config.
|
44
44
|
def self.config = @config ||= Configuration.new
|
45
45
|
|
46
|
+
##
|
47
|
+
# @private
|
48
|
+
def self.config=(cfg)
|
49
|
+
raise ArgumentError, 'invalid config provided' unless
|
50
|
+
cfg.is_a?(Configuration)
|
51
|
+
|
52
|
+
@config = cfg
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# @private
|
57
|
+
def self.reset!
|
58
|
+
@config = Configuration.new
|
59
|
+
end
|
60
|
+
|
46
61
|
##
|
47
62
|
# logger returns the configured logger.
|
48
63
|
#
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: request_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zeke Gabrielse
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- lib/request_migrations/migrator.rb
|
75
75
|
- lib/request_migrations/railtie.rb
|
76
76
|
- lib/request_migrations/router.rb
|
77
|
+
- lib/request_migrations/testing.rb
|
77
78
|
- lib/request_migrations/version.rb
|
78
79
|
homepage: https://github.com/keygen-sh/request_migrations
|
79
80
|
licenses:
|
@@ -87,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
88
|
requirements:
|
88
89
|
- - ">="
|
89
90
|
- !ruby/object:Gem::Version
|
90
|
-
version: '3.
|
91
|
+
version: '3.1'
|
91
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
93
|
requirements:
|
93
94
|
- - ">="
|