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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9ade55e7b503c08190a46b58d68dfc35f619c9cf949c12ef98e7cb58548d2b6
4
- data.tar.gz: 0e2a334989b6d79ec9296e9ef3c5866b42c0121e87cc2d9fa219835ff6e225e7
3
+ metadata.gz: '091e4436fcd439967859301cc299fccaf6081f5da326ed79813e7cbce3f4d86c'
4
+ data.tar.gz: 4be2f76ade4958a48794ee62e6184c964f747f6c59a44a12ef20dcbbcc8c32a0
5
5
  SHA512:
6
- metadata.gz: 15e401e45c34e2054505e2c85968cbe7b81b20b7e987b2034acce71ece1d8fb48ebf5906a2166c957cd6bbdb3f3a8e7fccdab66e27bccf1f1960288efa748f4d
7
- data.tar.gz: 97a1233a8c01c6d818e5682b06164bf15d6e75f890a6aebbc0188712e0fa996d0d40b48f1d72e83f478573d0e8bc97350f7450cd254bbf5a1cb0efe1a9992ba1
6
+ metadata.gz: e2a22c88034bd0a67f34849909d85686267a4fd2a62dfbcfcb963766f678397d141a628608b63e0549581fbd9e02184a938619a269c81e44bd1a094d2febe702
7
+ data.tar.gz: d92878fd3a359c744c4cc9354325a5fea7d89ca81e9c3aa2718bd033a8b7ed8f7840f8bce0ada3f170dfb8f3a1ea9f1362cff8b08effafee53ffc0b5b59911c4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1
4
+
5
+ - Fix application order of `request` migrations.
6
+
3
7
  ## 1.0.0
4
8
 
5
9
  - Initial release.
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. This gem was extracted
7
- from [Keygen](https://keygen.sh) and is being used in production to serve millions of API
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
- ![request_migrations diagram](https://user-images.githubusercontent.com/6979737/175406011-883b2671-152c-4e6e-8716-d6c4c3ed2676.png)
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
- `request_migrations` supports Ruby 3. We encourage you to upgrade if you're on an older
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 one-off migrations.
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
- and `me#show` resources, and only when the response is successfuly. In addition,
126
- the data is only migrated when the response body contains a user.
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 current version. Here, you can perform
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
- ### One-off migrations
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 one-off migrations (as well as offering
222
- a nice interface for pattern matching inputs).
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 one-off migration
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
- Now, we've successfully applied a migration to both our API responses, as well
281
- as to the webhook events we send. In this case, if our `event` matches the
282
- our user shape, e.g. `type: 'user'`, then the migration will be applied.
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 one-off migrations, this allows for easier testing.
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. You can also utilize routing constraints
290
- to remove an API endpoint entirely.
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 current version. Here, you can perform
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, and `2.0`. |
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 one-offs allows for easier testing of migrations. For example, using Rspec:
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 writing request migrations. We'll share tips here.
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, one-off migrations, e.g. for webhooks
548
- will apply the request migrations, which may erroneously produce bad output, or even undo a
549
- response migration. Instead, keep all request migration logic, e.g. transforming params,
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 one-off migrations)
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. You should remove docs for old
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 migration. Try to utilize the current data you have
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequestMigrations
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.1"
5
5
  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 [request] the block containing the migration.
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 [data] the block containing the migration.
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 [response] the block containing the migration.
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 [*] the data to be migrated.
18
+ # @param data [Any] the data to be migrated.
19
19
  #
20
- # @return [*] the migrated data.
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
 
@@ -1,4 +1,6 @@
1
1
  module RequestMigrations
2
+ ##
3
+ # @private
2
4
  class Railtie < ::Rails::Railtie
3
5
  ActionDispatch::Routing::Mapper.send(:include, Router::Constraints)
4
6
  end
@@ -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
@@ -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.0.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-06-24 00:00:00.000000000 Z
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.0'
91
+ version: '3.1'
91
92
  required_rubygems_version: !ruby/object:Gem::Requirement
92
93
  requirements:
93
94
  - - ">="