my_api_client 1.3.1 → 2.0.0

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.
Files changed (248) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +8 -0
  3. data/.envrc.skeleton +0 -1
  4. data/.github/dependabot.yml +82 -7
  5. data/.github/workflows/ci.yml +334 -0
  6. data/.github/workflows/dependabot-auto-merge.yml +96 -0
  7. data/.github/workflows/release.yml +33 -0
  8. data/.github/workflows/rubocop_challenge.yml +33 -0
  9. data/.gitignore +1 -0
  10. data/.rubocop.yml +1 -1
  11. data/.ruby-version +1 -1
  12. data/AGENTS.md +128 -0
  13. data/CHANGELOG.md +54 -2
  14. data/CLAUDE.md +6 -0
  15. data/Dockerfile +22 -0
  16. data/Gemfile +1 -1
  17. data/Gemfile.lock +67 -59
  18. data/README.md +398 -62
  19. data/docker-compose.yml +29 -0
  20. data/docs/runbooks/dependabot_pr_auto_merge.md +113 -0
  21. data/gemfiles/rails_7.2.gemfile +1 -1
  22. data/gemfiles/{rails_7.1.gemfile → rails_8.0.gemfile} +2 -2
  23. data/gemfiles/{rails_6.1.gemfile → rails_8.1.gemfile} +2 -2
  24. data/lib/my_api_client/default_error_handlers.rb +1 -1
  25. data/lib/my_api_client/errors/client_error.rb +5 -1
  26. data/lib/my_api_client/integrations/bugsnag.rb +1 -1
  27. data/lib/my_api_client/version.rb +1 -1
  28. data/lib/my_api_client.rb +1 -1
  29. data/my_api/.ruby-version +1 -1
  30. data/my_api/AGENTS.md +35 -0
  31. data/my_api/Dockerfile +25 -0
  32. data/my_api/Gemfile +5 -16
  33. data/my_api/Gemfile.lock +263 -228
  34. data/my_api/README.md +36 -25
  35. data/my_api/Rakefile +3 -2
  36. data/my_api/app/controllers/application_controller.rb +1 -2
  37. data/my_api/app/controllers/error_controller.rb +2 -2
  38. data/my_api/app/controllers/header_controller.rb +6 -3
  39. data/my_api/app/controllers/pagination_controller.rb +5 -15
  40. data/my_api/app/controllers/rest_controller.rb +12 -16
  41. data/my_api/app/controllers/status_controller.rb +3 -4
  42. data/my_api/config/application.rb +12 -67
  43. data/{rails_app/rails_6.1 → my_api}/config/boot.rb +1 -1
  44. data/{rails_app/rails_7.1 → my_api}/config/environment.rb +0 -2
  45. data/my_api/config/environments/development.rb +6 -6
  46. data/my_api/config/environments/production.rb +5 -8
  47. data/my_api/config/environments/test.rb +5 -6
  48. data/my_api/config/routes.rb +2 -10
  49. data/my_api/config.ru +2 -4
  50. data/my_api/public/index.html +10 -90
  51. data/{rails_app/rails_7.0 → my_api}/spec/rails_helper.rb +3 -4
  52. data/my_api/spec/requests/error_controller_spec.rb +18 -0
  53. data/my_api/spec/requests/pagination_controller_spec.rb +22 -0
  54. data/my_api/spec/requests/rest_controller_spec.rb +22 -0
  55. data/my_api/spec/requests/status_controller_spec.rb +20 -0
  56. data/my_api/spec/spec_helper.rb +15 -23
  57. data/my_api_client.gemspec +2 -2
  58. data/rails_app/AGENTS.md +13 -0
  59. data/rails_app/rails_7.2/Dockerfile +1 -1
  60. data/rails_app/rails_7.2/Gemfile +2 -0
  61. data/rails_app/rails_7.2/Gemfile.lock +143 -122
  62. data/rails_app/{rails_7.1 → rails_8.0}/.dockerignore +16 -2
  63. data/rails_app/{rails_6.1 → rails_8.0}/.gitattributes +2 -1
  64. data/rails_app/rails_8.0/.github/workflows/ci.yml +39 -0
  65. data/rails_app/{rails_6.1 → rails_8.0}/.gitignore +11 -7
  66. data/rails_app/rails_8.0/.rubocop.yml +8 -0
  67. data/rails_app/rails_8.0/.ruby-version +1 -0
  68. data/rails_app/{rails_7.1 → rails_8.0}/Dockerfile +28 -18
  69. data/rails_app/rails_8.0/Gemfile +15 -0
  70. data/rails_app/rails_8.0/Gemfile.lock +286 -0
  71. data/rails_app/{rails_6.1 → rails_8.0}/Rakefile +1 -3
  72. data/rails_app/{rails_7.1 → rails_8.0}/app/controllers/application_controller.rb +0 -2
  73. data/rails_app/rails_8.0/app/jobs/application_job.rb +7 -0
  74. data/rails_app/rails_8.0/app/mailers/application_mailer.rb +4 -0
  75. data/rails_app/{rails_7.0 → rails_8.0}/app/models/application_record.rb +0 -2
  76. data/rails_app/rails_8.0/app/views/layouts/mailer.html.erb +13 -0
  77. data/rails_app/rails_8.0/app/views/layouts/mailer.text.erb +1 -0
  78. data/rails_app/rails_8.0/bin/brakeman +7 -0
  79. data/rails_app/rails_8.0/bin/dev +2 -0
  80. data/rails_app/rails_8.0/bin/docker-entrypoint +14 -0
  81. data/rails_app/rails_8.0/bin/rails +4 -0
  82. data/rails_app/rails_8.0/bin/rake +4 -0
  83. data/rails_app/rails_8.0/bin/rubocop +8 -0
  84. data/rails_app/{rails_7.1 → rails_8.0}/bin/setup +11 -12
  85. data/rails_app/rails_8.0/bin/thrust +5 -0
  86. data/rails_app/{rails_7.1 → rails_8.0}/config/application.rb +14 -16
  87. data/rails_app/rails_8.0/config/boot.rb +3 -0
  88. data/rails_app/rails_8.0/config/cable.yml +10 -0
  89. data/rails_app/rails_8.0/config/credentials.yml.enc +1 -0
  90. data/rails_app/{rails_7.1 → rails_8.0}/config/database.yml +18 -2
  91. data/rails_app/{rails_6.1 → rails_8.0}/config/environment.rb +1 -3
  92. data/rails_app/rails_8.0/config/environments/development.rb +70 -0
  93. data/rails_app/rails_8.0/config/environments/production.rb +86 -0
  94. data/rails_app/{rails_7.1 → rails_8.0}/config/environments/test.rb +16 -19
  95. data/rails_app/{rails_7.1 → rails_8.0}/config/initializers/cors.rb +0 -1
  96. data/rails_app/{rails_7.1 → rails_8.0}/config/initializers/filter_parameter_logging.rb +2 -4
  97. data/rails_app/{rails_7.0 → rails_8.0}/config/initializers/inflections.rb +0 -1
  98. data/rails_app/rails_8.0/config/puma.rb +41 -0
  99. data/rails_app/{rails_7.1 → rails_8.0}/config/routes.rb +1 -3
  100. data/rails_app/rails_8.0/config/storage.yml +34 -0
  101. data/rails_app/{rails_7.1 → rails_8.0}/config.ru +1 -3
  102. data/rails_app/{rails_7.1 → rails_8.0}/db/seeds.rb +0 -1
  103. data/rails_app/{rails_7.1 → rails_8.0}/spec/rails_helper.rb +1 -1
  104. data/rails_app/rails_8.1/.dockerignore +45 -0
  105. data/rails_app/rails_8.1/.gitattributes +9 -0
  106. data/rails_app/rails_8.1/.github/workflows/ci.yml +52 -0
  107. data/rails_app/rails_8.1/.gitignore +33 -0
  108. data/rails_app/rails_8.1/.rspec +1 -0
  109. data/rails_app/rails_8.1/.rubocop.yml +8 -0
  110. data/rails_app/rails_8.1/.ruby-version +1 -0
  111. data/rails_app/rails_8.1/Dockerfile +68 -0
  112. data/rails_app/rails_8.1/Gemfile +15 -0
  113. data/rails_app/rails_8.1/Gemfile.lock +289 -0
  114. data/rails_app/{rails_7.0 → rails_8.1}/Rakefile +1 -3
  115. data/rails_app/{rails_6.1 → rails_8.1}/app/controllers/application_controller.rb +0 -2
  116. data/rails_app/rails_8.1/app/jobs/application_job.rb +7 -0
  117. data/rails_app/rails_8.1/app/mailers/application_mailer.rb +4 -0
  118. data/rails_app/{rails_7.1 → rails_8.1}/app/models/application_record.rb +0 -2
  119. data/rails_app/rails_8.1/app/views/layouts/mailer.html.erb +13 -0
  120. data/rails_app/rails_8.1/app/views/layouts/mailer.text.erb +1 -0
  121. data/rails_app/rails_8.1/bin/brakeman +7 -0
  122. data/rails_app/rails_8.1/bin/bundler-audit +6 -0
  123. data/rails_app/rails_8.1/bin/ci +6 -0
  124. data/rails_app/rails_8.1/bin/dev +2 -0
  125. data/rails_app/{rails_7.1 → rails_8.1}/bin/docker-entrypoint +1 -1
  126. data/rails_app/rails_8.1/bin/rails +4 -0
  127. data/rails_app/rails_8.1/bin/rake +4 -0
  128. data/rails_app/rails_8.1/bin/rubocop +8 -0
  129. data/rails_app/{rails_7.0 → rails_8.1}/bin/setup +13 -13
  130. data/rails_app/rails_8.1/bin/thrust +5 -0
  131. data/rails_app/{rails_6.1 → rails_8.1}/config/application.rb +19 -17
  132. data/rails_app/rails_8.1/config/boot.rb +3 -0
  133. data/rails_app/rails_8.1/config/bundler-audit.yml +5 -0
  134. data/rails_app/rails_8.1/config/cable.yml +10 -0
  135. data/rails_app/rails_8.1/config/ci.rb +19 -0
  136. data/rails_app/rails_8.1/config/credentials.yml.enc +1 -0
  137. data/rails_app/rails_8.1/config/database.yml +40 -0
  138. data/rails_app/{rails_7.0 → rails_8.1}/config/environment.rb +1 -3
  139. data/rails_app/rails_8.1/config/environments/development.rb +73 -0
  140. data/rails_app/rails_8.1/config/environments/production.rb +86 -0
  141. data/rails_app/rails_8.1/config/environments/test.rb +53 -0
  142. data/rails_app/{rails_7.0 → rails_8.1}/config/initializers/cors.rb +1 -2
  143. data/rails_app/rails_8.1/config/initializers/filter_parameter_logging.rb +8 -0
  144. data/rails_app/{rails_7.1 → rails_8.1}/config/initializers/inflections.rb +0 -1
  145. data/rails_app/rails_8.1/config/locales/en.yml +31 -0
  146. data/rails_app/rails_8.1/config/puma.rb +42 -0
  147. data/rails_app/rails_8.1/config/routes.rb +10 -0
  148. data/rails_app/rails_8.1/config/storage.yml +27 -0
  149. data/rails_app/{rails_7.0 → rails_8.1}/config.ru +1 -3
  150. data/rails_app/rails_8.1/db/seeds.rb +9 -0
  151. data/rails_app/{rails_6.1 → rails_8.1}/spec/rails_helper.rb +1 -2
  152. metadata +139 -136
  153. data/.circleci/config.yml +0 -344
  154. data/README.jp.md +0 -785
  155. data/gemfiles/rails_7.0.gemfile +0 -15
  156. data/my_api/.envrc.skeleton +0 -3
  157. data/my_api/.jetskeep +0 -1
  158. data/my_api/Procfile +0 -7
  159. data/my_api/app/helpers/application_helper.rb +0 -5
  160. data/my_api/app/jobs/application_job.rb +0 -7
  161. data/my_api/app/models/application_item.rb +0 -5
  162. data/my_api/config/dynamodb.yml +0 -22
  163. data/my_api/spec/controllers/error_controller_spec.rb +0 -43
  164. data/my_api/spec/controllers/header_controller_spec.rb +0 -33
  165. data/my_api/spec/controllers/pagination_controller_spec.rb +0 -73
  166. data/my_api/spec/controllers/rest_controller_spec.rb +0 -99
  167. data/my_api/spec/controllers/status_controller_spec.rb +0 -47
  168. data/my_api/spec/fixtures/payloads/posts-index.json +0 -51
  169. data/my_api/spec/fixtures/payloads/posts-show.json +0 -53
  170. data/rails_app/rails_6.1/.rspec +0 -3
  171. data/rails_app/rails_6.1/Gemfile +0 -17
  172. data/rails_app/rails_6.1/Gemfile.lock +0 -195
  173. data/rails_app/rails_6.1/app/models/application_record.rb +0 -5
  174. data/rails_app/rails_6.1/bin/bundle +0 -122
  175. data/rails_app/rails_6.1/bin/rails +0 -6
  176. data/rails_app/rails_6.1/bin/rake +0 -6
  177. data/rails_app/rails_6.1/bin/setup +0 -35
  178. data/rails_app/rails_6.1/config/credentials.yml.enc +0 -1
  179. data/rails_app/rails_6.1/config/database.yml +0 -25
  180. data/rails_app/rails_6.1/config/environments/development.rb +0 -59
  181. data/rails_app/rails_6.1/config/environments/production.rb +0 -97
  182. data/rails_app/rails_6.1/config/environments/test.rb +0 -51
  183. data/rails_app/rails_6.1/config/initializers/application_controller_renderer.rb +0 -9
  184. data/rails_app/rails_6.1/config/initializers/backtrace_silencers.rb +0 -10
  185. data/rails_app/rails_6.1/config/initializers/cors.rb +0 -17
  186. data/rails_app/rails_6.1/config/initializers/filter_parameter_logging.rb +0 -8
  187. data/rails_app/rails_6.1/config/initializers/inflections.rb +0 -17
  188. data/rails_app/rails_6.1/config/initializers/mime_types.rb +0 -5
  189. data/rails_app/rails_6.1/config/initializers/wrap_parameters.rb +0 -16
  190. data/rails_app/rails_6.1/config/locales/en.yml +0 -33
  191. data/rails_app/rails_6.1/config/routes.rb +0 -5
  192. data/rails_app/rails_6.1/config.ru +0 -8
  193. data/rails_app/rails_6.1/db/seeds.rb +0 -8
  194. data/rails_app/rails_7.0/Gemfile +0 -13
  195. data/rails_app/rails_7.0/Gemfile.lock +0 -212
  196. data/rails_app/rails_7.0/app/controllers/application_controller.rb +0 -4
  197. data/rails_app/rails_7.0/bin/bundle +0 -122
  198. data/rails_app/rails_7.0/bin/rails +0 -6
  199. data/rails_app/rails_7.0/bin/rake +0 -6
  200. data/rails_app/rails_7.0/config/application.rb +0 -41
  201. data/rails_app/rails_7.0/config/boot.rb +0 -5
  202. data/rails_app/rails_7.0/config/credentials.yml.enc +0 -1
  203. data/rails_app/rails_7.0/config/database.yml +0 -25
  204. data/rails_app/rails_7.0/config/environments/development.rb +0 -58
  205. data/rails_app/rails_7.0/config/environments/production.rb +0 -70
  206. data/rails_app/rails_7.0/config/environments/test.rb +0 -52
  207. data/rails_app/rails_7.0/config/initializers/filter_parameter_logging.rb +0 -8
  208. data/rails_app/rails_7.0/config/locales/en.yml +0 -33
  209. data/rails_app/rails_7.0/config/routes.rb +0 -8
  210. data/rails_app/rails_7.0/db/seeds.rb +0 -8
  211. data/rails_app/rails_7.1/Gemfile +0 -16
  212. data/rails_app/rails_7.1/Gemfile.lock +0 -241
  213. data/rails_app/rails_7.1/README.md +0 -24
  214. data/rails_app/rails_7.1/Rakefile +0 -8
  215. data/rails_app/rails_7.1/bin/bundle +0 -122
  216. data/rails_app/rails_7.1/bin/rails +0 -6
  217. data/rails_app/rails_7.1/bin/rake +0 -6
  218. data/rails_app/rails_7.1/config/boot.rb +0 -5
  219. data/rails_app/rails_7.1/config/credentials.yml.enc +0 -1
  220. data/rails_app/rails_7.1/config/environments/development.rb +0 -58
  221. data/rails_app/rails_7.1/config/environments/production.rb +0 -74
  222. data/rails_app/rails_7.1/config/puma.rb +0 -46
  223. data/rails_app/rails_7.1/public/robots.txt +0 -2
  224. data/rails_app/rails_7.1/spec/spec_helper.rb +0 -13
  225. data/rails_app/rails_7.1/vendor/.keep +0 -0
  226. data/rails_app/rails_7.2/.github/dependabot.yml +0 -12
  227. /data/rails_app/{rails_7.1 → rails_8.0}/.rspec +0 -0
  228. /data/rails_app/{rails_6.1 → rails_8.0}/README.md +0 -0
  229. /data/rails_app/{rails_6.1 → rails_8.0}/app/controllers/concerns/.keep +0 -0
  230. /data/rails_app/{rails_6.1/app/javascript → rails_8.0/app/models/concerns}/.keep +0 -0
  231. /data/rails_app/{rails_7.1 → rails_8.0}/config/locales/en.yml +0 -0
  232. /data/rails_app/{rails_6.1/app/models/concerns → rails_8.0/lib/tasks}/.keep +0 -0
  233. /data/rails_app/{rails_6.1/lib/tasks → rails_8.0/log}/.keep +0 -0
  234. /data/rails_app/{rails_6.1 → rails_8.0}/public/robots.txt +0 -0
  235. /data/rails_app/{rails_6.1/tmp → rails_8.0/script}/.keep +0 -0
  236. /data/rails_app/{rails_6.1 → rails_8.0}/spec/spec_helper.rb +0 -0
  237. /data/rails_app/{rails_6.1/tmp/pids → rails_8.0/storage}/.keep +0 -0
  238. /data/rails_app/{rails_6.1 → rails_8.0}/vendor/.keep +0 -0
  239. /data/rails_app/{rails_7.0 → rails_8.1}/README.md +0 -0
  240. /data/rails_app/{rails_7.1 → rails_8.1}/app/controllers/concerns/.keep +0 -0
  241. /data/rails_app/{rails_7.1 → rails_8.1}/app/models/concerns/.keep +0 -0
  242. /data/rails_app/{rails_7.1 → rails_8.1}/lib/tasks/.keep +0 -0
  243. /data/rails_app/{rails_7.1/storage → rails_8.1/log}/.keep +0 -0
  244. /data/rails_app/{rails_7.0 → rails_8.1}/public/robots.txt +0 -0
  245. /data/rails_app/{rails_7.1/tmp → rails_8.1/script}/.keep +0 -0
  246. /data/rails_app/{rails_7.0 → rails_8.1}/spec/spec_helper.rb +0 -0
  247. /data/rails_app/{rails_7.1/tmp/pids → rails_8.1/storage}/.keep +0 -0
  248. /data/rails_app/{rails_7.1/tmp/storage → rails_8.1/vendor}/.keep +0 -0
data/README.jp.md DELETED
@@ -1,785 +0,0 @@
1
- [![CircleCI](https://circleci.com/gh/ryz310/my_api_client.svg?style=svg)](https://circleci.com/gh/ryz310/my_api_client) [![Gem Version](https://badge.fury.io/rb/my_api_client.svg)](https://badge.fury.io/rb/my_api_client) [![Maintainability](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/maintainability)](https://codeclimate.com/github/ryz310/my_api_client/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/test_coverage)](https://codeclimate.com/github/ryz310/my_api_client/test_coverage) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=ryz310/my_api_client)](https://dependabot.com)
2
-
3
- # MyApiClient
4
-
5
- MyApiClient は API リクエストクラスを作成するための汎用的な機能を提供します。[Sawyer](https://github.com/lostisland/sawyer) や [Faraday](https://github.com/lostisland/faraday) をベースにエラーハンドリングの機能を強化した構造になっています。
6
-
7
- ただし、 Sawyer はダミーデータの作成が難しかったり、他の gem で競合することがよくあるので、将来的には依存しないように変更していくかもしれません。
8
-
9
- また、 Ruby on Rails で利用することを想定してますが、それ以外の環境でも動作するように作っています。不具合などあれば Issue ページからご報告下さい。
10
-
11
- [toc]
12
-
13
- ## Supported Versions
14
-
15
- - Ruby 3.1, 3.2, 3.3
16
- - Rails 6.1, 7.0, 7.1, 7.2
17
-
18
- ## Installation
19
-
20
- `my_api_client` を Gemfile に追加して下さい:
21
-
22
- ```ruby
23
- gem 'my_api_client'
24
- ```
25
-
26
- Ruby on Rails を利用している場合は `generator` 機能を利用できます。
27
-
28
- ```sh
29
- $ rails g api_client path/to/resource get:path/to/resource --endpoint https://example.com
30
-
31
- create app/api_clients/application_api_client.rb
32
- create app/api_clients/path/to/resource_api_client.rb
33
- invoke rspec
34
- create spec/api_clients/path/to/resource_api_client_spec.rb
35
- ```
36
-
37
- ## Usage
38
-
39
- ### Basic
40
-
41
- 最もシンプルな利用例を以下に示します。
42
-
43
- ```ruby
44
- class ExampleApiClient < MyApiClient::Base
45
- endpoint 'https://example.com/v1'
46
-
47
- attr_reader :access_token
48
-
49
- def initialize(access_token:)
50
- @access_token = access_token
51
- end
52
-
53
- # GET https://example.com/v1/users
54
- #
55
- # @return [Sawyer::Resource] HTTP resource parameter
56
- def get_users
57
- get 'users', headers: headers, query: { key: 'value' }
58
- end
59
-
60
- # POST https://example.com/v1/users
61
- #
62
- # @param name [String] Username which want to create
63
- # @return [Sawyer::Resource] HTTP resource parameter
64
- def post_user(name:)
65
- post 'users', headers: headers, body: { name: name }
66
- end
67
-
68
- private
69
-
70
- def headers
71
- {
72
- 'Content-Type': 'application/json;charset=UTF-8',
73
- 'Authorization': "Bearer #{access_token}",
74
- }
75
- end
76
- end
77
-
78
- api_clinet = ExampleApiClient.new(access_token: 'access_token')
79
- api_clinet.get_users #=> #<Sawyer::Resource>
80
- ```
81
-
82
- クラス定義の最初に記述される `endpoint` にはリクエスト URL の共通部分を定義します。後述の各メソッドで後続の path を定義しますが、上記の例だと `get 'users'` と定義すると、 `GET https://example.com/v1/users` というリクエストが実行されます。
83
-
84
- 次に、 `#initialize` を定義します。上記の例のように Access Token や API Key などを設定することを想定します。必要なければ定義の省略も可能です。
85
-
86
- 続いて、 `#get_users` や `#post_user` を定義します。メソッド名には API のタイトルを付けると良いと思います。メソッド内部で `#get` や `#post` を呼び出していますが、これがリクエスト時の HTTP Method になります。他にも `#patch` `#put` `#delete` が利用可能です。
87
-
88
- ### Pagination
89
-
90
- API の中にはレスポンスに結果の続きを取得するための URL を含んでいるものがあります。
91
-
92
- MyApiClient では、このような API を enumerable に扱うための `#pageable_get` というメソッドを用意しています。以下に例を示します。
93
-
94
- ```ruby
95
- class MyPaginationApiClient < ApplicationApiClient
96
- endpoint 'https://example.com/v1'
97
-
98
- # GET pagination?page=1
99
- def pagination
100
- pageable_get 'pagination', paging: '$.links.next', headers: headers, query: { page: 1 }
101
- end
102
-
103
- private
104
-
105
- def headers
106
- { 'Content-Type': 'application/json;charset=UTF-8' }
107
- end
108
- end
109
- ```
110
-
111
- 上記の例の場合、最初に `GET https://example.com/v1/pagination?page=1` に対してリクエストが実行され、続けてレスポンス JSON の `$.link.next` に含まれる URL に対して enumerable にリクエストを実行します。
112
-
113
- 例えば以下のようなレスポンスであれば、`$.link.next` は `"https://example.com/pagination?page=3"` を示します。
114
-
115
- ```json
116
- {
117
- "links": {
118
- "next": "https://example.com/pagination?page=3",
119
- "previous": "https://example.com/pagination?page=1"
120
- },
121
- "page": 2
122
- }
123
- ```
124
-
125
- そして `#pageable_get` は [Enumerator::Lazy](https://docs.ruby-lang.org/ja/latest/class/Enumerator=3a=3aLazy.html) を返すので、 `#each` や `#next` を実行することで次の結果を取得できます。
126
-
127
- ```ruby
128
- api_clinet = MyPaginationApiClient.new
129
- api_clinet.pagination.each do |response|
130
- # Do something.
131
- end
132
-
133
- result = api_clinet.pagination
134
- result.next # => 1st page result
135
- result.next # => 2nd page result
136
- result.next # => 3rd page result
137
- ```
138
-
139
- なお、`#each` はレスポンスに含まれる `paging` の値が `nil` になるまで繰り返されるのでご注意ください。例えば `#take` と組み合わせることでページネーションの上限を設定できます。
140
-
141
- `#pageable_get` の alias として `#pget` も利用可能です。
142
-
143
- ```ruby
144
- # GET pagination?page=1
145
- def pagination
146
- pget 'pagination', paging: '$.links.next', headers: headers, query: { page: 1 }
147
- end
148
- ```
149
-
150
- ### Error handling
151
-
152
- `my_api_client` ではレスポンスの内容によって例外を発生させるエラーハンドリングを定義できます。ここでは例として前述のコードにエラーハンドリングを定義しています。
153
-
154
- ```ruby
155
- class ExampleApiClient < MyApiClient::Base
156
- endpoint 'https://example.com'
157
-
158
- error_handling status_code: 400..499,
159
- raise: MyApiClient::ClientError
160
-
161
- error_handling status_code: 500..599, raise: MyApiClient::ServerError do |_params, logger|
162
- logger.warn 'Server error occurred.'
163
- end
164
-
165
- error_handling json: { '$.errors.code': 10..19 },
166
- raise: MyApiClient::ClientError,
167
- with: :my_error_handling
168
-
169
- # Omission...
170
-
171
- private
172
-
173
- # @param params [MyApiClient::Params::Params] HTTP reqest and response params
174
- # @param logger [MyApiClient::Request::Logger] Logger for a request processing
175
- def my_error_handling(params, logger)
176
- logger.warn "Response Body: #{params.response.body.inspect}"
177
- end
178
- end
179
- ```
180
-
181
- 一つずつ解説していきます。まず、以下のように `status_code` を指定するものについて。
182
-
183
- ```ruby
184
- error_handling status_code: 400..499, raise: MyApiClient::ClientError
185
- ```
186
-
187
- これは `ExampleApiClient` からのリクエスト全てにおいて、レスポンスのステータスコードが `400..499` であった場合に `MyApiClient::ClientError` が例外として発生するようになります。 `ExampleApiClient` を継承したクラスにもエラーハンドリングは適用されます。ステータスコードのエラーハンドリングは親クラスで定義すると良いと思います。
188
-
189
- なお、 `status_code` には `Integer` `Range` `Regexp` が指定可能です。
190
-
191
- `raise` には `MyApiClient::Error` を継承したクラスが指定可能です。`my_api_client` で標準で定義しているエラークラスについては以下のソースコードをご確認下さい。 `raise` を省略した場合は `MyApiClient::Error` を発生するようになります。
192
-
193
- https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/errors
194
-
195
- 次に、 `block` を指定する場合について。
196
-
197
- ```ruby
198
- error_handling status_code: 500..599, raise: MyApiClient::ServerError do |_params, logger|
199
- logger.warn 'Server error occurred.'
200
- end
201
- ```
202
-
203
- 上記の例であれば、ステータスコードが `500..599` の場合に `MyApiClient::ServerError` を発生させる前に `block` の内容が実行れます。引数の `params` にはリクエスト情報とレスポンス情報が含まれています。`logger` はログ出力用インスタンスですが、このインスタンスを使ってログ出力すると、以下のようにリクエスト情報がログ出力に含まれるようになり、デバッグの際に便利です。
204
-
205
- ```text
206
- API request `GET https://example.com/path/to/resouce`: "Server error occurred."
207
- ```
208
-
209
- `json` には `Hash` の Key に [JSONPath](https://goessner.net/articles/JsonPath/) を指定して、レスポンス JSON から任意の値を取得し、 Value とマッチするかどうかでエラーハンドリングできます。Value には `String` `Integer` `Range` `Regexp` が指定可能です。上記の場合であれば、以下のような JSON にマッチします。
210
-
211
- ```ruby
212
- error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
213
- ```
214
-
215
- ```json
216
- {
217
- "erros": {
218
- "code": 10,
219
- "message": "Some error has occurred."
220
- }
221
- }
222
- ```
223
-
224
- `headers` には `Hash` の Key に レスポンスのヘッダーキーを指定して、 Value とマッチするかどうかでエラーハンドリングできます。Value には `String` `Regexp` が指定可能です。
225
-
226
- ```ruby
227
- error_handling headers: { 'www-authenticate': /invalid token/ }, with: :my_error_handling
228
- ```
229
-
230
- 上記の場合であれば、以下のような レスポンスヘッダー にマッチします。
231
-
232
- ```text
233
- cache-control: no-cache, no-store, max-age=0, must-revalidate
234
- content-type: application/json
235
- www-authenticate: Bearer error="invalid_token", error_description="invalid token"
236
- content-length: 104
237
- ```
238
-
239
- `with` にはインスタンスメソッド名を指定することで、エラーを検出した際、例外を発生させる前に任意のメソッドを実行させることができます。メソッドに渡される引数は `block` 定義の場合と同じく `params` と `logger` です。なお、 `block` と `with` は同時には利用できません。
240
-
241
- ```ruby
242
- # @param params [MyApiClient::Params::Params] HTTP req and res params
243
- # @param logger [MyApiClient::Request::Logger] Logger for a request processing
244
- def my_error_handling(params, logger)
245
- logger.warn "Response Body: #{params.response.body.inspect}"
246
- end
247
- ```
248
-
249
- #### Default error handling
250
-
251
- `my_api_client` では、標準でステータスコード 400 ~ 500 番台のレスポンスを例外として処理するようにしています。ステータスコードが 400 番台場合は `MyApiClient::ClientError`、 500 番台の場合は `MyApiClient::ServerError` を継承した例外クラスが raise されます。
252
-
253
- また、 `MyApiClient::NetworkError` に対しても標準で `retry_on` が定義されています。
254
-
255
- いずれも override 可能ですので、必要に応じて `error_handling` を定義して下さい。
256
-
257
- 以下のファイルで定義しています。
258
-
259
- https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/default_error_handlers.rb
260
-
261
- #### Symbol を利用する
262
-
263
- ```ruby
264
- error_handling json: { '$.errors.code': :negative? }
265
- ```
266
-
267
- 実験的な機能ですが、`status` や `json` の Value に `Symbol` を指定することで、結果値に対してメソッド呼び出しを行い、結果を判定させる事ができます。上記の場合、以下のような JSON にマッチします。なお、対象 Object に `#negative?` が存在しない場合はメソッドは呼び出されません。
268
-
269
- ```json
270
- {
271
- "erros": {
272
- "code": -1,
273
- "message": "Some error has occurred."
274
- }
275
- }
276
- ```
277
-
278
- #### forbid_nil
279
-
280
- ```ruby
281
- error_handling status_code: 200, json: :forbid_nil
282
- ```
283
-
284
- 一部のサービスではサーバーから何らかの Response Body が返ってくる事を期待しているにも関わらず、空の結果が結果が返ってくるというケースがあるようです。こちらも実験的な機能ですが、そういったケースを検出するために `json: :forbid_nil` オプションを用意しました。通常の場合、Response Body が空の場合はエラー判定をしませんが、このオプションを指定するとエラーとして検知する様になります。正常応答が空となる API も存在するので、誤検知にご注意下さい。
285
-
286
- #### MyApiClient::Params::Params
287
-
288
- WIP
289
-
290
- #### MyApiClient::Error
291
-
292
- API リクエストのレスポンスが `error_handling` で定義した matcher に合致した場合、 `raise` で指定した例外処理が発生します。この例外クラスは `MyApiClient::Error` を継承している必要があります。
293
-
294
- この例外クラスには `#params` というメソッドが存在し、リクエストやレスポンスのパラメータを参照することが出来ます。
295
-
296
- ```ruby
297
- begin
298
- api_client.request
299
- rescue MyApiClient::Error => e
300
- e.params.inspect
301
- # => {
302
- # :request=>"#<MyApiClient::Params::Request#inspect>",
303
- # :response=>"#<Sawyer::Response#inspect>",
304
- # }
305
- end
306
- ```
307
-
308
- #### Bugsnag breadcrumbs
309
-
310
- [Bugsnag-Ruby v6.11.0](https://github.com/bugsnag/bugsnag-ruby/releases/tag/v6.11.0) 以降を利用している場合は [breadcrumbs 機能](https://docs.bugsnag.com/platforms/ruby/other/#logging-breadcrumbs) が自動的にサポートされます。この機能によって `MyApiClient::Error` 発生時に内部で `Bugsnag.leave_breadcrumb` が呼び出され、 Bugsnag のコンソールからエラー発生時のリクエスト情報、レスポンス情報などが確認できるようになります。
311
-
312
- ### Retry
313
-
314
- 次に `MyApiClient` が提供するリトライ機能についてご紹介致します。
315
-
316
- ```ruby
317
- class ExampleApiClient < MyApiClient::Base
318
- endpoint 'https://example.com'
319
-
320
- retry_on MyApiClient::NetworkError, wait: 0.1.seconds, attempts: 3
321
- retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
322
-
323
- error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError
324
- end
325
- ```
326
-
327
- API リクエストを何度も実行していると回線の不調などによりネットワークエラーが発生する事があります。長時間ネットワークが使えなくなるケースもありますが、瞬間的なエラーであるケースも多々あります。 `MyApiClient` ではネットワーク系の例外はまとめて `MyApiClient::NetworkError` として `raise` されます。この例外の詳細は後述しますが、 `retry_on` を利用する事で、 `ActiveJob` のように任意の例外処理を補足して、一定回数、一定の期間を空けて API リクエストをリトライさせる事ができます。
328
-
329
- なお、 `retry_on MyApiClient::NetworkError` は標準実装されているため、特別に定義せずとも自動的に適用されます。 `wait` や `attempts` に任意の値を設定したい場合のみ定義してご利用ください。
330
-
331
- ただし、 `ActiveJob` とは異なり同期処理でリトライするため、ネットワークの瞬断に備えたリトライ以外ではあまり使う機会はないのではないかと思います。上記の例のように API Rate Limit に備えてリトライするケースもあるかと思いますが、こちらは `ActiveJob` で対応した方が良いかもしれません。
332
-
333
- ちなみに一応 `discard_on` も実装していますが、作者自身が有効な用途を見出せていないので、詳細は割愛します。良い利用方法があれば教えてください。
334
-
335
- #### 便利な使い方
336
-
337
- `error_handling` に `retry` オプションを付与する事で `retry_on` の定義を省略できます。
338
- 例えば以下の 2 つのコードは同じ意味になります。
339
-
340
- ```ruby
341
- retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
342
- error_handling json: { '$.errors.code': 20 },
343
- raise: MyApiClient::ApiLimitError
344
- ```
345
-
346
- ```ruby
347
- error_handling json: { '$.errors.code': 20 },
348
- raise: MyApiClient::ApiLimitError,
349
- retry: { wait: 30.seconds, attempts: 3 }
350
- ```
351
-
352
- `retry_on` で `wait` や `attempts` を指定する必要がない場合は `retry: true` という記述で動作します。
353
-
354
- ```ruby
355
- error_handling json: { '$.errors.code': 20 },
356
- raise: MyApiClient::ApiLimitError,
357
- retry: true
358
- ```
359
-
360
- `retry` オプションを使用する際は以下の点に注意が必要です。
361
-
362
- - `error_handling` に `raise` オプションの指定が必須となります。
363
- - Block を使った `error_handling` の定義は禁止されます。
364
-
365
- #### MyApiClient::NetworkError
366
-
367
- 前述の通りですが、 `MyApiClient` ではネットワーク系の例外はまとめて `MyApiClient::NetworkError` として `raise` されます。他の例外と同じく `MyApiClient::Error` を親クラスとしています。 `MyApiClient::NetworkError` として扱われる例外クラスの一覧は `MyApiClient::NETWORK_ERRORS` で参照できます。また、元となった例外は `#original_error` で参照できます。
368
-
369
- ```ruby
370
- begin
371
- api_client.request
372
- rescue MyApiClient::NetworkError => e
373
- e.original_error # => #<Net::OpenTimeout>
374
- e.params.response # => nil
375
- end
376
- ```
377
-
378
- なお、通常の例外はリクエストの結果によって発生しますが、この例外はリクエスト中に発生するため、例外インスタンスにレスポンスパラメータは含まれません。
379
-
380
- ### Timeout
381
-
382
- WIP
383
-
384
- ### Logger
385
-
386
- WIP
387
-
388
- ## One request for one class
389
-
390
- 多くの場合、同一ホストの API は リクエストヘッダーやエラー情報が同じ構造になっているため、上記のように一つのクラス内に複数の API を定義する設計が理にかなっていますが、 API 毎に個別に定義したい場合は、以下のように 1 つのクラスに 1 の API という構造で設計することも可能です。
391
-
392
- ```ruby
393
- class ExampleApiClient < MyApiClient::Base
394
- endpoint 'https://example.com'
395
-
396
- error_handling status_code: 400..599
397
-
398
- attr_reader :access_token
399
-
400
- def initialize(access_token:)
401
- @access_token = access_token
402
- end
403
-
404
- private
405
-
406
- def headers
407
- {
408
- 'Content-Type': 'application/json;charset=UTF-8',
409
- 'Authorization': "Bearer #{access_token}",
410
- }
411
- end
412
- end
413
-
414
- class GetUsersApiClient < ExampleApiClient
415
- error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ClientError
416
-
417
- # GET https://example.com/users
418
- #
419
- # @return [Sawyer::Resource] HTTP resource parameter
420
- def request
421
- get 'users', query: { key: 'value' }, headers: headers
422
- end
423
- end
424
-
425
- class PostUserApiClient < ExampleApiClient
426
- error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ApiLimitError
427
-
428
- # POST https://example.com/users
429
- #
430
- # @param name [String] Username which want to create
431
- # @return [Sawyer::Resource] HTTP resource parameter
432
- def request(name:)
433
- post 'users', headers: headers, body: { name: name }
434
- end
435
- end
436
- ```
437
-
438
- ## RSpec
439
-
440
- ### Setup
441
-
442
- RSpec を使ったテストをサポートしています。
443
- 以下のコードを `spec/spec_helper.rb` (または `spec/rails_helper.rb`) に追記して下さい。
444
-
445
- ```ruby
446
- require 'my_api_client/rspec'
447
- ```
448
-
449
- ### Testing
450
-
451
- 以下のような `ApiClient` を定義しているとします。
452
-
453
- ```ruby
454
- class ExampleApiClient < MyApiClient::Base
455
- endpoint 'https://example.com/v1'
456
-
457
- error_handling status_code: 200, json: { '$.errors.code': 10 },
458
- raise: MyApiClient::ClientError
459
-
460
- attr_reader :access_token
461
-
462
- def initialize(access_token:)
463
- @access_token = access_token
464
- end
465
-
466
- # GET https://example.com/v1/users
467
- def get_users(condition:)
468
- get 'users', headers: headers, query: { search: condition }
469
- end
470
-
471
- private
472
-
473
- def headers
474
- {
475
- 'Content-Type': 'application/json;charset=UTF-8',
476
- 'Authorization': "Bearer #{access_token}",
477
- }
478
- end
479
- end
480
- ```
481
-
482
- 通常の場合 `ApiClient` を新たに定義した際にテストすべき項目が 2 つあります。
483
-
484
- 1. 指定したエンドポイントに対して、任意のパラメータを使ったリクエストが実行されること
485
- 2. 特定のレスポンスに対して適切にエラーハンドリングが実行されること
486
-
487
- `my_api_client` ではこれらをテストするための Custom Matcher を用意しています。
488
-
489
- #### 1. 指定したエンドポイントに対して、任意のパラメータを使ったリクエストが実行されること
490
-
491
- 例えば上述の `#get_users` の内部では、入力引数を用いて検索クエリが組み立てられていたり、 Header に `access_token` を利用したりしています。これらの値が正しくリクエストに用いられているかどうかのテストが必要となります。
492
-
493
- この場合 `request_to` と `with` という Custom Matcher を利用することで簡単にテストを記述することが出来ます。 `expect` にはブロック `{}` を指定する必要がある点にご注意ください。他にも `with` には `body` というキーワード引数も指定できます。
494
-
495
- ```ruby
496
- RSpec.describe ExampleApiClient, type: :api_client do
497
- let(:api_client) { described_class.new(access_token: 'access token') }
498
- let(:headers) do
499
- {
500
- 'Content-Type': 'application/json;charset=UTF-8',
501
- 'Authorization': 'Bearer access token',
502
- }
503
- end
504
-
505
- describe '#get_users' do
506
- it do
507
- expect { api_client.get_users(condition: 'condition') }
508
- .to request_to(:get, 'https://example.com/v1/users')
509
- .with(headers: headers, query: { condition: 'condition' })
510
- end
511
- end
512
- end
513
- ```
514
-
515
- #### 2. 特定のレスポンスに対して適切にエラーハンドリングが実行されること
516
-
517
- 次に `error_handling` についてのテストも記述していきます。ここではレスポンスのステータスコードが `200` かつ Body に `'$.errors.code': 10` という値が含まれていた場合は `MyApiClient::ClientError` を `raise` する、というエラーハンドリングが定義されています。
518
-
519
- ここでは `be_handled_as_an_error` と `when_receive` という Custom Matcher を利用します。ここでも `expect` にはブロック `{}` を指定する必要がある点にご注意ください。
520
-
521
- `be_handled_as_an_error` の引数には期待する例外クラスを指定します。 `when_receive` にはリクエスト結果としてどのような値が返ってきたのかを指定します。
522
-
523
- なお、 `error_handling` で例外を発生させないケースは現在想定していないため、これ以外の Custom Matcher は定義されていません。何かユースケースがあれば教えて下さい。
524
-
525
- ```ruby
526
- it do
527
- expect { api_client.get_users(condition: 'condition') }
528
- .to be_handled_as_an_error(MyApiClient::ClientError)
529
- .when_receive(status_code: 200, body: { errors: { code: 10 } }.to_json)
530
- end
531
- ```
532
-
533
- また、以下のように正常なレスポンスが返ってきた時に誤ってエラーハンドリングされていないかをテストすることもできます。
534
-
535
- ```ruby
536
- it do
537
- expect { api_client.get_users(condition: 'condition') }
538
- .not_to be_handled_as_an_error(MyApiClient::ClientError)
539
- .when_receive(status_code: 200, body: { users: { id: 1 } }.to_json)
540
- end
541
- ```
542
-
543
- ##### `retry_on` を定義している場合
544
-
545
- 以下のように `retry_on` を API Client に定義している場合:
546
-
547
- ```ruby
548
- class ExampleApiClient < MyApiClient::Base
549
- endpoint 'https://example.com'
550
-
551
- error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError
552
- retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
553
- end
554
- ```
555
-
556
- `after_retry` と `times` という Custom Matcher を利用することが出来ます。
557
-
558
- ```ruby
559
- it do
560
- expect { api_client.get_users(condition: 'condition') }
561
- .to be_handled_as_an_error(MyApiClient::ApiLimitError)
562
- .after_retry(3).times
563
- .when_receive(status_code: 200, body: { errors: { code: 20 } }.to_json)
564
- end
565
- ```
566
-
567
- ### Stubbing
568
-
569
- #### `response` option
570
-
571
- 以下のような `ApiClient` を定義しているとします。
572
-
573
- ```ruby
574
- class ExampleApiClient < MyApiClient::Base
575
- endpoint 'https://example.com'
576
-
577
- def request(user_id:)
578
- get "users/#{user_id}"
579
- end
580
- end
581
- ```
582
-
583
- `stub_api_client_all` や `stub_api_client` を使うことで、 `ExampleApiClient#request` をスタブ化することができます。これで `#request` を実行してもリアルな HTTP リクエストが実行されなくなります。
584
-
585
- ```ruby
586
- stub_api_client_all(
587
- ExampleApiClient,
588
- request: { response: { id: 12345 } }
589
- )
590
-
591
- response = ExampleApiClient.new.request(user_id: 1)
592
- response.id # => 12345
593
- ```
594
-
595
- `response` は省略することも可能です。
596
-
597
- ```ruby
598
- stub_api_client_all(
599
- ExampleApiClient,
600
- request: { id: 12345 }
601
- )
602
-
603
- response = ExampleApiClient.new.request(user_id: 1)
604
- response.id # => 12345
605
- ```
606
-
607
- #### Proc
608
-
609
- リクスエストパラメータを使ったレスポンスを返すようにスタブ化したい場合は、 `Proc` を利用することで実現できます。
610
-
611
- ```ruby
612
- stub_api_client_all(
613
- ExampleApiClient,
614
- request: ->(params) { { id: params[:user_id] } }
615
- )
616
-
617
- response = ExampleApiClient.new.request(user_id: 1)
618
- response.id # => 1
619
- ```
620
-
621
- #### Return value of `#stub_api_client_all` and `#stub_api_client`
622
-
623
- `#stub_api_client_all` や `#stub_api_client` の戻り値はスタブ化した API Client のスタブオブジェクトです。`receive` や `have_received` を使ったテストを書きたい場合は、これらの値を利用すると良いでしょう。
624
-
625
- ```ruby
626
- def execute_api_request
627
- ExampleApiClient.new.request(user_id: 1)
628
- end
629
-
630
- api_clinet = stub_api_client_all(ExampleApiClient, request: nil)
631
- execute_api_request
632
- expect(api_client).to have_received(:request).with(user_id: 1)
633
- ```
634
-
635
- #### `raise` option
636
-
637
- 例外が発生する場合のテストを書きたい場合は、 `raise` オプションを利用することができます。
638
-
639
- ```ruby
640
- def execute_api_request
641
- ExampleApiClient.new.request(user_id: 1)
642
- end
643
-
644
- stub_api_client_all(ExampleApiClient, request: { raise: MyApiClient::Error })
645
- expect { execute_api_request }.to raise_error(MyApiClient::Error)
646
- ```
647
-
648
- なお、発生した例外に含まれるレスポンスパラメータやステータスコードもスタブ化したい場合は、 `response` オプションと同時に指定することが可能です。
649
-
650
- ```ruby
651
- stub_api_client_all(
652
- ExampleApiClient,
653
- request: {
654
- raise: MyApiClient::Error,
655
- response: { message: 'error' },
656
- status_code: 429
657
- }
658
- )
659
-
660
- begin
661
- ExampleApiClient.new.request(user_id: 1)
662
- rescue MyApiClient::Error => e
663
- response_body = e.params.response.data.to_h
664
- expect(response_body).to eq(message: 'error')
665
- status_code = e.params.response.status
666
- expect(status_code).to eq(429)
667
- end
668
- ```
669
-
670
- #### `pageable` option
671
-
672
- `#pageable_get` (`#pget`) を使った実装用に `pageable` というオプションが利用できます。
673
- `pageable` に設定する値は `Enumerable` である必要があります。
674
-
675
- ```ruby
676
- stub_api_client_all(
677
- MyPaginationApiClient,
678
- pagination: {
679
- pageable: [
680
- { page: 1 },
681
- { page: 2 },
682
- { page: 3 },
683
- ],
684
- }
685
- )
686
-
687
- MyPaginationApiClient.new.pagination.each do |response|
688
- response.page #=> 1, 2, 3
689
- end
690
- ```
691
-
692
- なお、 `Enumerable` の各値にはここまで紹介した `response`, `raise`, `Proc` など全てのオプションが利用可能です。
693
-
694
- ```ruby
695
- stub_api_client_all(
696
- MyPaginationApiClient,
697
- pagination: {
698
- pageable: [
699
- { response: { page: 1 } },
700
- { page: 2 },
701
- ->(params) { { page: 3, user_id: params[:user_id] } },
702
- { raise: MyApiClient::ClientError::IamTeapot },
703
- ],
704
- }
705
- )
706
- ```
707
-
708
- また、 `Enumerator` を使えば無限に続くページネーションを定義することもできます。
709
-
710
- ```ruby
711
- stub_api_client_all(
712
- MyPaginationApiClient,
713
- pagination: {
714
- pageable: Enumerator.new do |y|
715
- loop.with_index(1) do |_, i|
716
- y << { page: i }
717
- end
718
- end,
719
- }
720
- )
721
- ```
722
-
723
- ## Deployment
724
-
725
- この gem のリリースには [gem_comet](https://github.com/ryz310/gem_comet) を利用しています。
726
- `gem_comet` の README.md にも使い方が載っていますが、備忘録のため、こちらにもリリースフローを記載しておきます。
727
-
728
- ### Preparement
729
-
730
- 以下のコマンドで `.envrc` を作成し、 `GITHUB_ACCESS_TOKEN` を設定します。
731
-
732
- ```sh
733
- $ cp .envrc.skeleton .envrc
734
- ```
735
-
736
- 以下のコマンドで `gem_comet` をインストールします。
737
-
738
- ```sh
739
- $ gem install gem_comet
740
- ```
741
-
742
- ### USAGE
743
-
744
- 以下のコマンドで、最後のリリースから現在までに merge した PR の一覧を確認できます。
745
-
746
- ```sh
747
- $ gem_comet changelog
748
- ```
749
-
750
- 以下のコマンドで gem のリリースを実行します。
751
- `{VERSION}` には新しく付与するバージョン番号を指定します。
752
-
753
- ```sh
754
- $ gem_comet release {VERSION}
755
- ```
756
-
757
- 実行すると、 https://github.com/ryz310/my_api_client/pulls に以下のような PR が作成されます。
758
-
759
- - [Update v0\.16\.1](https://github.com/ryz310/my_api_client/pull/297)
760
- - [Release v0\.16\.1](https://github.com/ryz310/my_api_client/pull/298)
761
-
762
- まず、 `Update v{VERSION}` という PR から merge に取り掛かります。
763
-
764
- PR のコメントにも TODO が記載されていますが、まず、バージョン番号が正しく採番されているかを確認します。
765
-
766
- See: [314a4c0](https://github.com/ryz310/my_api_client/pull/297/commits/314a4c06f66324ce77b640b1ee8db5c84ee038a2)
767
-
768
- 次に `CHANGELOG.md` を編集して、 CHANGELOG を見やすく整理します。
769
-
770
- See: [33a2d17](https://github.com/ryz310/my_api_client/pull/297/commits/33a2d1703c773813c837e74ee3181906b2f2e502)
771
-
772
- これらが整ったら、 `Update v{VERSION}` を merge します。
773
-
774
- これでリリース準備が整ったので、`Release v{VERSION}` の merge に取り掛かります。
775
-
776
- この PR にこれからリリースする gem に対する変更が全て載っています。
777
- 変更内容の最終確認をして、 CI も通ったことを確認したら `Release v{VERSION}` を merge します。
778
-
779
- あとは Circle CI 側で gem のリリースが自動実行されるので、暫く待ちましょう。
780
-
781
- お疲れさまでした :tea:
782
-
783
- ## Contributing
784
-
785
- 不具合の報告や Pull Request を歓迎しています。OSS という事で自分はなるべく頑張って英語を使うようにしていますが、日本語での報告でも大丈夫です :+1: