request_migrations 1.0.1 → 1.0.2
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 +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
|
[](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
|
|
@@ -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
|