request_migrations 1.0.1 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/request_migrations.svg)](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
|
+
![request_migrations diagram](https://user-images.githubusercontent.com/6979737/175964358-a2d8951d-46c6-4962-9f5e-0569cbf5972e.png)
|
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
|
- - ">="
|