request_migrations 1.0.1 → 1.0.2
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 +55 -38
- 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
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1575eecb9ad23c9dae9534b03a31acbcef8c6ed7bf46f4ffca1748ef21fcead4
|
4
|
+
data.tar.gz: 141dbbf4576e8b2da641ab32bd377173bbe61717d330c741c0b10c5cf9eff854
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 119241518c4d8a9a954bd4918a6e2edbc0338938c7173953a406821f34f0f4da735e7819b7cc0e929be45c78a7da88f035f1456d5b98d53d6cb00b0fca1c8362
|
7
|
+
data.tar.gz: 5afffe316b08692daeaf2366fab161f23794b80326112a2b4c4ece1a13f0d40f8d61423aa81d4bf28c62274acf8a33dd63c01214295524cd7691c42203a71575
|
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
|
|
@@ -50,7 +52,7 @@ _We're working on improving the docs._
|
|
50
52
|
|
51
53
|
- Define migrations for migrating a response between versions.
|
52
54
|
- Define migrations for migrating a request between versions.
|
53
|
-
- Define migrations for applying
|
55
|
+
- Define migrations for applying data migrations.
|
54
56
|
- Define version-based routing constraints.
|
55
57
|
- It's fast.
|
56
58
|
|
@@ -130,7 +132,7 @@ Next, we'll need to configure `request_migrations` via an initializer under
|
|
130
132
|
|
131
133
|
```ruby
|
132
134
|
RequestMigrations.configure do |config|
|
133
|
-
# Define a resolver to determine the
|
135
|
+
# Define a resolver to determine the target version. Here, you can perform
|
134
136
|
# a lookup on the current user using request parameters, or simply use
|
135
137
|
# a header like we are here, defaulting to the latest version.
|
136
138
|
config.request_version_resolver = -> request {
|
@@ -189,7 +191,12 @@ end
|
|
189
191
|
|
190
192
|
The `response` method accepts an `:if` keyword, which should be a lambda
|
191
193
|
that evaluates to a boolean, which determines whether or not the migration
|
192
|
-
should be applied.
|
194
|
+
should be applied. An `ActionDispatch::Response` will be yielded, the
|
195
|
+
current response (calls `controller#response`).
|
196
|
+
|
197
|
+
The gem makes no assumption on a response's content type or what the migration
|
198
|
+
will do. You could, for example, migrate the response body, or mutate the
|
199
|
+
headers, or even change the response's status code.
|
193
200
|
|
194
201
|
### Request migrations
|
195
202
|
|
@@ -209,19 +216,25 @@ end
|
|
209
216
|
|
210
217
|
The `request` method accepts an `:if` keyword, which should be a lambda
|
211
218
|
that evaluates to a boolean, which determines whether or not the migration
|
212
|
-
should be applied.
|
219
|
+
should be applied. An `ActionDispatch::Request` object will be yielded,
|
220
|
+
the current request (calls `controller#request`).
|
221
|
+
|
222
|
+
Again, like with response migrations, the gem makes no assumption on what
|
223
|
+
a migration does. A migration could mutate a request's params, or mutate
|
224
|
+
headers. It's up to you, all it does is provide the request.
|
225
|
+
|
226
|
+
Request migrations should [avoid using the `migrate` method](#avoid-migrate-for-request-migrations).
|
213
227
|
|
214
|
-
###
|
228
|
+
### Data migrations
|
215
229
|
|
216
230
|
In our first scenario, where we combined our user's name attributes, we defined
|
217
231
|
our migration using the `migrate` class method. At this point, you may be wondering
|
218
232
|
why we did that, since we didn't use that method for the 2 previous request and
|
219
233
|
response migrations above.
|
220
234
|
|
221
|
-
Well, it comes down to support for
|
222
|
-
|
223
|
-
|
224
|
-
Let's go back to our first example, `CombineNamesForUserMigration`.
|
235
|
+
Well, it comes down to support for data migrations (as well as offering a nice
|
236
|
+
interface for pattern matching inputs). Let's go back to our first example,
|
237
|
+
`CombineNamesForUserMigration`.
|
225
238
|
|
226
239
|
```ruby
|
227
240
|
class CombineNamesForUserMigration < RequestMigrations::Migration
|
@@ -251,7 +264,7 @@ end
|
|
251
264
|
```
|
252
265
|
|
253
266
|
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
|
267
|
+
that we also needed to apply these migrations to? Well, we can use a data migration
|
255
268
|
here, via the `Migrator` class:
|
256
269
|
|
257
270
|
```ruby
|
@@ -277,17 +290,19 @@ class WebhookWorker
|
|
277
290
|
end
|
278
291
|
```
|
279
292
|
|
280
|
-
|
293
|
+
This will apply the block defined in `migrate` onto our data. With that,
|
294
|
+
we've successfully applied a migration to both our API responses, as well
|
281
295
|
as to the webhook events we send. In this case, if our `event` matches the
|
282
296
|
our user shape, e.g. `type: 'user'`, then the migration will be applied.
|
283
297
|
|
284
|
-
In addition to
|
298
|
+
In addition to data migrations, this allows for easier testing.
|
285
299
|
|
286
300
|
### Routing constraints
|
287
301
|
|
288
302
|
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
|
-
|
303
|
+
to define routes only available for certain versions.
|
304
|
+
|
305
|
+
You can also utilize routing constraints to remove an API endpoint entirely.
|
291
306
|
|
292
307
|
```ruby
|
293
308
|
Rails.application.routes.draw do
|
@@ -305,13 +320,13 @@ Rails.application.routes.draw do
|
|
305
320
|
end
|
306
321
|
```
|
307
322
|
|
308
|
-
Currently, routing constraints only work for the `:semver` version format.
|
323
|
+
Currently, routing constraints only work for the `:semver` version format. (PRs welcome!)
|
309
324
|
|
310
325
|
### Configuration
|
311
326
|
|
312
327
|
```ruby
|
313
328
|
RequestMigrations.configure do |config|
|
314
|
-
# Define a resolver to determine the
|
329
|
+
# Define a resolver to determine the target version. Here, you can perform
|
315
330
|
# a lookup on the current user using request parameters, or simply use
|
316
331
|
# a header like we are here, defaulting to the latest version.
|
317
332
|
config.request_version_resolver = -> request {
|
@@ -348,17 +363,19 @@ end
|
|
348
363
|
By default, `request_migrations` uses a `:semver` version format, but it can be configured
|
349
364
|
to instead use one of the following, set via `config.version_format=`.
|
350
365
|
|
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`.
|
366
|
+
| Format | |
|
367
|
+
|:-----------|:-----------------------------------------------------|
|
368
|
+
| `:semver` | Use semantic versions, e.g. `1.0`, `1.1`, and `2.0`. |
|
369
|
+
| `:date` | Use date versions, e.g. `2020-09-02`, `2021-01-01`. |
|
370
|
+
| `:integer` | Use integer versions, e.g. `1`, `2`, and `3`. |
|
371
|
+
| `:float` | Use float versions, e.g. `1.0`, `1.1`, and `2.0`. |
|
372
|
+
| `:string` | Use string versions, e.g. `a`, `b`, and `z`. |
|
373
|
+
|
374
|
+
All versions will be sorted according to the format's type.
|
358
375
|
|
359
376
|
## Testing
|
360
377
|
|
361
|
-
Using
|
378
|
+
Using data migrations allows for easier testing of migrations. For example, using Rspec:
|
362
379
|
|
363
380
|
```ruby
|
364
381
|
describe CombineNamesForUserMigration do
|
@@ -390,7 +407,7 @@ end
|
|
390
407
|
|
391
408
|
## Tips and tricks
|
392
409
|
|
393
|
-
Over the years, we're learned a thing or two about
|
410
|
+
Over the years, we're learned a thing or two about versioning an API. We'll share tips here.
|
394
411
|
|
395
412
|
### Use pattern matching
|
396
413
|
|
@@ -544,14 +561,14 @@ end
|
|
544
561
|
|
545
562
|
### Avoid migrate for request migrations
|
546
563
|
|
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.
|
564
|
+
Avoid using `migrate` for request migrations. If you do, then data migrations, e.g. for
|
565
|
+
webhooks, will attempt to apply the request migrations. This may erroneously produce bad
|
566
|
+
output, or even undo a response migration. Instead, keep all request migration logic,
|
567
|
+
e.g. transforming params, inside of the `request` block.
|
551
568
|
|
552
569
|
```ruby
|
553
570
|
class SomeMigration < RequestMigrations::Migration
|
554
|
-
# Bad (side-effects for
|
571
|
+
# Bad (side-effects for data migrations)
|
555
572
|
migrate do |params|
|
556
573
|
params[:foo] = params.delete(:bar)
|
557
574
|
end
|
@@ -570,8 +587,8 @@ end
|
|
570
587
|
### Avoid routing contraints
|
571
588
|
|
572
589
|
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.
|
590
|
+
during upgrades. Consider only making _additive_ changes. Instead, consider removing or
|
591
|
+
hiding the documenation for old or deprecated endpoints, to limit any new usage.
|
575
592
|
|
576
593
|
```ruby
|
577
594
|
Rails.application.routes.draw do
|
@@ -591,7 +608,7 @@ end
|
|
591
608
|
|
592
609
|
### Avoid n+1s
|
593
610
|
|
594
|
-
Avoid introducing n+1 queries in your
|
611
|
+
Avoid introducing n+1 queries in your migrations. Try to utilize the current data you have
|
595
612
|
to perform more meaningful queries, returning only the data needed for the migration.
|
596
613
|
|
597
614
|
```ruby
|
@@ -642,7 +659,7 @@ end
|
|
642
659
|
```
|
643
660
|
|
644
661
|
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.
|
662
|
+
to get the data we need in order to complete the migration.
|
646
663
|
|
647
664
|
---
|
648
665
|
|
@@ -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
|
|
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.0.
|
4
|
+
version: 1.0.2
|
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-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|