faraday 0.9.1 → 0.11.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +53 -16
  4. data/lib/faraday/adapter/em_http.rb +8 -2
  5. data/lib/faraday/adapter/em_http_ssl_patch.rb +1 -1
  6. data/lib/faraday/adapter/em_synchrony.rb +16 -2
  7. data/lib/faraday/adapter/excon.rb +7 -7
  8. data/lib/faraday/adapter/httpclient.rb +27 -5
  9. data/lib/faraday/adapter/net_http.rb +13 -8
  10. data/lib/faraday/adapter/net_http_persistent.rb +6 -4
  11. data/lib/faraday/adapter/patron.rb +13 -9
  12. data/lib/faraday/adapter/test.rb +64 -21
  13. data/lib/faraday/adapter.rb +8 -1
  14. data/lib/faraday/connection.rb +14 -9
  15. data/lib/faraday/error.rb +11 -1
  16. data/lib/faraday/options.rb +18 -1
  17. data/lib/faraday/parameters.rb +54 -38
  18. data/lib/faraday/rack_builder.rb +3 -2
  19. data/lib/faraday/request/authorization.rb +1 -2
  20. data/lib/faraday/request/retry.rb +10 -4
  21. data/lib/faraday/request.rb +2 -0
  22. data/lib/faraday/response/logger.rb +27 -6
  23. data/lib/faraday/response.rb +6 -2
  24. data/lib/faraday/utils.rb +22 -2
  25. data/lib/faraday.rb +8 -33
  26. metadata +7 -93
  27. data/.document +0 -6
  28. data/CHANGELOG.md +0 -20
  29. data/CONTRIBUTING.md +0 -36
  30. data/Gemfile +0 -25
  31. data/Rakefile +0 -71
  32. data/faraday.gemspec +0 -34
  33. data/script/cached-bundle +0 -46
  34. data/script/console +0 -7
  35. data/script/generate_certs +0 -42
  36. data/script/package +0 -7
  37. data/script/proxy-server +0 -42
  38. data/script/release +0 -17
  39. data/script/s3-put +0 -71
  40. data/script/server +0 -36
  41. data/script/test +0 -172
  42. data/test/adapters/default_test.rb +0 -14
  43. data/test/adapters/em_http_test.rb +0 -20
  44. data/test/adapters/em_synchrony_test.rb +0 -20
  45. data/test/adapters/excon_test.rb +0 -20
  46. data/test/adapters/httpclient_test.rb +0 -21
  47. data/test/adapters/integration.rb +0 -254
  48. data/test/adapters/logger_test.rb +0 -82
  49. data/test/adapters/net_http_persistent_test.rb +0 -20
  50. data/test/adapters/net_http_test.rb +0 -14
  51. data/test/adapters/patron_test.rb +0 -20
  52. data/test/adapters/rack_test.rb +0 -31
  53. data/test/adapters/test_middleware_test.rb +0 -114
  54. data/test/adapters/typhoeus_test.rb +0 -28
  55. data/test/authentication_middleware_test.rb +0 -65
  56. data/test/composite_read_io_test.rb +0 -111
  57. data/test/connection_test.rb +0 -522
  58. data/test/env_test.rb +0 -218
  59. data/test/helper.rb +0 -81
  60. data/test/live_server.rb +0 -67
  61. data/test/middleware/instrumentation_test.rb +0 -88
  62. data/test/middleware/retry_test.rb +0 -177
  63. data/test/middleware_stack_test.rb +0 -173
  64. data/test/multibyte.txt +0 -1
  65. data/test/options_test.rb +0 -252
  66. data/test/parameters_test.rb +0 -64
  67. data/test/request_middleware_test.rb +0 -142
  68. data/test/response_middleware_test.rb +0 -72
  69. data/test/strawberry.rb +0 -2
  70. data/test/utils_test.rb +0 -58
@@ -55,11 +55,11 @@ module Faraday
55
55
  # :user - String (optional)
56
56
  # :password - String (optional)
57
57
  def initialize(url = nil, options = nil)
58
+ options = ConnectionOptions.from(options) unless options and options.is_a?(ConnectionOptions)
59
+
58
60
  if url.is_a?(Hash)
59
- options = ConnectionOptions.from(url)
61
+ options = options.merge(url)
60
62
  url = options.url
61
- else
62
- options = ConnectionOptions.from(options)
63
63
  end
64
64
 
65
65
  @parallel_manager = nil
@@ -126,7 +126,7 @@ module Faraday
126
126
  # req.body = JSON.generate(:query => {...})
127
127
  # end
128
128
  #
129
- # Yields a Faraday::Response for further request customizations.
129
+ # Yields a Faraday::Request for further request customizations.
130
130
  # Returns a Faraday::Response.
131
131
  #
132
132
  # Signature
@@ -163,7 +163,7 @@ module Faraday
163
163
  # req.body = JSON.generate(:user => 'kimchy', ...)
164
164
  # end
165
165
  #
166
- # Yields a Faraday::Response for further request customizations.
166
+ # Yields a Faraday::Request for further request customizations.
167
167
  # Returns a Faraday::Response.
168
168
  #
169
169
  # Signature
@@ -358,7 +358,7 @@ module Faraday
358
358
  #
359
359
  # method - The Symbol HTTP method.
360
360
  # url - The String or URI to access.
361
- # body - The String body
361
+ # body - The request body that will eventually be converted to a string.
362
362
  # headers - Hash of unencoded HTTP header key/value pairs.
363
363
  #
364
364
  # Returns a Faraday::Response.
@@ -396,7 +396,7 @@ module Faraday
396
396
  # of the resulting url (default: nil).
397
397
  #
398
398
  # Returns the resulting URI instance.
399
- def build_exclusive_url(url = nil, params = nil)
399
+ def build_exclusive_url(url = nil, params = nil, params_encoder = nil)
400
400
  url = nil if url.respond_to?(:empty?) and url.empty?
401
401
  base = url_prefix
402
402
  if url and base.path and base.path !~ /\/$/
@@ -404,7 +404,7 @@ module Faraday
404
404
  base.path = base.path + '/' # ensure trailing slash
405
405
  end
406
406
  uri = url ? base + url : base
407
- uri.query = params.to_query(options.params_encoder) if params
407
+ uri.query = params.to_query(params_encoder || options.params_encoder) if params
408
408
  uri.query = nil if uri.query and uri.query.empty?
409
409
  uri
410
410
  end
@@ -413,7 +413,12 @@ module Faraday
413
413
  #
414
414
  # Returns a Faraday::Connection.
415
415
  def dup
416
- self.class.new(build_exclusive_url, :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
416
+ self.class.new(build_exclusive_url,
417
+ :headers => headers.dup,
418
+ :params => params.dup,
419
+ :builder => builder.dup,
420
+ :ssl => ssl.dup,
421
+ :request => options.dup)
417
422
  end
418
423
 
419
424
  # Internal: Yields username and password extracted from a URI if they both exist.
data/lib/faraday/error.rb CHANGED
@@ -29,7 +29,17 @@ module Faraday
29
29
  end
30
30
 
31
31
  def inspect
32
- %(#<#{self.class}>)
32
+ inner = ''
33
+ if @wrapped_exception
34
+ inner << " wrapped=#{@wrapped_exception.inspect}"
35
+ end
36
+ if @response
37
+ inner << " response=#{@response.inspect}"
38
+ end
39
+ if inner.empty?
40
+ inner << " #{super}"
41
+ end
42
+ %(#<#{self.class}#{inner}>)
33
43
  end
34
44
  end
35
45
 
@@ -51,7 +51,14 @@ module Faraday
51
51
  def merge(value)
52
52
  dup.update(value)
53
53
  end
54
+
55
+ # Public
56
+ def dup
57
+ self.class.from(self)
58
+ end
54
59
 
60
+ alias clone dup
61
+
55
62
  # Public
56
63
  def fetch(key, *args)
57
64
  unless symbolized_key_set.include?(key.to_sym)
@@ -252,7 +259,8 @@ module Faraday
252
259
  end
253
260
 
254
261
  class Env < Options.new(:method, :body, :url, :request, :request_headers,
255
- :ssl, :parallel_manager, :params, :response, :response_headers, :status)
262
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status,
263
+ :reason_phrase)
256
264
 
257
265
  ContentLength = 'Content-Length'.freeze
258
266
  StatusesWithoutBody = Set.new [204, 304]
@@ -269,6 +277,15 @@ module Faraday
269
277
 
270
278
  def_delegators :request, :params_encoder
271
279
 
280
+ # Public
281
+ def self.from(value)
282
+ env = super(value)
283
+ if value.respond_to?(:custom_members)
284
+ env.custom_members.update(value.custom_members)
285
+ end
286
+ env
287
+ end
288
+
272
289
  # Public
273
290
  def [](key)
274
291
  if in_member_set?(key)
@@ -46,6 +46,8 @@ module Faraday
46
46
  buffer << "#{to_query.call(new_parent, val)}&"
47
47
  end
48
48
  return buffer.chop
49
+ elsif value.nil?
50
+ return parent
49
51
  else
50
52
  encoded_value = escape(value)
51
53
  return "#{parent}=#{encoded_value}"
@@ -63,50 +65,64 @@ module Faraday
63
65
 
64
66
  def self.decode(query)
65
67
  return nil if query == nil
66
- # Recursive helper lambda
67
- dehash = lambda do |hash|
68
- hash.each do |(key, value)|
69
- if value.kind_of?(Hash)
70
- hash[key] = dehash.call(value)
68
+
69
+ params = {}
70
+ query.split("&").each do |pair|
71
+ next if pair.empty?
72
+ key, value = pair.split("=", 2)
73
+ key = unescape(key)
74
+ value = unescape(value.gsub(/\+/, ' ')) if value
75
+
76
+ subkeys = key.scan(/[^\[\]]+(?:\]?\[\])?/)
77
+ context = params
78
+ subkeys.each_with_index do |subkey, i|
79
+ is_array = subkey =~ /[\[\]]+\Z/
80
+ subkey = $` if is_array
81
+ last_subkey = i == subkeys.length - 1
82
+
83
+ if !last_subkey || is_array
84
+ value_type = is_array ? Array : Hash
85
+ if context[subkey] && !context[subkey].is_a?(value_type)
86
+ raise TypeError, "expected %s (got %s) for param `%s'" % [
87
+ value_type.name,
88
+ context[subkey].class.name,
89
+ subkey
90
+ ]
91
+ end
92
+ context = (context[subkey] ||= value_type.new)
71
93
  end
72
- end
73
- # Numeric keys implies an array
74
- if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
75
- hash.sort.inject([]) do |accu, (_, value)|
76
- accu << value; accu
94
+
95
+ if context.is_a?(Array) && !is_array
96
+ if !context.last.is_a?(Hash) || context.last.has_key?(subkey)
97
+ context << {}
98
+ end
99
+ context = context.last
100
+ end
101
+
102
+ if last_subkey
103
+ if is_array
104
+ context << value
105
+ else
106
+ context[subkey] = value
107
+ end
77
108
  end
78
- else
79
- hash
80
109
  end
81
110
  end
82
111
 
83
- empty_accumulator = {}
84
- return ((query.split('&').map do |pair|
85
- pair.split('=', 2) if pair && !pair.empty?
86
- end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
87
- key = unescape(key)
88
- if value.kind_of?(String)
89
- value = unescape(value.gsub(/\+/, ' '))
90
- end
112
+ dehash(params, 0)
113
+ end
91
114
 
92
- array_notation = !!(key =~ /\[\]$/)
93
- subkeys = key.split(/[\[\]]+/)
94
- current_hash = accu
95
- for i in 0...(subkeys.size - 1)
96
- subkey = subkeys[i]
97
- current_hash[subkey] = {} unless current_hash[subkey]
98
- current_hash = current_hash[subkey]
99
- end
100
- if array_notation
101
- current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
102
- current_hash[subkeys.last] << value
103
- else
104
- current_hash[subkeys.last] = value
105
- end
106
- accu
107
- end).inject(empty_accumulator.dup) do |accu, (key, value)|
108
- accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
109
- accu
115
+ # Internal: convert a nested hash with purely numeric keys into an array.
116
+ # FIXME: this is not compatible with Rack::Utils.parse_nested_query
117
+ def self.dehash(hash, depth)
118
+ hash.each do |key, value|
119
+ hash[key] = dehash(value, depth + 1) if value.kind_of?(Hash)
120
+ end
121
+
122
+ if depth > 0 && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
123
+ hash.keys.sort.inject([]) { |all, key| all << hash[key] }
124
+ else
125
+ hash
110
126
  end
111
127
  end
112
128
  end
@@ -151,8 +151,9 @@ module Faraday
151
151
  lock!
152
152
  to_app(lambda { |env|
153
153
  response = Response.new
154
- response.finish(env) unless env.parallel?
155
154
  env.response = response
155
+ response.finish(env) unless env.parallel?
156
+ response
156
157
  })
157
158
  end
158
159
  end
@@ -188,7 +189,7 @@ module Faraday
188
189
  # :ssl - Hash of options for configuring SSL requests.
189
190
  def build_env(connection, request)
190
191
  Env.new(request.method, request.body,
191
- connection.build_exclusive_url(request.path, request.params),
192
+ connection.build_exclusive_url(request.path, request.params, request.options.params_encoder),
192
193
  request.options, request.headers, connection.ssl,
193
194
  connection.parallel_manager)
194
195
  end
@@ -16,8 +16,7 @@ module Faraday
16
16
 
17
17
  # Internal
18
18
  def self.build_hash(type, hash)
19
- offset = KEY.size + type.size + 3
20
- comma = ",\n#{' ' * offset}"
19
+ comma = ", "
21
20
  values = []
22
21
  hash.each do |key, value|
23
22
  values << "#{key}=#{value.to_s.inspect}"
@@ -22,12 +22,12 @@ module Faraday
22
22
 
23
23
  IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
24
24
 
25
- class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor,
26
- :exceptions, :methods, :retry_if)
25
+ class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
26
+ :backoff_factor, :exceptions, :methods, :retry_if)
27
27
  DEFAULT_CHECK = lambda { |env,exception| false }
28
28
 
29
29
  def self.from(value)
30
- if Fixnum === value
30
+ if Integer === value
31
31
  new(value)
32
32
  else
33
33
  super(value)
@@ -42,8 +42,12 @@ module Faraday
42
42
  (self[:interval] ||= 0).to_f
43
43
  end
44
44
 
45
+ def max_interval
46
+ (self[:max_interval] ||= Float::MAX).to_f
47
+ end
48
+
45
49
  def interval_randomness
46
- (self[:interval_randomness] ||= 0).to_i
50
+ (self[:interval_randomness] ||= 0).to_f
47
51
  end
48
52
 
49
53
  def backoff_factor
@@ -73,6 +77,7 @@ module Faraday
73
77
  # interval_randomness - The maximum random interval amount expressed
74
78
  # as a float between 0 and 1 to use in addition to the
75
79
  # interval. (default: 0)
80
+ # max_interval - An upper limit for the interval (default: Float::MAX)
76
81
  # backoff_factor - The amount to multiple each successive retry's
77
82
  # interval amount by in order to provide backoff
78
83
  # (default: 1)
@@ -98,6 +103,7 @@ module Faraday
98
103
  def sleep_amount(retries)
99
104
  retry_index = @options.max - retries
100
105
  current_interval = @options.interval * (@options.backoff_factor ** retry_index)
106
+ current_interval = [current_interval, @options.max_interval].min
101
107
  random_interval = rand * @options.interval_randomness.to_f * @options.interval
102
108
  current_interval + random_interval
103
109
  end
@@ -52,6 +52,8 @@ module Faraday
52
52
  path.query = nil
53
53
  end
54
54
  else
55
+ anchor_index = path.index('#')
56
+ path = path.slice(0, anchor_index) unless anchor_index.nil?
55
57
  path, query = path.split('?', 2)
56
58
  end
57
59
  self.path = path
@@ -4,7 +4,7 @@ module Faraday
4
4
  class Response::Logger < Response::Middleware
5
5
  extend Forwardable
6
6
 
7
- DEFAULT_OPTIONS = { :bodies => false }
7
+ DEFAULT_OPTIONS = { :headers => true, :bodies => false }
8
8
 
9
9
  def initialize(app, logger = nil, options = {})
10
10
  super(app)
@@ -12,22 +12,28 @@ module Faraday
12
12
  require 'logger'
13
13
  ::Logger.new(STDOUT)
14
14
  end
15
+ @filter = []
15
16
  @options = DEFAULT_OPTIONS.merge(options)
17
+ yield self if block_given?
16
18
  end
17
19
 
18
20
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
19
21
 
20
22
  def call(env)
21
- info "#{env.method} #{env.url.to_s}"
22
- debug('request') { dump_headers env.request_headers }
23
- debug('request') { dump_body(env[:body]) } if env[:body] && log_body?(:request)
23
+ info "#{env.method} #{apply_filters(env.url.to_s)}"
24
+ debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
+ debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
24
26
  super
25
27
  end
26
28
 
27
29
  def on_complete(env)
28
30
  info('Status') { env.status.to_s }
29
- debug('response') { dump_headers env.response_headers }
30
- debug('response') { dump_body env[:body] } if env[:body] && log_body?(:response)
31
+ debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
+ debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
+ end
34
+
35
+ def filter(filter_word, filter_replacement)
36
+ @filter.push([ filter_word, filter_replacement ])
31
37
  end
32
38
 
33
39
  private
@@ -49,11 +55,26 @@ module Faraday
49
55
  body.pretty_inspect
50
56
  end
51
57
 
58
+ def log_headers?(type)
59
+ case @options[:headers]
60
+ when Hash then @options[:headers][type]
61
+ else @options[:headers]
62
+ end
63
+ end
64
+
52
65
  def log_body?(type)
53
66
  case @options[:bodies]
54
67
  when Hash then @options[:bodies][type]
55
68
  else @options[:bodies]
56
69
  end
57
70
  end
71
+
72
+ def apply_filters(output)
73
+ @filter.each do |pattern, replacement|
74
+ output = output.to_s.gsub(pattern, replacement)
75
+ end
76
+ output
77
+ end
78
+
58
79
  end
59
80
  end
@@ -37,6 +37,10 @@ module Faraday
37
37
  finished? ? env.status : nil
38
38
  end
39
39
 
40
+ def reason_phrase
41
+ finished? ? env.reason_phrase : nil
42
+ end
43
+
40
44
  def headers
41
45
  finished? ? env.response_headers : {}
42
46
  end
@@ -61,8 +65,8 @@ module Faraday
61
65
 
62
66
  def finish(env)
63
67
  raise "response already finished" if finished?
64
- @on_complete_callbacks.each { |callback| callback.call(env) }
65
- @env = Env.from(env)
68
+ @env = env.is_a?(Env) ? env : Env.from(env)
69
+ @on_complete_callbacks.each { |callback| callback.call(@env) }
66
70
  return self
67
71
  end
68
72
 
data/lib/faraday/utils.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'thread'
2
- Faraday.require_libs 'parameters'
3
2
 
4
3
  module Faraday
5
4
  module Utils
@@ -17,6 +16,12 @@ module Faraday
17
16
  self.update(hash || {})
18
17
  end
19
18
 
19
+ # on dup/clone, we need to duplicate @names hash
20
+ def initialize_copy(other)
21
+ super
22
+ @names = other.names.dup
23
+ end
24
+
20
25
  # need to synchronize concurrent writes to the shared KeyMap
21
26
  keymap_mutex = Mutex.new
22
27
 
@@ -81,6 +86,7 @@ module Faraday
81
86
 
82
87
  def replace(other)
83
88
  clear
89
+ @names.clear
84
90
  self.update other
85
91
  self
86
92
  end
@@ -91,7 +97,7 @@ module Faraday
91
97
  return unless header_string && !header_string.empty?
92
98
  header_string.split(/\r\n/).
93
99
  tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
94
- map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
100
+ map { |h| h.split(/:\s*/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
95
101
  each { |key, value|
96
102
  # join multiple values with a comma
97
103
  if self[key]
@@ -101,6 +107,20 @@ module Faraday
101
107
  end
102
108
  }
103
109
  end
110
+
111
+ def init_with(coder)
112
+ @names = coder['names']
113
+ end
114
+
115
+ def encode_with(coder)
116
+ coder['names'] = @names
117
+ end
118
+
119
+ protected
120
+
121
+ def names
122
+ @names
123
+ end
104
124
  end
105
125
 
106
126
  # hash with stringified keys
data/lib/faraday.rb CHANGED
@@ -14,7 +14,7 @@ require 'forwardable'
14
14
  # conn.get '/'
15
15
  #
16
16
  module Faraday
17
- VERSION = "0.9.1"
17
+ VERSION = "0.11.0"
18
18
 
19
19
  class << self
20
20
  # Public: Gets or sets the root path that Faraday is being loaded from.
@@ -34,9 +34,6 @@ module Faraday
34
34
  # Faraday.get "https://faraday.com"
35
35
  attr_writer :default_connection
36
36
 
37
- # Public: Sets the default options used when calling Faraday#new.
38
- attr_writer :default_connection_options
39
-
40
37
  # Public: Initializes a new Faraday::Connection.
41
38
  #
42
39
  # url - The optional String base URL to use as a prefix for all
@@ -92,6 +89,10 @@ module Faraday
92
89
 
93
90
  alias require_lib require_libs
94
91
 
92
+ def respond_to?(symbol, include_private = false)
93
+ default_connection.respond_to?(symbol, include_private) || super
94
+ end
95
+
95
96
  private
96
97
  # Internal: Proxies method calls on the Faraday constant to
97
98
  # #default_connection.
@@ -118,13 +119,9 @@ module Faraday
118
119
  @default_connection_options ||= ConnectionOptions.new
119
120
  end
120
121
 
121
- if (!defined?(RUBY_ENGINE) || "ruby" == RUBY_ENGINE) && RUBY_VERSION < '1.9'
122
- begin
123
- require 'system_timer'
124
- Timer = SystemTimer
125
- rescue LoadError
126
- warn "Faraday: you may want to install system_timer for reliable timeouts"
127
- end
122
+ # Public: Sets the default options used when calling Faraday#new.
123
+ def self.default_connection_options=(options)
124
+ @default_connection_options = ConnectionOptions.from(options)
128
125
  end
129
126
 
130
127
  unless const_defined? :Timer
@@ -244,25 +241,3 @@ module Faraday
244
241
  require_lib 'autoload'
245
242
  end
246
243
  end
247
-
248
- # not pulling in active-support JUST for this method. And I love this method.
249
- class Object
250
- # The primary purpose of this method is to "tap into" a method chain,
251
- # in order to perform operations on intermediate results within the chain.
252
- #
253
- # Examples
254
- #
255
- # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
256
- # tap { |x| puts "array: #{x.inspect}" }.
257
- # select { |x| x%2 == 0 }.
258
- # tap { |x| puts "evens: #{x.inspect}" }.
259
- # map { |x| x*x }.
260
- # tap { |x| puts "squares: #{x.inspect}" }
261
- #
262
- # Yields self.
263
- # Returns self.
264
- def tap
265
- yield(self)
266
- self
267
- end unless Object.respond_to?(:tap)
268
- end