acfs 1.3.0 → 1.4.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +3 -4
  4. data/acfs.gemspec +19 -10
  5. data/lib/acfs.rb +2 -0
  6. data/lib/acfs/adapter/base.rb +6 -8
  7. data/lib/acfs/adapter/typhoeus.rb +26 -7
  8. data/lib/acfs/collection.rb +2 -1
  9. data/lib/acfs/collections/paginatable.rb +2 -1
  10. data/lib/acfs/configuration.rb +14 -7
  11. data/lib/acfs/errors.rb +33 -12
  12. data/lib/acfs/global.rb +12 -2
  13. data/lib/acfs/location.rb +9 -5
  14. data/lib/acfs/middleware/base.rb +5 -1
  15. data/lib/acfs/middleware/json.rb +2 -0
  16. data/lib/acfs/middleware/logger.rb +2 -0
  17. data/lib/acfs/middleware/msgpack.rb +2 -0
  18. data/lib/acfs/middleware/print.rb +2 -0
  19. data/lib/acfs/middleware/serializer.rb +2 -0
  20. data/lib/acfs/operation.rb +5 -3
  21. data/lib/acfs/request.rb +3 -0
  22. data/lib/acfs/request/callbacks.rb +5 -1
  23. data/lib/acfs/resource.rb +2 -0
  24. data/lib/acfs/resource/attributes.rb +3 -2
  25. data/lib/acfs/resource/attributes/base.rb +2 -1
  26. data/lib/acfs/resource/attributes/boolean.rb +2 -0
  27. data/lib/acfs/resource/attributes/date_time.rb +2 -1
  28. data/lib/acfs/resource/attributes/dict.rb +2 -0
  29. data/lib/acfs/resource/attributes/float.rb +5 -3
  30. data/lib/acfs/resource/attributes/integer.rb +2 -0
  31. data/lib/acfs/resource/attributes/list.rb +2 -0
  32. data/lib/acfs/resource/attributes/string.rb +2 -0
  33. data/lib/acfs/resource/attributes/uuid.rb +4 -3
  34. data/lib/acfs/resource/dirty.rb +2 -0
  35. data/lib/acfs/resource/initialization.rb +2 -0
  36. data/lib/acfs/resource/loadable.rb +2 -0
  37. data/lib/acfs/resource/locatable.rb +10 -6
  38. data/lib/acfs/resource/operational.rb +2 -1
  39. data/lib/acfs/resource/persistence.rb +7 -4
  40. data/lib/acfs/resource/query_methods.rb +5 -3
  41. data/lib/acfs/resource/service.rb +3 -1
  42. data/lib/acfs/resource/validation.rb +3 -1
  43. data/lib/acfs/response.rb +2 -0
  44. data/lib/acfs/response/formats.rb +2 -0
  45. data/lib/acfs/response/status.rb +3 -1
  46. data/lib/acfs/rspec.rb +2 -0
  47. data/lib/acfs/runner.rb +6 -1
  48. data/lib/acfs/service.rb +8 -2
  49. data/lib/acfs/service/middleware.rb +2 -0
  50. data/lib/acfs/service/middleware/stack.rb +5 -3
  51. data/lib/acfs/singleton_resource.rb +4 -2
  52. data/lib/acfs/stub.rb +33 -11
  53. data/lib/acfs/util.rb +2 -0
  54. data/lib/acfs/version.rb +3 -1
  55. data/lib/acfs/yard.rb +1 -0
  56. data/spec/acfs/adapter/typhoeus_spec.rb +30 -3
  57. data/spec/acfs/collection_spec.rb +7 -5
  58. data/spec/acfs/configuration_spec.rb +2 -0
  59. data/spec/acfs/global_spec.rb +48 -1
  60. data/spec/acfs/location_spec.rb +25 -0
  61. data/spec/acfs/middleware/json_spec.rb +2 -0
  62. data/spec/acfs/middleware/msgpack_spec.rb +2 -0
  63. data/spec/acfs/operation_spec.rb +2 -0
  64. data/spec/acfs/request/callbacks_spec.rb +2 -0
  65. data/spec/acfs/request_spec.rb +3 -1
  66. data/spec/acfs/resource/attributes/boolean_spec.rb +2 -0
  67. data/spec/acfs/resource/attributes/date_time_spec.rb +2 -0
  68. data/spec/acfs/resource/attributes/dict_spec.rb +4 -2
  69. data/spec/acfs/resource/attributes/float_spec.rb +2 -0
  70. data/spec/acfs/resource/attributes/integer_spec.rb +2 -0
  71. data/spec/acfs/resource/attributes/list_spec.rb +5 -3
  72. data/spec/acfs/resource/attributes/uuid_spec.rb +2 -0
  73. data/spec/acfs/resource/attributes_spec.rb +6 -4
  74. data/spec/acfs/resource/dirty_spec.rb +2 -0
  75. data/spec/acfs/resource/initialization_spec.rb +8 -2
  76. data/spec/acfs/resource/loadable_spec.rb +2 -0
  77. data/spec/acfs/resource/locatable_spec.rb +2 -0
  78. data/spec/acfs/resource/persistance_spec.rb +10 -4
  79. data/spec/acfs/resource/query_methods_spec.rb +25 -18
  80. data/spec/acfs/resource/validation_spec.rb +2 -0
  81. data/spec/acfs/response/formats_spec.rb +3 -1
  82. data/spec/acfs/response/status_spec.rb +2 -0
  83. data/spec/acfs/runner_spec.rb +6 -8
  84. data/spec/acfs/service/middleware_spec.rb +2 -0
  85. data/spec/acfs/service_spec.rb +3 -1
  86. data/spec/acfs/singleton_resource_spec.rb +2 -0
  87. data/spec/acfs/stub_spec.rb +2 -0
  88. data/spec/acfs_spec.rb +2 -0
  89. data/spec/spec_helper.rb +11 -6
  90. data/spec/support/hash.rb +2 -0
  91. data/spec/support/response.rb +2 -0
  92. data/spec/support/service.rb +1 -0
  93. data/spec/support/shared/find_callbacks.rb +2 -0
  94. metadata +12 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81eada64bf268a7ee4bdec316baf65e2f8ca0ac8cd80120474dfede36d4b4f51
4
- data.tar.gz: b5a0d11f1eb60c8bfb16ccd66a8d7ee8f09fcaeea8976f47540239725a08d08b
3
+ metadata.gz: 6f998ff3e28801676c732fa44df93e18f651e96eb986c857c61b7f25b312a2c1
4
+ data.tar.gz: a122064cb630a664489629ef658d5ecde917ced4cdc43a600b49d0e18e00bea7
5
5
  SHA512:
6
- metadata.gz: 2cb0a9482b37b1a57bfad86f4213040d27babbf2cd6d4496a93b3260e50435ff1c575d57ba4a0d48b22ada2a3fbda44289283da1032fe3ecdc6010fe39f7646e
7
- data.tar.gz: b21823474d56cdda6ca816f21d3f76674aa00a4d2636ec0dedc15deecd17469ef55716688306a654952bee4438b5ce338cc2471b4db644852daaa09e0b2bbc4c
6
+ metadata.gz: 7801c96d794f08ad03b9ad204f8e7c8b611300922b12714ce7864a33c5a6268bc47c42e66b86c410358f17a0439d382c358ba15283eee07ff092d19a1f86a8be
7
+ data.tar.gz: 78b2c804251fb6aaee270d9c894ff36b7b8dc7bb777148de55963255847fc28adcdbff9c7132352c5448bef7b9980fd72e6a14ffceda38540d661aceaf1e5e9d
@@ -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://badge.fury.io/rb/acfs.svg)](http://badge.fury.io/rb/acfs)
4
- [![Build Status](http://img.shields.io/travis/jgraichen/acfs/master.svg)](https://travis-ci.org/jgraichen/acfs)
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.
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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(jg@altimos.de)
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(lib)
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', '~> 1.3'
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
  require 'active_support/core_ext/hash'
3
5
  require 'active_support/core_ext/class'
@@ -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
- @options = kwargs
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(**@options)
41
+ @hydra ||= ::Typhoeus::Hydra.new(**@kwargs)
33
42
  end
34
43
 
35
44
  def convert_request(req)
36
- request = ::Typhoeus::Request.new req.url,
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
- req.complete! convert_response(req, response)
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
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'delegate'
2
4
 
3
5
  require 'acfs/resource/loadable'
4
6
  require 'acfs/collections/paginatable'
5
7
 
6
8
  module Acfs
7
- #
8
9
  class Collection < ::Delegator
9
10
  include Resource::Loadable
10
11
  include Acfs::Util::Callbacks
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Acfs::Collections
2
- #
3
4
  module Paginatable
4
5
  extend ActiveSupport::Concern
5
6
 
@@ -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 > 0
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 Service identity key for service that is reachable under given URL.
41
- # @param [String] uri URL where service is reachable. Will be passed to {URI.parse}.
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.load File.read filename
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
- @configuration ||= new
104
+ @current ||= new
99
105
  end
100
106
 
101
107
  # @api private
102
108
  #
103
- # Swap configuration object with given new one. Must be a {Configuration} object.
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
- @configuration = configuration if configuration.is_a? Configuration
116
+ @current = configuration if configuration.is_a? Configuration
110
117
  end
111
118
  end
112
119
  end
@@ -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
- message = opts[:message]
40
+
21
41
  if response
22
- if message
23
- message << ':'
24
- else
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 ||= 'Received erroneous response'
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
- super opts, "Ambiguous stubs for #{operation.action} on #{operation.resource}.\n" +
45
- stubs.map {|s| " #{s.opts.pretty_inspect}" }.join
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}` is no subclass of #{base_class}"
120
+ opts[:message] = "Received resource type `#{type_name}` " \
121
+ "is no subclass of #{base_class}"
101
122
  super
102
123
  end
103
124
  end
@@ -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) unless resources.any? {|res| !res.loaded? }
96
+ yield(*resources) if resources.all? {|res| res.nil? || res.loaded? }
87
97
  end
88
98
  end
89
99
  end
@@ -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 = URI.escape '/' + struct.map {|s| lookup_arg(s, args) }.join('/')
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
- alias_method :to_s, :raw_uri
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
- return (index == 0 ? hash.delete(key) : hash.fetch(key)) if hash.key?(key)
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