rom-http 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +81 -28
  3. data/README.md +15 -181
  4. data/lib/rom/http/associations/many_to_many.rb +12 -0
  5. data/lib/rom/http/associations/many_to_one.rb +20 -0
  6. data/lib/rom/http/associations/one_to_many.rb +20 -0
  7. data/lib/rom/http/associations/one_to_one.rb +12 -0
  8. data/lib/rom/http/associations.rb +14 -0
  9. data/lib/rom/http/attribute.rb +10 -0
  10. data/lib/rom/http/commands/create.rb +2 -0
  11. data/lib/rom/http/commands/delete.rb +2 -0
  12. data/lib/rom/http/commands/update.rb +2 -0
  13. data/lib/rom/http/commands.rb +6 -4
  14. data/lib/rom/http/dataset.rb +212 -115
  15. data/lib/rom/http/error.rb +2 -0
  16. data/lib/rom/http/gateway.rb +47 -6
  17. data/lib/rom/http/handlers/json.rb +64 -0
  18. data/lib/rom/http/handlers.rb +16 -0
  19. data/lib/rom/http/mapper_compiler.rb +11 -0
  20. data/lib/rom/http/relation.rb +22 -66
  21. data/lib/rom/http/schema/dsl.rb +12 -0
  22. data/lib/rom/http/schema.rb +40 -0
  23. data/lib/rom/http/transformer.rb +2 -0
  24. data/lib/rom/http/types.rb +13 -0
  25. data/lib/rom/http/version.rb +3 -1
  26. data/lib/rom/http.rb +9 -6
  27. data/lib/rom-http.rb +3 -1
  28. metadata +41 -120
  29. data/.gitignore +0 -16
  30. data/.rspec +0 -3
  31. data/.rubocop.yml +0 -22
  32. data/.rubocop_todo.yml +0 -12
  33. data/.travis.yml +0 -20
  34. data/Gemfile +0 -24
  35. data/LICENSE.txt +0 -22
  36. data/Rakefile +0 -24
  37. data/examples/repository_with_combine.rb +0 -154
  38. data/lib/rom/http/dataset/class_interface.rb +0 -33
  39. data/rakelib/rubocop.rake +0 -18
  40. data/rom-http.gemspec +0 -32
  41. data/spec/integration/abstract/commands/create_spec.rb +0 -119
  42. data/spec/integration/abstract/commands/delete_spec.rb +0 -52
  43. data/spec/integration/abstract/commands/update_spec.rb +0 -119
  44. data/spec/integration/abstract/relation_spec.rb +0 -78
  45. data/spec/shared/setup.rb +0 -18
  46. data/spec/shared/users_and_tasks.rb +0 -30
  47. data/spec/spec_helper.rb +0 -19
  48. data/spec/support/mutant.rb +0 -10
  49. data/spec/unit/rom/http/dataset_spec.rb +0 -824
  50. data/spec/unit/rom/http/gateway_spec.rb +0 -69
  51. data/spec/unit/rom/http/relation_spec.rb +0 -268
@@ -1,114 +1,199 @@
1
- require 'uri'
2
- require 'dry-configurable'
3
- require 'dry/core/deprecations'
4
- require 'rom/initializer'
5
- require 'rom/http/dataset/class_interface'
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ require "dry/configurable"
6
+ require "dry/core/deprecations"
7
+
8
+ require "rom/support/memoizable"
9
+ require "rom/constants"
10
+ require "rom/initializer"
11
+ require "rom/http/types"
12
+ require "rom/http/transformer"
6
13
 
7
14
  module ROM
8
15
  module HTTP
9
16
  # HTTP Dataset
10
17
  #
11
- # Represents a specific HTTP collection resource
18
+ # Represents a specific HTTP collection resource. This class can be
19
+ # subclassed in a specialized HTTP adapter to provide its own
20
+ # response/request handlers or any other configuration that should
21
+ # differ from the defaults.
12
22
  #
13
23
  # @api public
14
24
  class Dataset
15
- PATH_SEPARATOR = '/'.freeze
16
-
17
- extend ::ROM::Initializer
18
- extend ::Dry::Configurable
19
- extend ::ROM::HTTP::Dataset::ClassInterface
20
- include ::Enumerable
21
- include ::Dry::Equalizer(:config, :options)
22
-
23
- setting :default_request_handler
24
- setting :default_response_handler
25
- setting :param_encoder, ->(params) { URI.encode_www_form(params) }
26
-
27
- param :config
28
-
29
- option :request_method, type: Types::Symbol, default: proc { :get }, reader: true
30
- option :base_path, type: Types::String, default: proc { name }
31
- option :path, type: Types::String, default: proc { '' }, reader: false
32
- option :params, type: Types::Hash, default: proc { {} }, reader: true
33
- option :headers, type: Types::Hash, default: proc { {} }
34
-
35
- # Return the gateway's URI
36
- #
37
- # @return [String]
38
- #
39
- # @raise [Error] if the configuration does not contain a URI
25
+ PATH_SEPARATOR = "/"
26
+
27
+ extend Dry::Configurable
28
+ extend ROM::Initializer
29
+
30
+ include ROM::Memoizable
31
+ include Enumerable
32
+ include Dry::Equalizer(:options)
33
+
34
+ # @!method self.default_request_handler
35
+ # Return configured default request handler
36
+ #
37
+ # @example
38
+ # class MyDataset < ROM::HTTP::Dataset
39
+ # configure do |config|
40
+ # config.default_request_handler = MyRequestHandler
41
+ # end
42
+ # end
43
+ #
44
+ # MyDataset.default_request_handler # MyRequestHandler
45
+ # MyDataset.new(uri: "http://localhost").request_handler # MyRequestHandler
46
+ setting :default_request_handler, reader: true
47
+
48
+ # @!method self.default_response_handler
49
+ # Return configured default response handler
50
+ #
51
+ # @example
52
+ # class MyDataset < ROM::HTTP::Dataset
53
+ # configure do |config|
54
+ # config.default_response_handler = MyResponseHandler
55
+ # end
56
+ # end
57
+ #
58
+ # MyDataset.default_response_handler # MyResponseHandler
59
+ # MyDataset.new(uri: "http://localhost").response_handler # MyResponseHandler
60
+ setting :default_response_handler, reader: true
61
+
62
+ # @!method self.query_param_encoder
63
+ # Return configured query param encoder
64
+ #
65
+ # @example
66
+ # class MyDataset < ROM::HTTP::Dataset
67
+ # configure do |config|
68
+ # config.query_param_encoder = MyParamEncoder
69
+ # end
70
+ # end
71
+ #
72
+ # MyDataset.query_param_encoder # MyParamEncoder
73
+ # MyDataset.new(uri: "http://localhost").query_param_encoder # MyParamEncoder
74
+ setting :query_param_encoder, default: URI.method(:encode_www_form), reader: true
75
+
76
+ # @!attribute [r] request_handler
77
+ # @return [Object]
78
+ # @api public
79
+ option :request_handler, default: proc { self.class.default_request_handler }
80
+
81
+ # @!attribute [r] response_handler
82
+ # @return [Object]
83
+ # @api public
84
+ option :response_handler, default: proc { self.class.default_response_handler }
85
+
86
+ # @!attribute [r] request_method
87
+ # @return [Symbol]
88
+ # @api public
89
+ option :request_method, type: Types::Symbol, default: proc { :get }
90
+
91
+ # @!attribute [r] base_path
92
+ # @return [String]
93
+ # @api public
94
+ option :base_path, type: Types::Path, default: proc { EMPTY_STRING }
95
+
96
+ # @!attribute [r] path
97
+ # @return [String]
98
+ # @api public
99
+ option :path, type: Types::Path, default: proc { EMPTY_STRING }
100
+
101
+ # @!attribute [r] query_params
102
+ # @return [Hash]
103
+ # @api public
104
+ option :query_params, type: Types::Hash, default: proc { EMPTY_HASH }
105
+
106
+ # @!attribute [r] body_params
107
+ # @return [Hash]
108
+ # @api public
109
+ option :body_params, type: Types::Hash, default: proc { EMPTY_HASH }
110
+
111
+ # @!attribute [r] headers
112
+ # @return [Hash]
113
+ # @api public
114
+ option :headers, type: Types::Hash, default: proc { EMPTY_HASH }
115
+
116
+ # @!attribute [r] headers
117
+ # @return [Hash]
118
+ # @api public
119
+ option :query_param_encoder, default: proc { self.class.query_param_encoder }
120
+
121
+ # @!attribute [r] uri
122
+ # @return [String]
123
+ # @api public
124
+ option :uri, type: Types::String
125
+
126
+ # Return the dataset's URI
127
+ #
128
+ # @return [URI::HTTP]
40
129
  #
41
130
  # @api public
42
131
  def uri
43
- uri = config.fetch(:uri) { fail Error, '+uri+ configuration missing' }
44
- uri = URI(join_path(uri, path))
45
- if request_method == :get && params.any?
46
- uri.query = self.class.config.param_encoder.call(params)
132
+ uri = URI(join_path(super, path))
133
+
134
+ if query_params.any?
135
+ uri.query = query_param_encoder.call(query_params)
47
136
  end
48
137
 
49
138
  uri
50
139
  end
51
140
 
52
- # Return request headers
141
+ # Return true if request method is set to :get
53
142
  #
54
- # Merges default headers from the Gateway configuration and the
55
- # current Dataset
143
+ # @return [Boolean]
56
144
  #
57
- # @example
58
- # config = { Accepts: 'application/json' }
59
- # users = Dataset.new(config, headers: { 'Cache-Control': 'no-cache' }
60
- # users.headers
61
- # # => {:Accepts => "application/json", :'Cache-Control' => 'no-cache'}
145
+ # @api public
146
+ def get?
147
+ request_method.equal?(:get)
148
+ end
149
+
150
+ # Return true if request method is set to :post
62
151
  #
63
- # @return [Hash]
152
+ # @return [Boolean]
64
153
  #
65
154
  # @api public
66
- def headers
67
- config.fetch(:headers, {}).merge(options.fetch(:headers, {}))
155
+ def post?
156
+ request_method.equal?(:post)
68
157
  end
69
158
 
70
- # Return the dataset name
159
+ # Return true if request method is set to :put
71
160
  #
72
- # @return [String]
161
+ # @return [Boolean]
73
162
  #
74
163
  # @api public
75
- def name
76
- config[:name].to_s
164
+ def put?
165
+ request_method.equal?(:put)
77
166
  end
78
167
 
79
- # Return the base path
80
- #
81
- # @example
82
- # Dataset.new(config, base_path: '/users').base_path
83
- # # => 'users'
168
+ # Return true if request method is set to :delete
84
169
  #
85
- # @return [String] the dataset path, without a leading slash
170
+ # @return [Boolean]
86
171
  #
87
172
  # @api public
88
- def base_path
89
- strip_path(super)
173
+ def delete?
174
+ request_method.equal?(:delete)
90
175
  end
91
176
 
92
177
  # Return the dataset path
93
178
  #
94
179
  # @example
95
- # Dataset.new(config, path: '/users').path
180
+ # Dataset.new(path: '/users').path
96
181
  # # => 'users'
97
182
  #
98
183
  # @return [String] the dataset path, without a leading slash
99
184
  #
100
185
  # @api public
101
186
  def path
102
- join_path(base_path, strip_path(options[:path].to_s))
187
+ join_path(base_path, super)
103
188
  end
104
189
 
105
190
  # Return the dataset path
106
191
  #
107
192
  # @example
108
- # Dataset.new(config, path: '/users').path
193
+ # Dataset.new(path: '/users').path
109
194
  # # => '/users'
110
195
  #
111
- # @return [string] the dataset path, with leading slash
196
+ # @return [String] the dataset path, with leading slash
112
197
  #
113
198
  # @api public
114
199
  def absolute_path
@@ -123,7 +208,7 @@ module ROM
123
208
  # To non-destructively add a new header, use `#add_header`
124
209
  #
125
210
  # @example
126
- # users = Dataset.new(config, headers: { Accept: 'application/json' })
211
+ # users = Dataset.new(headers: { Accept: 'application/json' })
127
212
  # users.with_headers(:'X-Api-Key' => '1234').headers
128
213
  # # => { :'X-Api-Key' => '1234' }
129
214
  #
@@ -131,7 +216,7 @@ module ROM
131
216
  #
132
217
  # @api public
133
218
  def with_headers(headers)
134
- __new__(config, options.merge(headers: headers))
219
+ with_options(headers: headers)
135
220
  end
136
221
 
137
222
  # Return a new dataset with additional header
@@ -140,7 +225,7 @@ module ROM
140
225
  # @param value [String] the header value
141
226
  #
142
227
  # @example
143
- # users = Dataset.new(config, headers: { Accept: 'application/json' })
228
+ # users = Dataset.new(headers: { Accept: 'application/json' })
144
229
  # users.add_header(:'X-Api-Key', '1234').headers
145
230
  # # => { :Accept => 'application/json', :'X-Api-Key' => '1234' }
146
231
  #
@@ -159,7 +244,7 @@ module ROM
159
244
  #
160
245
  # @api public
161
246
  def with_options(opts)
162
- __new__(config, options.merge(opts))
247
+ __new__(**options.merge(opts))
163
248
  end
164
249
 
165
250
  # Return a new dataset with a different base path
@@ -204,7 +289,7 @@ module ROM
204
289
  #
205
290
  # @api public
206
291
  def append_path(append_path)
207
- with_options(path: join_path(path, append_path))
292
+ with_path(join_path(options[:path], append_path))
208
293
  end
209
294
 
210
295
  # Return a new dataset with a different request method
@@ -221,39 +306,70 @@ module ROM
221
306
  with_options(request_method: request_method)
222
307
  end
223
308
 
224
- # Return a new dataset with replaced request parameters
309
+ # Return a new dataset with replaced request query parameters
225
310
  #
226
- # @param [Hash] params the new request parameters
311
+ # @param [Hash] query_params the new request query parameters
227
312
  #
228
313
  # @example
229
- # users = Dataset.new(config, params: { uid: 33 })
230
- # users.with_params(login: 'jdoe').params
314
+ # users = Dataset.new(query_params: { uid: 33 })
315
+ # users.with_query_params(login: 'jdoe').query_params
231
316
  # # => { :login => 'jdoe' }
232
317
  #
233
318
  # @return [Dataset]
234
319
  #
235
320
  # @api public
236
- def with_params(params)
237
- with_options(params: params)
321
+ def with_query_params(query_params)
322
+ with_options(query_params: query_params)
238
323
  end
239
324
 
240
- # Return a new dataset with merged request parameters
325
+ # Return a new dataset with merged request query parameters
241
326
  #
242
- # @param [Hash] params the new request parameters to add
327
+ # @param [Hash] query_params the new request query parameters to add
243
328
  #
244
329
  # @example
245
- # users = Dataset.new(config, params: { uid: 33 })
246
- # users.add_params(login: 'jdoe').params
330
+ # users = Dataset.new(query_params: { uid: 33 })
331
+ # users.add_query_params(login: 'jdoe').query_params
247
332
  # # => { uid: 33, :login => 'jdoe' }
248
333
  #
249
334
  # @return [Dataset]
250
335
  #
251
336
  # @api public
252
- def add_params(new_params)
253
- # TODO: Should we merge arrays?
254
- with_options(
255
- params: ::ROM::HTTP::Transformer[:deep_merge][params, new_params]
256
- )
337
+ def add_query_params(new_query_params)
338
+ with_options(query_params: ::ROM::HTTP::Transformer[:deep_merge][query_params,
339
+ new_query_params])
340
+ end
341
+
342
+ # Return a new dataset with replaced request body parameters
343
+ #
344
+ # @param [Hash] body_params the new request body parameters
345
+ #
346
+ # @example
347
+ # users = Dataset.new(body_params: { uid: 33 })
348
+ # users.with_body_params(login: 'jdoe').body_params
349
+ # # => { :login => 'jdoe' }
350
+ #
351
+ # @return [Dataset]
352
+ #
353
+ # @api public
354
+ def with_body_params(body_params)
355
+ with_options(body_params: body_params)
356
+ end
357
+
358
+ # Return a new dataset with merged request body parameters
359
+ #
360
+ # @param [Hash] body_params the new request body parameters to add
361
+ #
362
+ # @example
363
+ # users = Dataset.new(body_params: { uid: 33 })
364
+ # users.add_body_params(login: 'jdoe').body_params
365
+ # # => { uid: 33, :login => 'jdoe' }
366
+ #
367
+ # @return [Dataset]
368
+ #
369
+ # @api public
370
+ def add_body_params(new_body_params)
371
+ with_options(body_params: ::ROM::HTTP::Transformer[:deep_merge][body_params,
372
+ new_body_params])
257
373
  end
258
374
 
259
375
  # Iterate over each response value
@@ -266,34 +382,35 @@ module ROM
266
382
  # @api public
267
383
  def each(&block)
268
384
  return to_enum unless block_given?
385
+
269
386
  response.each(&block)
270
387
  end
271
388
 
272
389
  # Perform an insert over HTTP Post
273
390
  #
274
- # @params [Hash] params The request parameters to send
391
+ # @param [Hash] attributes the attributes to insert
275
392
  #
276
393
  # @return [Array<Hash>]
277
394
  #
278
395
  # @api public
279
- def insert(params)
396
+ def insert(attributes)
280
397
  with_options(
281
398
  request_method: :post,
282
- params: params
399
+ body_params: attributes
283
400
  ).response
284
401
  end
285
402
 
286
403
  # Perform an update over HTTP Put
287
404
  #
288
- # @params [Hash] params The request parameters to send
405
+ # @param [Hash] attributes the attributes to update
289
406
  #
290
407
  # @return [Array<Hash>]
291
408
  #
292
409
  # @api public
293
- def update(params)
410
+ def update(attributes)
294
411
  with_options(
295
412
  request_method: :put,
296
- params: params
413
+ body_params: attributes
297
414
  ).response
298
415
  end
299
416
 
@@ -304,9 +421,7 @@ module ROM
304
421
  #
305
422
  # @api public
306
423
  def delete
307
- with_options(
308
- request_method: :delete
309
- ).response
424
+ with_options(request_method: :delete).response
310
425
  end
311
426
 
312
427
  # Execute the current dataset
@@ -318,37 +433,19 @@ module ROM
318
433
  response_handler.call(request_handler.call(self), self)
319
434
  end
320
435
 
321
- private
322
-
323
- def response_handler
324
- response_handler = config.fetch(
325
- :response_handler,
326
- self.class.config.default_response_handler
327
- )
328
- fail Error, '+default_response_handler+ configuration missing' if response_handler.nil?
329
- response_handler
330
- end
436
+ memoize :uri, :absolute_path
331
437
 
332
- def request_handler
333
- request_handler = config.fetch(
334
- :request_handler,
335
- self.class.config.default_request_handler
336
- )
337
- fail Error, '+default_response_handler+ configuration missing' if request_handler.nil?
338
- request_handler
339
- end
438
+ private
340
439
 
341
- def __new__(*args, &block)
342
- self.class.new(*args, &block)
440
+ # @api private
441
+ def __new__(*args, **kwargs, &block)
442
+ self.class.new(*args, **kwargs, &block)
343
443
  end
344
444
 
445
+ # @api private
345
446
  def join_path(*paths)
346
447
  paths.reject(&:empty?).join(PATH_SEPARATOR)
347
448
  end
348
-
349
- def strip_path(path)
350
- path.sub(%r{\A/}, '')
351
- end
352
449
  end
353
450
  end
354
451
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module HTTP
3
5
  Error = Class.new(StandardError)
@@ -1,5 +1,10 @@
1
- require 'concurrent'
2
- require 'rom/http/dataset'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ require "rom/http/dataset"
6
+ require "rom/http/handlers"
7
+ require "rom/http/mapper_compiler"
3
8
 
4
9
  module ROM
5
10
  module HTTP
@@ -19,7 +24,6 @@ module ROM
19
24
  adapter :http
20
25
 
21
26
  attr_reader :datasets, :config
22
- private :datasets, :config
23
27
 
24
28
  # HTTP gateway interface
25
29
  #
@@ -54,8 +58,7 @@ module ROM
54
58
  #
55
59
  # @api public
56
60
  def dataset(name)
57
- dataset_klass = namespace.const_defined?(:Dataset) ? namespace.const_get(:Dataset) : Dataset
58
- datasets[name] = dataset_klass.new(config.merge(name: name))
61
+ datasets[name] = dataset_class.new(**dataset_options(name))
59
62
  end
60
63
 
61
64
  # Check if dataset exists
@@ -69,8 +72,46 @@ module ROM
69
72
 
70
73
  private
71
74
 
75
+ # Return Dataset class
76
+ #
77
+ # @return [Class]
78
+ #
79
+ # @api private
80
+ def dataset_class
81
+ namespace.const_defined?(:Dataset) ? namespace.const_get(:Dataset) : Dataset
82
+ end
83
+
84
+ # Return Dataset options
85
+ #
86
+ # @return [Class]
87
+ #
88
+ # @api private
89
+ def dataset_options(name)
90
+ config.merge(uri: uri, base_path: name, **default_handlers)
91
+ end
92
+
93
+ # Return default handlers registered in Handlers registry
94
+ #
95
+ # @return [Hash]
96
+ #
97
+ # @api private
98
+ def default_handlers
99
+ if handlers_key = config[:handlers]
100
+ Handlers[handlers_key]
101
+ .map { |key, value| [:"#{key}_handler", value] }.to_h
102
+ else
103
+ EMPTY_HASH
104
+ end
105
+ end
106
+
107
+ # @api private
108
+ def uri
109
+ config.fetch(:uri) { raise Error, "+uri+ configuration missing" }
110
+ end
111
+
112
+ # @api private
72
113
  def namespace
73
- self.class.to_s[/(.*)(?=::)/].split('::').inject(::Object) do |constant, const_name|
114
+ self.class.to_s[/(.*)(?=::)/].split("::").inject(::Object) do |constant, const_name|
74
115
  constant.const_get(const_name)
75
116
  end
76
117
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "net/http"
5
+ require "json"
6
+
7
+ require "rom/support/inflector"
8
+
9
+ module ROM
10
+ module HTTP
11
+ # Default request/response handlers
12
+ #
13
+ # @api public
14
+ class Handlers
15
+ # Default handler for JSON requests
16
+ #
17
+ # @api public
18
+ class JSONRequest
19
+ # Handle JSON request for the provided dataset
20
+ #
21
+ # @param [Dataset] dataset
22
+ #
23
+ # @return [Array<Hash>]
24
+ #
25
+ # @api public
26
+ def self.call(dataset)
27
+ uri = dataset.uri
28
+
29
+ http = Net::HTTP.new(uri.host, uri.port)
30
+ http.use_ssl = true if uri.scheme.eql?("https")
31
+
32
+ request_class = Net::HTTP.const_get(ROM::Inflector.classify(dataset.request_method))
33
+
34
+ request = request_class.new(uri.request_uri)
35
+
36
+ dataset.headers.each_with_object(request) do |(header, value), request|
37
+ request[header.to_s] = value
38
+ end
39
+
40
+ request.body = JSON.dump(dataset.body_params) if dataset.body_params.any?
41
+
42
+ http.request(request)
43
+ end
44
+ end
45
+
46
+ # Default handler for JSON responses
47
+ #
48
+ # @api public
49
+ class JSONResponse
50
+ # Handle JSON responses
51
+ #
52
+ # @param [Net::HTTP::Response] response
53
+ # @param [Dataset] dataset
54
+ #
55
+ # @return [Array<Hash>]
56
+ #
57
+ # @api public
58
+ def self.call(response, _dataset)
59
+ Array([JSON.parse(response.body)]).flatten(1)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/http/handlers/json"
4
+
5
+ module ROM
6
+ module HTTP
7
+ # Request/response handler registry
8
+ #
9
+ # @api public
10
+ class Handlers
11
+ extend Dry::Container::Mixin
12
+
13
+ register(:json, request: JSONRequest, response: JSONResponse)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/mapper_compiler"
4
+
5
+ module ROM
6
+ module HTTP
7
+ class MapperCompiler < ROM::MapperCompiler
8
+ mapper_options(reject_keys: true)
9
+ end
10
+ end
11
+ end