acfs 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +3 -4
- data/acfs.gemspec +19 -10
- data/lib/acfs.rb +2 -0
- data/lib/acfs/adapter/base.rb +6 -8
- data/lib/acfs/adapter/typhoeus.rb +26 -7
- data/lib/acfs/collection.rb +2 -1
- data/lib/acfs/collections/paginatable.rb +2 -1
- data/lib/acfs/configuration.rb +14 -7
- data/lib/acfs/errors.rb +33 -12
- data/lib/acfs/global.rb +12 -2
- data/lib/acfs/location.rb +9 -5
- data/lib/acfs/middleware/base.rb +5 -1
- data/lib/acfs/middleware/json.rb +2 -0
- data/lib/acfs/middleware/logger.rb +2 -0
- data/lib/acfs/middleware/msgpack.rb +2 -0
- data/lib/acfs/middleware/print.rb +2 -0
- data/lib/acfs/middleware/serializer.rb +2 -0
- data/lib/acfs/operation.rb +5 -3
- data/lib/acfs/request.rb +3 -0
- data/lib/acfs/request/callbacks.rb +5 -1
- data/lib/acfs/resource.rb +2 -0
- data/lib/acfs/resource/attributes.rb +3 -2
- data/lib/acfs/resource/attributes/base.rb +2 -1
- data/lib/acfs/resource/attributes/boolean.rb +2 -0
- data/lib/acfs/resource/attributes/date_time.rb +2 -1
- data/lib/acfs/resource/attributes/dict.rb +2 -0
- data/lib/acfs/resource/attributes/float.rb +5 -3
- data/lib/acfs/resource/attributes/integer.rb +2 -0
- data/lib/acfs/resource/attributes/list.rb +2 -0
- data/lib/acfs/resource/attributes/string.rb +2 -0
- data/lib/acfs/resource/attributes/uuid.rb +4 -3
- data/lib/acfs/resource/dirty.rb +2 -0
- data/lib/acfs/resource/initialization.rb +2 -0
- data/lib/acfs/resource/loadable.rb +2 -0
- data/lib/acfs/resource/locatable.rb +10 -6
- data/lib/acfs/resource/operational.rb +2 -1
- data/lib/acfs/resource/persistence.rb +7 -4
- data/lib/acfs/resource/query_methods.rb +5 -3
- data/lib/acfs/resource/service.rb +3 -1
- data/lib/acfs/resource/validation.rb +3 -1
- data/lib/acfs/response.rb +2 -0
- data/lib/acfs/response/formats.rb +2 -0
- data/lib/acfs/response/status.rb +3 -1
- data/lib/acfs/rspec.rb +2 -0
- data/lib/acfs/runner.rb +6 -1
- data/lib/acfs/service.rb +8 -2
- data/lib/acfs/service/middleware.rb +2 -0
- data/lib/acfs/service/middleware/stack.rb +5 -3
- data/lib/acfs/singleton_resource.rb +4 -2
- data/lib/acfs/stub.rb +33 -11
- data/lib/acfs/util.rb +2 -0
- data/lib/acfs/version.rb +3 -1
- data/lib/acfs/yard.rb +1 -0
- data/spec/acfs/adapter/typhoeus_spec.rb +30 -3
- data/spec/acfs/collection_spec.rb +7 -5
- data/spec/acfs/configuration_spec.rb +2 -0
- data/spec/acfs/global_spec.rb +48 -1
- data/spec/acfs/location_spec.rb +25 -0
- data/spec/acfs/middleware/json_spec.rb +2 -0
- data/spec/acfs/middleware/msgpack_spec.rb +2 -0
- data/spec/acfs/operation_spec.rb +2 -0
- data/spec/acfs/request/callbacks_spec.rb +2 -0
- data/spec/acfs/request_spec.rb +3 -1
- data/spec/acfs/resource/attributes/boolean_spec.rb +2 -0
- data/spec/acfs/resource/attributes/date_time_spec.rb +2 -0
- data/spec/acfs/resource/attributes/dict_spec.rb +4 -2
- data/spec/acfs/resource/attributes/float_spec.rb +2 -0
- data/spec/acfs/resource/attributes/integer_spec.rb +2 -0
- data/spec/acfs/resource/attributes/list_spec.rb +5 -3
- data/spec/acfs/resource/attributes/uuid_spec.rb +2 -0
- data/spec/acfs/resource/attributes_spec.rb +6 -4
- data/spec/acfs/resource/dirty_spec.rb +2 -0
- data/spec/acfs/resource/initialization_spec.rb +8 -2
- data/spec/acfs/resource/loadable_spec.rb +2 -0
- data/spec/acfs/resource/locatable_spec.rb +2 -0
- data/spec/acfs/resource/persistance_spec.rb +10 -4
- data/spec/acfs/resource/query_methods_spec.rb +25 -18
- data/spec/acfs/resource/validation_spec.rb +2 -0
- data/spec/acfs/response/formats_spec.rb +3 -1
- data/spec/acfs/response/status_spec.rb +2 -0
- data/spec/acfs/runner_spec.rb +6 -8
- data/spec/acfs/service/middleware_spec.rb +2 -0
- data/spec/acfs/service_spec.rb +3 -1
- data/spec/acfs/singleton_resource_spec.rb +2 -0
- data/spec/acfs/stub_spec.rb +2 -0
- data/spec/acfs_spec.rb +2 -0
- data/spec/spec_helper.rb +11 -6
- data/spec/support/hash.rb +2 -0
- data/spec/support/response.rb +2 -0
- data/spec/support/service.rb +1 -0
- data/spec/support/shared/find_callbacks.rb +2 -0
- metadata +12 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f998ff3e28801676c732fa44df93e18f651e96eb986c857c61b7f25b312a2c1
|
4
|
+
data.tar.gz: a122064cb630a664489629ef658d5ecde917ced4cdc43a600b49d0e18e00bea7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7801c96d794f08ad03b9ad204f8e7c8b611300922b12714ce7864a33c5a6268bc47c42e66b86c410358f17a0439d382c358ba15283eee07ff092d19a1f86a8be
|
7
|
+
data.tar.gz: 78b2c804251fb6aaee270d9c894ff36b7b8dc7bb777148de55963255847fc28adcdbff9c7132352c5448bef7b9980fd72e6a14ffceda38540d661aceaf1e5e9d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,55 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
|
4
|
+
|
5
|
+
## Unreleased
|
6
|
+
---
|
7
|
+
|
8
|
+
### New
|
9
|
+
|
10
|
+
### Changes
|
11
|
+
|
12
|
+
### Fixes
|
13
|
+
|
14
|
+
### Breaks
|
15
|
+
|
16
|
+
|
17
|
+
## 1.4.0 - (2020-06-12)
|
18
|
+
---
|
19
|
+
|
20
|
+
### New
|
21
|
+
* Use strict TCP keepalive probing by default (5s/5s)
|
22
|
+
* Adapter accepts curl request opts
|
23
|
+
|
24
|
+
|
25
|
+
## 1.3.4 - (2020-03-22)
|
26
|
+
---
|
27
|
+
|
28
|
+
### Fixes
|
29
|
+
* Empty package build for Gem release 1.3.3
|
30
|
+
|
31
|
+
|
32
|
+
## 1.3.3 - (2020-03-22)
|
33
|
+
---
|
34
|
+
|
35
|
+
### Changes
|
36
|
+
* Improved handling of low-level connection errors and timeouts
|
37
|
+
|
38
|
+
|
39
|
+
## 1.3.2 - (2019-09-24)
|
40
|
+
|
41
|
+
|
42
|
+
### Fixes
|
43
|
+
* Fix Acfs.on callbacks for empty find_by results (#42)
|
44
|
+
|
45
|
+
|
46
|
+
---
|
47
|
+
|
48
|
+
## 1.3.1 - (2019-07-02)
|
49
|
+
|
50
|
+
### Fixes
|
51
|
+
* Improve URL argument encoding when building resource requests
|
52
|
+
|
3
53
|
## 1.3.0
|
4
54
|
|
5
55
|
* Change default error messages to a more compact representation to ease integration with error reporting services.
|
data/README.md
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# Acfs - *API client for services*
|
2
2
|
|
3
|
-
[![Gem Version](https://
|
4
|
-
[![Build Status](
|
3
|
+
[![Gem Version](https://img.shields.io/gem/v/acfs?logo=ruby)](https://rubygems.org/gems/acfs)
|
4
|
+
[![Build Status](https://img.shields.io/travis/jgraichen/acfs/master?logo=travis)](https://travis-ci.org/jgraichen/acfs)
|
5
|
+
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/jgraichen/acfs/Test/master?logo=github)](https://github.com/jgraichen/acfs/actions?query=branch%3Amaster)
|
5
6
|
[![Coverage Status](http://img.shields.io/coveralls/jgraichen/acfs/master.svg)](https://coveralls.io/r/jgraichen/acfs)
|
6
|
-
[![Code Climate](http://img.shields.io/codeclimate/github/jgraichen/acfs.svg)](https://codeclimate.com/github/jgraichen/acfs)
|
7
|
-
[![Dependency Status](http://img.shields.io/gemnasium/jgraichen/acfs.svg)](https://gemnasium.com/jgraichen/acfs)
|
8
7
|
[![RubyDoc Documentation](http://img.shields.io/badge/rubydoc-here-blue.svg)](http://rubydoc.info/github/jgraichen/acfs/master/frames)
|
9
8
|
|
10
9
|
Acfs is a library to develop API client libraries for single services within a larger service oriented application.
|
data/acfs.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'acfs/version'
|
5
6
|
|
@@ -7,20 +8,26 @@ Gem::Specification.new do |spec|
|
|
7
8
|
spec.name = 'acfs'
|
8
9
|
spec.version = Acfs::VERSION
|
9
10
|
spec.authors = ['Jan Graichen']
|
10
|
-
spec.email = %w
|
11
|
-
spec.description = 'API Client For Services'
|
12
|
-
spec.summary = 'An abstract API base client for service oriented application.'
|
11
|
+
spec.email = %w[jgraichen@altimos.de]
|
13
12
|
spec.homepage = 'https://github.com/jgraichen/acfs'
|
14
13
|
spec.license = 'MIT'
|
14
|
+
spec.description = 'API Client For Services'
|
15
|
+
spec.summary = <<~SUMMARY.strip
|
16
|
+
An abstract API base client for service oriented application.
|
17
|
+
SUMMARY
|
18
|
+
|
19
|
+
spec.files = Dir['**/*'].grep(%r{
|
20
|
+
^((bin|lib|test|spec|features)/|
|
21
|
+
.*\.gemspec|.*LICENSE.*|.*README.*|.*CHANGELOG.*)
|
22
|
+
}xi)
|
15
23
|
|
16
|
-
spec.files = Dir['**/*'].grep(%r{^((bin|lib|test|spec|features)/|.*\.gemspec|.*LICENSE.*|.*README.*|.*CHANGELOG.*)})
|
17
24
|
spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f) }
|
18
25
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = %w
|
26
|
+
spec.require_paths = %w[lib]
|
20
27
|
|
21
|
-
spec.add_runtime_dependency 'activesupport', '>= 4.2'
|
22
|
-
spec.add_runtime_dependency 'activemodel', '>= 4.2'
|
23
28
|
spec.add_runtime_dependency 'actionpack', '>= 4.2'
|
29
|
+
spec.add_runtime_dependency 'activemodel', '>= 4.2'
|
30
|
+
spec.add_runtime_dependency 'activesupport', '>= 4.2'
|
24
31
|
spec.add_runtime_dependency 'multi_json'
|
25
32
|
|
26
33
|
# Bundle update w/o version resolves to 0.3.3 ...
|
@@ -28,10 +35,12 @@ Gem::Specification.new do |spec|
|
|
28
35
|
|
29
36
|
spec.add_runtime_dependency 'rack'
|
30
37
|
|
31
|
-
spec.add_development_dependency 'bundler'
|
38
|
+
spec.add_development_dependency 'bundler'
|
32
39
|
|
33
40
|
if ENV['TRAVIS_BUILD_NUMBER'] && !ENV['TRAVIS_TAG']
|
34
41
|
# Append travis build number for auto-releases
|
42
|
+
# rubocop:disable Gemspec/DuplicatedAssignment
|
35
43
|
spec.version = "#{spec.version}.1.b#{ENV['TRAVIS_BUILD_NUMBER']}"
|
44
|
+
# rubocop:enable Gemspec/DuplicatedAssignment
|
36
45
|
end
|
37
46
|
end
|
data/lib/acfs.rb
CHANGED
data/lib/acfs/adapter/base.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Acfs::Adapter
|
2
4
|
# Base adapter handling operation queuing
|
3
5
|
# and processing.
|
@@ -5,22 +7,18 @@ module Acfs::Adapter
|
|
5
7
|
class Base
|
6
8
|
# Start processing queued requests.
|
7
9
|
#
|
8
|
-
def start
|
9
|
-
end
|
10
|
+
def start; end
|
10
11
|
|
11
12
|
# Abort running and queued requests.
|
12
13
|
#
|
13
|
-
def abort
|
14
|
-
end
|
14
|
+
def abort; end
|
15
15
|
|
16
16
|
# Run request right now skipping queue.
|
17
17
|
#
|
18
|
-
def run(_)
|
19
|
-
end
|
18
|
+
def run(_); end
|
20
19
|
|
21
20
|
# Enqueue request to be run later.
|
22
21
|
#
|
23
|
-
def queue(_)
|
24
|
-
end
|
22
|
+
def queue(_); end
|
25
23
|
end
|
26
24
|
end
|
@@ -1,17 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'typhoeus'
|
2
4
|
|
3
5
|
module Acfs
|
4
6
|
module Adapter
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
tcp_keepalive: true,
|
9
|
+
tcp_keepidle: 5,
|
10
|
+
tcp_keepintvl: 5
|
11
|
+
}.freeze
|
12
|
+
|
5
13
|
# Adapter for Typhoeus.
|
6
14
|
#
|
7
15
|
class Typhoeus < Base
|
8
|
-
def initialize(**kwargs)
|
9
|
-
@
|
16
|
+
def initialize(opts: {}, **kwargs)
|
17
|
+
@opts = DEFAULT_OPTIONS.merge(opts)
|
18
|
+
@kwargs = kwargs
|
10
19
|
end
|
11
20
|
|
12
21
|
def start
|
13
22
|
hydra.run
|
14
|
-
rescue
|
23
|
+
rescue StandardError
|
15
24
|
@hydra = nil
|
16
25
|
raise
|
17
26
|
end
|
@@ -29,21 +38,31 @@ module Acfs
|
|
29
38
|
protected
|
30
39
|
|
31
40
|
def hydra
|
32
|
-
@hydra ||= ::Typhoeus::Hydra.new(**@
|
41
|
+
@hydra ||= ::Typhoeus::Hydra.new(**@kwargs)
|
33
42
|
end
|
34
43
|
|
35
44
|
def convert_request(req)
|
36
|
-
|
45
|
+
opts = {
|
37
46
|
method: req.method,
|
38
47
|
params: req.params,
|
39
48
|
headers: req.headers.merge(
|
40
|
-
'Expect'
|
49
|
+
'Expect' => '',
|
41
50
|
'Transfer-Encoding' => ''
|
42
51
|
),
|
43
52
|
body: req.body
|
53
|
+
}
|
54
|
+
|
55
|
+
request = ::Typhoeus::Request.new(req.url, **@opts.merge(opts))
|
44
56
|
|
45
57
|
request.on_complete do |response|
|
46
|
-
|
58
|
+
if response.timed_out?
|
59
|
+
raise ::Acfs::TimeoutError.new(req)
|
60
|
+
elsif response.code.zero?
|
61
|
+
# Failed to get HTTP response
|
62
|
+
raise ::Acfs::RequestError.new(req, response.return_message)
|
63
|
+
else
|
64
|
+
req.complete! convert_response(req, response)
|
65
|
+
end
|
47
66
|
end
|
48
67
|
|
49
68
|
request
|
data/lib/acfs/collection.rb
CHANGED
data/lib/acfs/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'yaml'
|
3
5
|
|
@@ -25,7 +27,7 @@ module Acfs
|
|
25
27
|
# @return [undefined]
|
26
28
|
#
|
27
29
|
def configure(&block)
|
28
|
-
if block.arity
|
30
|
+
if block.arity.positive?
|
29
31
|
block.call self
|
30
32
|
else
|
31
33
|
instance_eval(&block)
|
@@ -37,8 +39,12 @@ module Acfs
|
|
37
39
|
# @overload locate(service, uri)
|
38
40
|
# Configures URL where a service can be reached.
|
39
41
|
#
|
40
|
-
# @param [Symbol] service
|
41
|
-
#
|
42
|
+
# @param [Symbol] service
|
43
|
+
# Service identity key for service that is reachable under given URL.
|
44
|
+
#
|
45
|
+
# @param [String] uri
|
46
|
+
# URL where service is reachable. Will be passed to {URI.parse}.
|
47
|
+
#
|
42
48
|
# @return [undefined]
|
43
49
|
#
|
44
50
|
# @overload locate(service)
|
@@ -64,7 +70,7 @@ module Acfs
|
|
64
70
|
# @return [undefined]
|
65
71
|
#
|
66
72
|
def load(filename)
|
67
|
-
config = YAML.
|
73
|
+
config = YAML.safe_load(File.read(filename), [], [], true)
|
68
74
|
env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
|
69
75
|
|
70
76
|
config = config[env] if config.key? env
|
@@ -95,18 +101,19 @@ module Acfs
|
|
95
101
|
# @return [Configuration]
|
96
102
|
#
|
97
103
|
def current
|
98
|
-
@
|
104
|
+
@current ||= new
|
99
105
|
end
|
100
106
|
|
101
107
|
# @api private
|
102
108
|
#
|
103
|
-
# Swap configuration object with given new one. Must be
|
109
|
+
# Swap configuration object with given new one. Must be
|
110
|
+
# a {Configuration} object.
|
104
111
|
#
|
105
112
|
# @param [Configuration] configuration
|
106
113
|
# @return [undefined]
|
107
114
|
#
|
108
115
|
def set(configuration)
|
109
|
-
@
|
116
|
+
@current = configuration if configuration.is_a? Configuration
|
110
117
|
end
|
111
118
|
end
|
112
119
|
end
|
data/lib/acfs/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Acfs
|
2
4
|
# Acfs base error.
|
3
5
|
#
|
@@ -10,6 +12,24 @@ module Acfs
|
|
10
12
|
|
11
13
|
class UnsupportedOperation < StandardError; end
|
12
14
|
|
15
|
+
class RequestError < Error
|
16
|
+
attr_reader :request
|
17
|
+
|
18
|
+
def initialize(request, message)
|
19
|
+
@request = request
|
20
|
+
|
21
|
+
message = "#{message}: #{request.method.upcase} #{request.url}"
|
22
|
+
|
23
|
+
super message: message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TimeoutError < RequestError
|
28
|
+
def initialize(request)
|
29
|
+
super(request, "Timeout reached")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
13
33
|
# Response error containing the responsible response object.
|
14
34
|
#
|
15
35
|
class ErroneousResponse < Error
|
@@ -17,17 +37,15 @@ module Acfs
|
|
17
37
|
|
18
38
|
def initialize(opts = {})
|
19
39
|
@response = opts[:response]
|
20
|
-
|
40
|
+
|
21
41
|
if response
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
message = 'Received'
|
26
|
-
end
|
27
|
-
message << " #{response.code} for #{response.request.method.upcase} #{response.request.url} #{response.request.format}"
|
42
|
+
message = (opts[:message] ? opts[:message] + ':' : 'Received') +
|
43
|
+
" #{response.code} for #{response.request.method.upcase}" \
|
44
|
+
" #{response.request.url} #{response.request.format}"
|
28
45
|
else
|
29
|
-
message
|
46
|
+
message = opts[:message] || 'Received erroneous response'
|
30
47
|
end
|
48
|
+
|
31
49
|
super opts, message
|
32
50
|
end
|
33
51
|
end
|
@@ -41,8 +59,11 @@ module Acfs
|
|
41
59
|
@stubs = opts.delete :stubs
|
42
60
|
@operation = opts.delete :operation
|
43
61
|
|
44
|
-
|
45
|
-
|
62
|
+
message = "Ambiguous stubs for #{operation.action} " \
|
63
|
+
"on #{operation.resource}.\n" +
|
64
|
+
stubs.map {|s| " #{s.opts.pretty_inspect}" }.join
|
65
|
+
|
66
|
+
super opts, message
|
46
67
|
end
|
47
68
|
end
|
48
69
|
|
@@ -51,7 +72,6 @@ module Acfs
|
|
51
72
|
class ResourceNotFound < ErroneousResponse
|
52
73
|
end
|
53
74
|
|
54
|
-
#
|
55
75
|
class InvalidResource < ErroneousResponse
|
56
76
|
attr_reader :errors, :resource
|
57
77
|
|
@@ -97,7 +117,8 @@ module Acfs
|
|
97
117
|
def initialize(opts = {})
|
98
118
|
@base_class = opts.delete :base_class
|
99
119
|
@type_name = opts.delete :type_name
|
100
|
-
opts[:message] = "Received resource type `#{type_name}`
|
120
|
+
opts[:message] = "Received resource type `#{type_name}` " \
|
121
|
+
"is no subclass of #{base_class}"
|
101
122
|
super
|
102
123
|
end
|
103
124
|
end
|
data/lib/acfs/global.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Acfs
|
2
4
|
#
|
3
5
|
# Global Acfs module methods.
|
@@ -73,7 +75,7 @@ module Acfs
|
|
73
75
|
end
|
74
76
|
return false if block.nil?
|
75
77
|
|
76
|
-
if resource.loaded?
|
78
|
+
if resource.nil? || resource.loaded?
|
77
79
|
block.call resource
|
78
80
|
else
|
79
81
|
resource.__callbacks__ << block
|
@@ -81,9 +83,17 @@ module Acfs
|
|
81
83
|
end
|
82
84
|
|
83
85
|
def on(*resources)
|
86
|
+
# If all resources have already been loaded, we run the callback immediately.
|
87
|
+
if resources.all? {|res| res.nil? || res.loaded? }
|
88
|
+
yield(*resources)
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
# Otherwise, we add a callback to *each* resource with a guard that ensures
|
93
|
+
# that only the very last resource being loaded executes the callback.
|
84
94
|
resources.each do |resource|
|
85
95
|
add_callback resource do |_|
|
86
|
-
yield(*resources)
|
96
|
+
yield(*resources) if resources.all? {|res| res.nil? || res.loaded? }
|
87
97
|
end
|
88
98
|
end
|
89
99
|
end
|
data/lib/acfs/location.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Acfs
|
2
4
|
# @api private
|
3
5
|
#
|
@@ -6,7 +8,7 @@ module Acfs
|
|
6
8
|
class Location
|
7
9
|
attr_reader :arguments, :raw, :struct, :args
|
8
10
|
|
9
|
-
REGEXP = /^:([A-z][A-z0-9_]*)
|
11
|
+
REGEXP = /^:([A-z][A-z0-9_]*)$/.freeze
|
10
12
|
|
11
13
|
def initialize(uri, args = {})
|
12
14
|
@raw = URI.parse uri
|
@@ -33,20 +35,22 @@ module Acfs
|
|
33
35
|
|
34
36
|
def str
|
35
37
|
uri = raw.dup
|
36
|
-
uri.path =
|
38
|
+
uri.path = '/' + struct.map {|s| lookup_arg(s, args) }.join('/')
|
37
39
|
uri.to_s
|
38
40
|
end
|
39
41
|
|
40
42
|
def raw_uri
|
41
43
|
raw.to_s
|
42
44
|
end
|
43
|
-
|
45
|
+
alias to_s raw_uri
|
44
46
|
|
45
47
|
private
|
46
48
|
|
47
49
|
def extract_arg(key, hashes)
|
48
50
|
hashes.each_with_index do |hash, index|
|
49
|
-
|
51
|
+
if hash.key?(key)
|
52
|
+
return (index == 0 ? hash.delete(key) : hash.fetch(key))
|
53
|
+
end
|
50
54
|
end
|
51
55
|
|
52
56
|
nil
|
@@ -58,7 +62,7 @@ module Acfs
|
|
58
62
|
|
59
63
|
def lookup_replacement(sym, args)
|
60
64
|
value = get_replacement(sym, args).to_s
|
61
|
-
return value unless value.empty?
|
65
|
+
return ::URI.encode_www_form_component(value) unless value.empty?
|
62
66
|
|
63
67
|
raise ArgumentError.new "Cannot replace path argument `#{sym}' with empty string."
|
64
68
|
end
|