acfs 1.4.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +22 -39
  4. data/acfs.gemspec +8 -14
  5. data/lib/acfs/adapter/base.rb +2 -0
  6. data/lib/acfs/adapter/typhoeus.rb +16 -11
  7. data/lib/acfs/collections/paginatable.rb +1 -1
  8. data/lib/acfs/configuration.rb +13 -3
  9. data/lib/acfs/errors.rb +41 -21
  10. data/lib/acfs/global.rb +2 -2
  11. data/lib/acfs/location.rb +26 -32
  12. data/lib/acfs/middleware/base.rb +2 -2
  13. data/lib/acfs/middleware/json.rb +4 -2
  14. data/lib/acfs/middleware/logger.rb +4 -6
  15. data/lib/acfs/middleware/serializer.rb +1 -1
  16. data/lib/acfs/operation.rb +21 -8
  17. data/lib/acfs/request/callbacks.rb +4 -4
  18. data/lib/acfs/request.rb +4 -11
  19. data/lib/acfs/resource/attributes/date_time.rb +1 -1
  20. data/lib/acfs/resource/attributes/uuid.rb +1 -1
  21. data/lib/acfs/resource/attributes.rb +16 -15
  22. data/lib/acfs/resource/dirty.rb +2 -2
  23. data/lib/acfs/resource/initialization.rb +5 -5
  24. data/lib/acfs/resource/locatable.rb +11 -8
  25. data/lib/acfs/resource/operational.rb +6 -3
  26. data/lib/acfs/resource/persistence.rb +13 -15
  27. data/lib/acfs/resource/query_methods.rb +10 -10
  28. data/lib/acfs/resource/service.rb +2 -2
  29. data/lib/acfs/resource/validation.rb +17 -7
  30. data/lib/acfs/response.rb +5 -5
  31. data/lib/acfs/runner.rb +15 -15
  32. data/lib/acfs/service.rb +16 -19
  33. data/lib/acfs/singleton_resource.rb +2 -2
  34. data/lib/acfs/stub.rb +41 -31
  35. data/lib/acfs/version.rb +2 -2
  36. data/spec/acfs/adapter/typhoeus_spec.rb +2 -2
  37. data/spec/acfs/collection_spec.rb +66 -41
  38. data/spec/acfs/configuration_spec.rb +22 -12
  39. data/spec/acfs/global_spec.rb +11 -9
  40. data/spec/acfs/location_spec.rb +2 -2
  41. data/spec/acfs/middleware/json_spec.rb +22 -8
  42. data/spec/acfs/middleware/{msgpack_spec.rb → message_pack_spec.rb} +6 -6
  43. data/spec/acfs/operation_spec.rb +3 -2
  44. data/spec/acfs/request/callbacks_spec.rb +19 -10
  45. data/spec/acfs/request_spec.rb +16 -20
  46. data/spec/acfs/resource/attributes/boolean_spec.rb +32 -32
  47. data/spec/acfs/resource/attributes/date_time_spec.rb +16 -8
  48. data/spec/acfs/resource/attributes/dict_spec.rb +15 -9
  49. data/spec/acfs/resource/attributes/float_spec.rb +20 -10
  50. data/spec/acfs/resource/attributes/integer_spec.rb +10 -5
  51. data/spec/acfs/resource/attributes/list_spec.rb +13 -8
  52. data/spec/acfs/resource/attributes/uuid_spec.rb +12 -6
  53. data/spec/acfs/resource/attributes_spec.rb +37 -38
  54. data/spec/acfs/resource/dirty_spec.rb +6 -3
  55. data/spec/acfs/resource/initialization_spec.rb +4 -5
  56. data/spec/acfs/resource/loadable_spec.rb +3 -1
  57. data/spec/acfs/resource/locatable_spec.rb +24 -18
  58. data/spec/acfs/resource/{persistance_spec.rb → persistence_spec.rb} +122 -90
  59. data/spec/acfs/resource/query_methods_spec.rb +143 -110
  60. data/spec/acfs/resource/validation_spec.rb +34 -27
  61. data/spec/acfs/response/formats_spec.rb +8 -8
  62. data/spec/acfs/response/status_spec.rb +16 -9
  63. data/spec/acfs/runner_spec.rb +10 -8
  64. data/spec/acfs/service/middleware_spec.rb +3 -3
  65. data/spec/acfs/service_spec.rb +6 -5
  66. data/spec/acfs/singleton_resource_spec.rb +2 -1
  67. data/spec/acfs/stub_spec.rb +57 -53
  68. data/spec/acfs_spec.rb +111 -93
  69. data/spec/spec_helper.rb +1 -2
  70. data/spec/support/response.rb +2 -2
  71. data/spec/support/service.rb +1 -1
  72. data/spec/support/shared/find_callbacks.rb +14 -10
  73. metadata +30 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f998ff3e28801676c732fa44df93e18f651e96eb986c857c61b7f25b312a2c1
4
- data.tar.gz: a122064cb630a664489629ef658d5ecde917ced4cdc43a600b49d0e18e00bea7
3
+ metadata.gz: 3cbc57dd595fa4242fa029498d90ca5a497216d2003674296209b679b84a6df2
4
+ data.tar.gz: 31a009d88f2a028e2fc11513b68dd011f5cfa0b7f11a83238f167e807c365b35
5
5
  SHA512:
6
- metadata.gz: 7801c96d794f08ad03b9ad204f8e7c8b611300922b12714ce7864a33c5a6268bc47c42e66b86c410358f17a0439d382c358ba15283eee07ff092d19a1f86a8be
7
- data.tar.gz: 78b2c804251fb6aaee270d9c894ff36b7b8dc7bb777148de55963255847fc28adcdbff9c7132352c5448bef7b9980fd72e6a14ffceda38540d661aceaf1e5e9d
6
+ metadata.gz: ba37a7c96e65b8774879835d93ccf43a5c91bcd77ce9cf604937544f0897ebf307ffbbf914d5e7304eb90e6f62139e37eb52b2e6406db4e86d454ef45bb4423b
7
+ data.tar.gz: 976193afda7d9e6d5bbdc3eef3ec7235a90abcaf23e79f055735cb663400c02448a683e734b9f3212b2a27c101fabad4055f0a0c5dff23a517885a7bda4cea43
data/CHANGELOG.md CHANGED
@@ -6,6 +6,7 @@
6
6
  ---
7
7
 
8
8
  ### New
9
+ * Support for Ruby 3.1 and Rails 7.0
9
10
 
10
11
  ### Changes
11
12
 
@@ -14,6 +15,31 @@
14
15
  ### Breaks
15
16
 
16
17
 
18
+ ## 1.6.0 - (2021-01-07)
19
+ ---
20
+
21
+ ### New
22
+ * Support Ruby 3.0
23
+ * Use keyword arguments in parameters and when calling methods
24
+
25
+
26
+ ## 1.5.1 - (2020-12-30)
27
+ ---
28
+
29
+ ### Changes
30
+ * Revert back to using `::MultiJson`
31
+
32
+
33
+ ## 1.5.0 - (2020-06-19)
34
+ ---
35
+
36
+ ### New
37
+ * Error classes for more HTTP error responses: `400`, `401`, `403`, `500`, `502`, `503`, `504`.
38
+
39
+ ### Changes
40
+ * Replace deprecated MultiJson with core JSON module
41
+
42
+
17
43
  ## 1.4.0 - (2020-06-12)
18
44
  ---
19
45
 
data/README.md CHANGED
@@ -8,13 +8,16 @@
8
8
 
9
9
  Acfs is a library to develop API client libraries for single services within a larger service oriented application.
10
10
 
11
- Acfs covers model and service abstraction, convenient query and filter methods, full middleware stack for pre-processing requests and responses on a per service level and automatic request queuing and parallel processing. See Usage for more.
11
+ Acfs covers model and service abstraction, convenient query and filter methods, full middleware stack for pre-processing requests and responses, as well as automatic request queuing and parallel processing.
12
+
12
13
 
13
14
  ## Installation
14
15
 
15
16
  Add this line to your application's Gemfile:
16
17
 
17
- gem 'acfs', '~> 1.3'
18
+ ```ruby
19
+ gem 'acfs', '~> 1.7'
20
+ ```
18
21
 
19
22
  And then execute:
20
23
 
@@ -24,6 +27,7 @@ Or install it yourself as:
24
27
 
25
28
  > gem install acfs
26
29
 
30
+
27
31
  ## Usage
28
32
 
29
33
  First you need to define your service(s):
@@ -119,7 +123,7 @@ Acfs.run # This call will fire all request as parallel as possible.
119
123
  @friends[0].name # => "Miraculix"
120
124
  ```
121
125
 
122
- Use `.find_by` to get first element only. `.find_by` will call the `index`-Action and return the first resource. Optionally passed params will be sent as `GET` parameters and can be used for filtering in the service's controller.
126
+ Use `.find_by` to get first element only. `.find_by` will call the `index`-Action and return the first resource. Optionally passed parameters will be sent as `GET` parameters and can be used for filtering in the service's controller.
123
127
  ```ruby
124
128
  @user = User.find_by age: 24
125
129
 
@@ -145,6 +149,7 @@ Acfs has basic update support using `PUT` requests:
145
149
  @user.persisted? # => true
146
150
  ```
147
151
 
152
+
148
153
  ## Singleton resources
149
154
 
150
155
  Singletons can be used in Acfs by creating a new resource which inherits from `SingletonResource`:
@@ -176,18 +181,17 @@ my_single.save # sends PUT request to /single
176
181
  my_single.delete # sends DELETE request to /single
177
182
  ```
178
183
 
179
- You also can pass parameters to the find call, these will sent as GET params to the index action:
184
+ You also can pass parameters to the find call. They will be sent as query parameters to the index action:
180
185
 
181
186
  ```ruby
182
187
  my_single = Single.find name: 'Max'
183
188
  Acfs.run # sends GET request with param to /single?name=Max
184
189
  ```
185
190
 
191
+
186
192
  ## Resource Inheritance
187
193
 
188
- Acfs provides a resource inheritance similar to ActiveRecord Single Table Inheritance. If a
189
- `type` attribute exists and is a valid subclass of your resource they will be converted
190
- to you subclassed resources:
194
+ Acfs provides a resource inheritance similar to ActiveRecord Single Table Inheritance. If a `type` attribute exists and is a valid subclass of your resource they will be converted to you subclassed resources:
191
195
 
192
196
  ```ruby
193
197
  class Computer < Acfs::Resource
@@ -198,8 +202,7 @@ class Pc < Computer end
198
202
  class Mac < Computer end
199
203
  ```
200
204
 
201
- With the following response on `GET /computers` the collection will contain the appropriate
202
- subclass resources:
205
+ With the following response on `GET /computers` the collection will contain the appropriate subclass resources:
203
206
 
204
207
  ```json
205
208
  [
@@ -219,6 +222,7 @@ Acfs.run
219
222
  @computer[2].class # => Pc
220
223
  ```
221
224
 
225
+
222
226
  ## Stubbing
223
227
 
224
228
  You can stub resources in applications using an Acfs service client:
@@ -243,9 +247,9 @@ it 'should find user number one' do
243
247
  user = MyUser.find 1
244
248
  Acfs.run
245
249
 
246
- expect(user.id).to be == 1
247
- expect(user.name).to be == 'John Smith'
248
- expect(user.age).to be == 32
250
+ expect(user.id).to eq 1
251
+ expect(user.name).to eq 'John Smith'
252
+ expect(user.age).to eq 32
249
253
 
250
254
  expect(@stub).to be_called
251
255
  expect(@stub).to_not be_called 5.times
@@ -260,8 +264,8 @@ end
260
264
  it 'should allow stub resource creation' do
261
265
  session = Session.create! ident: 'john@exmaple.org', password: 's3cr3t'
262
266
 
263
- expect(session.id).to be == 'longhash'
264
- expect(session.user).to be == 1
267
+ expect(session.id).to eq 'longhash'
268
+ expect(session.user).to eq 1
265
269
  end
266
270
  ```
267
271
 
@@ -279,11 +283,10 @@ it 'should find user number one' do
279
283
  end
280
284
  ```
281
285
 
282
- ## Instrumentation
283
286
 
284
- Acfs supports [instrumentation via active support][1].
287
+ ## Instrumentation
285
288
 
286
- Acfs expose to following events
289
+ Acfs supports [instrumentation via active support][1] and exposes the following events:
287
290
 
288
291
  * `acfs.operation.complete(operation, response)`: Acfs operation completed
289
292
  * `acfs.runner.sync_run(operation)`: Run operation right now skipping queue.
@@ -291,26 +294,11 @@ Acfs expose to following events
291
294
  * `acfs.before_run`: directly before `acfs.run`
292
295
  * `acfs.run`: Run all queued operations.
293
296
 
294
- Read [official guide][2] to see to to subscribe.
297
+ Read the [official guide][2] on how to subscribe to these events.
295
298
 
296
299
  [1]: http://guides.rubyonrails.org/active_support_instrumentation.html
297
300
  [2]: http://guides.rubyonrails.org/active_support_instrumentation.html#subscribing-to-an-event
298
301
 
299
- ## Roadmap
300
-
301
- * Update
302
- * Better new? detection eg. storing ETag from request resources.
303
- * Use PATCH for with only changed attributes and `If-Unmodifed-Since`
304
- and `If-Match` header fields if resource was surly loaded from service
305
- and not created with an id (e.g `User.new id: 5, name: "john"`).
306
- * Conflict detection (ETag / If-Unmodified-Since)
307
- * High level features
308
- * Support for custom mime types on client and server side. (`application/vnd.myservice.user.v2+msgpack`)
309
- * Server side components
310
- * Reusing model definitions for generating responses?
311
- * Rails responders providing REST operations with integrated ETag,
312
- Modified Headers, conflict detection, ...
313
- * Documentation
314
302
 
315
303
  ## Contributing
316
304
 
@@ -322,14 +310,9 @@ Read [official guide][2] to see to to subscribe.
322
310
  7. Push to the branch (`git push origin my-new-feature`)
323
311
  8. Create new Pull Request
324
312
 
325
- ## Contributors
326
-
327
- * [Nicolas Fricke](https://github.com/nicolas-fricke)
328
- * [Tino Junge](https://github.com/tino-junge)
329
- * [Malte Swart](https://github.com/mswart)
330
313
 
331
314
  ## License
332
315
 
333
316
  MIT License
334
317
 
335
- Copyright (c) 2013 Jan Graichen. MIT license, see LICENSE for more details.
318
+ Copyright (c) 2013-2022 Jan Graichen. MIT license, see LICENSE for more details.
data/acfs.gemspec CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
16
16
  An abstract API base client for service oriented application.
17
17
  SUMMARY
18
18
 
19
+ spec.metadata['rubygems_mfa_required'] = 'true'
20
+
19
21
  spec.files = Dir['**/*'].grep(%r{
20
22
  ^((bin|lib|test|spec|features)/|
21
23
  .*\.gemspec|.*LICENSE.*|.*README.*|.*CHANGELOG.*)
@@ -25,22 +27,14 @@ Gem::Specification.new do |spec|
25
27
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
28
  spec.require_paths = %w[lib]
27
29
 
28
- spec.add_runtime_dependency 'actionpack', '>= 4.2'
29
- spec.add_runtime_dependency 'activemodel', '>= 4.2'
30
- spec.add_runtime_dependency 'activesupport', '>= 4.2'
31
- spec.add_runtime_dependency 'multi_json'
32
-
33
- # Bundle update w/o version resolves to 0.3.3 ...
34
- spec.add_runtime_dependency 'typhoeus', '~> 1.0'
30
+ spec.required_ruby_version = '>= 2.5.0'
35
31
 
32
+ spec.add_runtime_dependency 'actionpack', '>= 5.2'
33
+ spec.add_runtime_dependency 'activemodel', '>= 5.2'
34
+ spec.add_runtime_dependency 'activesupport', '>= 5.2'
35
+ spec.add_runtime_dependency 'multi_json', '~> 1.0'
36
36
  spec.add_runtime_dependency 'rack'
37
+ spec.add_runtime_dependency 'typhoeus', '~> 1.0'
37
38
 
38
39
  spec.add_development_dependency 'bundler'
39
-
40
- if ENV['TRAVIS_BUILD_NUMBER'] && !ENV['TRAVIS_TAG']
41
- # Append travis build number for auto-releases
42
- # rubocop:disable Gemspec/DuplicatedAssignment
43
- spec.version = "#{spec.version}.1.b#{ENV['TRAVIS_BUILD_NUMBER']}"
44
- # rubocop:enable Gemspec/DuplicatedAssignment
45
- end
46
40
  end
@@ -5,6 +5,8 @@ module Acfs::Adapter
5
5
  # and processing.
6
6
  #
7
7
  class Base
8
+ def initialize(*); end
9
+
8
10
  # Start processing queued requests.
9
11
  #
10
12
  def start; end
@@ -7,14 +7,19 @@ module Acfs
7
7
  DEFAULT_OPTIONS = {
8
8
  tcp_keepalive: true,
9
9
  tcp_keepidle: 5,
10
- tcp_keepintvl: 5
10
+ tcp_keepintvl: 5,
11
11
  }.freeze
12
12
 
13
13
  # Adapter for Typhoeus.
14
14
  #
15
15
  class Typhoeus < Base
16
- def initialize(opts: {}, **kwargs)
17
- @opts = DEFAULT_OPTIONS.merge(opts)
16
+ def initialize(**kwargs)
17
+ super
18
+
19
+ extra_opts = kwargs.delete(:opts)
20
+
21
+ @opts = DEFAULT_OPTIONS
22
+ @opts = @opts.merge(extra_opts) if extra_opts
18
23
  @kwargs = kwargs
19
24
  end
20
25
 
@@ -47,22 +52,22 @@ module Acfs
47
52
  params: req.params,
48
53
  headers: req.headers.merge(
49
54
  'Expect' => '',
50
- 'Transfer-Encoding' => ''
55
+ 'Transfer-Encoding' => '',
51
56
  ),
52
- body: req.body
57
+ body: req.body,
53
58
  }
54
59
 
55
- request = ::Typhoeus::Request.new(req.url, **@opts.merge(opts))
60
+ request = ::Typhoeus::Request.new(req.url, **@opts, **opts)
56
61
 
57
62
  request.on_complete do |response|
58
- if response.timed_out?
59
- raise ::Acfs::TimeoutError.new(req)
60
- elsif response.code.zero?
63
+ raise ::Acfs::TimeoutError.new(req) if response.timed_out?
64
+
65
+ if response.code.zero?
61
66
  # Failed to get HTTP response
62
67
  raise ::Acfs::RequestError.new(req, response.return_message)
63
- else
64
- req.complete! convert_response(req, response)
65
68
  end
69
+
70
+ req.complete! convert_response(req, response)
66
71
  end
67
72
 
68
73
  request
@@ -5,7 +5,7 @@ module Acfs::Collections
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- def self.operation(_action, opts = {}, &_block)
8
+ def self.operation(_action, **opts, &_block)
9
9
  opts[:url]
10
10
  end
11
11
 
@@ -28,7 +28,7 @@ module Acfs
28
28
  #
29
29
  def configure(&block)
30
30
  if block.arity.positive?
31
- block.call self
31
+ yield self
32
32
  else
33
33
  instance_eval(&block)
34
34
  end
@@ -70,8 +70,8 @@ module Acfs
70
70
  # @return [undefined]
71
71
  #
72
72
  def load(filename)
73
- config = YAML.safe_load(File.read(filename), [], [], true)
74
- env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
73
+ config = load_yaml_file(filename)
74
+ env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
75
75
 
76
76
  config = config[env] if config.key? env
77
77
  config.each do |key, value|
@@ -116,5 +116,15 @@ module Acfs
116
116
  @current = configuration if configuration.is_a? Configuration
117
117
  end
118
118
  end
119
+
120
+ private
121
+
122
+ def load_yaml_file(path)
123
+ if YAML.respond_to?(:safe_load_file)
124
+ YAML.safe_load_file(path, aliases: true)
125
+ else
126
+ YAML.safe_load(File.read(path), [], [], true)
127
+ end
128
+ end
119
129
  end
120
130
  end
data/lib/acfs/errors.rb CHANGED
@@ -5,7 +5,7 @@ module Acfs
5
5
  #
6
6
  class Error < StandardError
7
7
  def initialize(opts = {}, message = nil)
8
- opts.merge! message: message if message
8
+ opts[:message] = message if message
9
9
  super opts[:message]
10
10
  end
11
11
  end
@@ -26,7 +26,7 @@ module Acfs
26
26
 
27
27
  class TimeoutError < RequestError
28
28
  def initialize(request)
29
- super(request, "Timeout reached")
29
+ super(request, 'Timeout reached')
30
30
  end
31
31
  end
32
32
 
@@ -38,13 +38,13 @@ module Acfs
38
38
  def initialize(opts = {})
39
39
  @response = opts[:response]
40
40
 
41
- if response
42
- message = (opts[:message] ? opts[:message] + ':' : 'Received') +
43
- " #{response.code} for #{response.request.method.upcase}" \
44
- " #{response.request.url} #{response.request.format}"
45
- else
46
- message = opts[:message] || 'Received erroneous response'
47
- end
41
+ message = if response
42
+ (opts[:message] ? "#{opts[:message]}:" : 'Received') +
43
+ " #{response.code} for #{response.request.method.upcase}" \
44
+ " #{response.request.url} #{response.request.format}"
45
+ else
46
+ opts[:message] || 'Received erroneous response'
47
+ end
48
48
 
49
49
  super opts, message
50
50
  end
@@ -67,11 +67,19 @@ module Acfs
67
67
  end
68
68
  end
69
69
 
70
- # Resource not found error raised on a 404 response
71
- #
72
- class ResourceNotFound < ErroneousResponse
73
- end
70
+ # 400
71
+ class BadRequest < ErroneousResponse; end
72
+
73
+ # 401
74
+ class Unauthorized < ErroneousResponse; end
75
+
76
+ # 403
77
+ class Forbidden < ErroneousResponse; end
74
78
 
79
+ # 404
80
+ class ResourceNotFound < ErroneousResponse; end
81
+
82
+ # 422
75
83
  class InvalidResource < ErroneousResponse
76
84
  attr_reader :errors, :resource
77
85
 
@@ -79,18 +87,31 @@ module Acfs
79
87
  @errors = opts.delete :errors
80
88
  @resource = opts.delete :resource
81
89
 
82
- if @errors.is_a?(Hash)
83
- opts[:message] ||= @errors.each_pair.map do |k, v|
84
- @errors.is_a?(Array) ? "#{k}: #{v.join(', ')}" : "#{k}: #{v}"
85
- end.join ', '
86
- elsif @errors.is_a?(Array)
87
- opts[:message] ||= @errors.join ', '
90
+ case @errors
91
+ when Hash
92
+ opts[:message] ||= @errors.each_pair.map do |k, v|
93
+ @errors.is_a?(Array) ? "#{k}: #{v.join(', ')}" : "#{k}: #{v}"
94
+ end.join ', '
95
+ when Array
96
+ opts[:message] ||= @errors.join ', '
88
97
  end
89
98
 
90
99
  super
91
100
  end
92
101
  end
93
102
 
103
+ # 500
104
+ class ServerError < ErroneousResponse; end
105
+
106
+ # 502
107
+ class BadGateway < ErroneousResponse; end
108
+
109
+ # 503
110
+ class ServiceUnavailable < ErroneousResponse; end
111
+
112
+ # 504
113
+ class GatewayTimeout < ErroneousResponse; end
114
+
94
115
  # A ResourceNotLoaded error will be thrown when calling some
95
116
  # modifing methods on not loaded resources as it is usally
96
117
  # unwanted to call e.g. `update_attributes` on a not loaded
@@ -111,8 +132,7 @@ module Acfs
111
132
  # parent resource. Check if the type is set to the correct
112
133
  # Acfs::Resource Name
113
134
  class ResourceTypeError < Error
114
- attr_reader :base_class
115
- attr_reader :type_name
135
+ attr_reader :base_class, :type_name
116
136
 
117
137
  def initialize(opts = {})
118
138
  @base_class = opts.delete :base_class
data/lib/acfs/global.rb CHANGED
@@ -71,12 +71,12 @@ module Acfs
71
71
  def add_callback(resource, &block)
72
72
  unless resource.respond_to?(:__callbacks__)
73
73
  raise ArgumentError.new 'Given resource is not an Acfs resource ' \
74
- "delegator but a: #{resource.class.name}"
74
+ "delegator but a: #{resource.class.name}"
75
75
  end
76
76
  return false if block.nil?
77
77
 
78
78
  if resource.nil? || resource.loaded?
79
- block.call resource
79
+ yield resource
80
80
  else
81
81
  resource.__callbacks__ << block
82
82
  end
data/lib/acfs/location.rb CHANGED
@@ -6,36 +6,31 @@ module Acfs
6
6
  # Describes a URL with placeholders.
7
7
  #
8
8
  class Location
9
- attr_reader :arguments, :raw, :struct, :args
9
+ attr_reader :arguments, :raw, :struct, :vars
10
10
 
11
11
  REGEXP = /^:([A-z][A-z0-9_]*)$/.freeze
12
12
 
13
- def initialize(uri, args = {})
13
+ def initialize(uri, vars = {})
14
14
  @raw = URI.parse uri
15
- @args = args
15
+ @vars = vars
16
16
  @struct = raw.path.split('/').reject(&:empty?).map {|s| s =~ REGEXP ? Regexp.last_match[1].to_sym : s }
17
17
  @arguments = struct.select {|s| s.is_a?(Symbol) }
18
18
  end
19
19
 
20
- def build(args = {})
21
- unless args.is_a?(Hash)
22
- raise ArgumentError.new "URI path arguments must be a hash, `#{args.inspect}' given."
23
- end
24
-
25
- self.class.new raw.to_s, args.merge(self.args)
20
+ def build(vars)
21
+ self.class.new raw.to_s, vars.stringify_keys.merge(self.vars)
26
22
  end
27
23
 
28
24
  def extract_from(*args)
29
- args = {}.tap do |collect|
30
- arguments.each {|key| collect[key] = extract_arg key, args }
31
- end
25
+ vars = {}
26
+ arguments.each {|key| vars[key.to_s] = extract_arg(key, args) }
32
27
 
33
- build args
28
+ build(vars)
34
29
  end
35
30
 
36
31
  def str
37
32
  uri = raw.dup
38
- uri.path = '/' + struct.map {|s| lookup_arg(s, args) }.join('/')
33
+ uri.path = "/#{struct.map {|s| lookup_variable(s) }.join('/')}"
39
34
  uri.to_s
40
35
  end
41
36
 
@@ -49,34 +44,33 @@ module Acfs
49
44
  def extract_arg(key, hashes)
50
45
  hashes.each_with_index do |hash, index|
51
46
  if hash.key?(key)
52
- return (index == 0 ? hash.delete(key) : hash.fetch(key))
47
+ return (index.zero? ? hash.delete(key) : hash.fetch(key))
53
48
  end
54
49
  end
55
50
 
56
51
  nil
57
52
  end
58
53
 
59
- def lookup_arg(arg, args)
60
- arg.is_a?(Symbol) ? lookup_replacement(arg, args) : arg
61
- end
54
+ def lookup_variable(name)
55
+ return name unless name.is_a?(Symbol)
56
+
57
+ value = vars.fetch(name.to_s) do
58
+ if @raise.nil? || @raise
59
+ raise ArgumentError.new <<~ERROR.strip
60
+ URI path argument `#{name}' missing on `#{self}'. Given: `#{vars}.inspect'
61
+ ERROR
62
+ end
62
63
 
63
- def lookup_replacement(sym, args)
64
- value = get_replacement(sym, args).to_s
65
- return ::URI.encode_www_form_component(value) unless value.empty?
64
+ ":#{name}"
65
+ end
66
66
 
67
- raise ArgumentError.new "Cannot replace path argument `#{sym}' with empty string."
68
- end
67
+ value = value.to_s.strip
69
68
 
70
- def get_replacement(sym, args)
71
- args.fetch(sym.to_s) do
72
- args.fetch(sym) do
73
- if args[:raise].nil? || args[:raise]
74
- raise ArgumentError.new "URI path argument `#{sym}' missing on `#{self}'. Given: `#{args}.inspect'"
75
- else
76
- ":#{sym}"
77
- end
78
- end
69
+ if value.empty?
70
+ raise ArgumentError.new "Cannot replace path argument `#{name}' with empty string."
79
71
  end
72
+
73
+ ::URI.encode_www_form_component(value)
80
74
  end
81
75
  end
82
76
  end
@@ -8,9 +8,9 @@ module Acfs
8
8
  class Base
9
9
  attr_reader :app, :options
10
10
 
11
- def initialize(app, options = {})
11
+ def initialize(app, **opts)
12
12
  @app = app
13
- @options = options
13
+ @options = opts
14
14
  end
15
15
 
16
16
  def call(request)
@@ -12,11 +12,13 @@ module Acfs
12
12
  end
13
13
 
14
14
  def encode(data)
15
- ::MultiJson.dump data
15
+ ::MultiJson.dump(data)
16
16
  end
17
17
 
18
18
  def decode(body)
19
- ::MultiJson.load body
19
+ ::MultiJson.load(body)
20
+ rescue ::MultiJson::ParseError => e
21
+ raise ::JSON::ParserError.new(e)
20
22
  end
21
23
  end
22
24
 
@@ -7,19 +7,17 @@ module Acfs
7
7
  # Log requests and responses.
8
8
  #
9
9
  class Logger < Base
10
- def initialize(app, options = {})
10
+ attr_reader :logger
11
+
12
+ def initialize(app, **opts)
11
13
  super
12
- @logger = options[:logger] if options[:logger]
14
+ @logger = options[:logger] || ::Logger.new($stdout)
13
15
  end
14
16
 
15
17
  def response(res, nxt)
16
18
  logger.info "[ACFS] #{res.request.method.to_s.upcase} #{res.request.url} -> #{res.status}"
17
19
  nxt.call res
18
20
  end
19
-
20
- def logger
21
- @logger ||= ::Logger.new STDOUT
22
- end
23
21
  end
24
22
  end
25
23
  end
@@ -29,7 +29,7 @@ module Acfs
29
29
  request.headers['Accept'] = accept.join(',')
30
30
 
31
31
  request.on_complete do |response, nxt|
32
- response.data = decode response.body if mime == response.content_type
32
+ response.data = decode(response.body) if mime == response.content_type
33
33
 
34
34
  nxt.call response
35
35
  end