api_client 0.5.24-java → 0.6.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/lib/api_client/base.rb +77 -0
  4. data/lib/api_client/connection/abstract.rb +81 -0
  5. data/lib/api_client/connection/basic.rb +131 -0
  6. data/lib/api_client/connection/json.rb +14 -0
  7. data/lib/api_client/connection/middlewares/request/json.rb +34 -0
  8. data/lib/api_client/connection/middlewares/request/logger.rb +64 -0
  9. data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
  10. data/lib/api_client/connection/oauth.rb +18 -0
  11. data/lib/api_client/errors.rb +32 -0
  12. data/lib/api_client/mixins/configuration.rb +24 -0
  13. data/lib/api_client/mixins/connection_hooks.rb +24 -0
  14. data/lib/api_client/mixins/delegation.rb +23 -0
  15. data/lib/api_client/mixins/inheritance.rb +19 -0
  16. data/lib/api_client/mixins/instantiation.rb +29 -0
  17. data/lib/api_client/mixins/scoping.rb +49 -0
  18. data/lib/api_client/resource/base.rb +67 -0
  19. data/lib/api_client/resource/name_resolver.rb +37 -0
  20. data/lib/api_client/resource/scope.rb +73 -0
  21. data/lib/api_client/scope.rb +125 -0
  22. data/lib/api_client/utils.rb +18 -0
  23. data/lib/api_client/version.rb +3 -0
  24. data/spec/api_client/base/connection_hook_spec.rb +18 -0
  25. data/spec/api_client/base/delegation_spec.rb +15 -0
  26. data/spec/api_client/base/inheritance_spec.rb +44 -0
  27. data/spec/api_client/base/instantiation_spec.rb +55 -0
  28. data/spec/api_client/base/marshalling_spec.rb +33 -0
  29. data/spec/api_client/base/parsing_spec.rb +38 -0
  30. data/spec/api_client/base/scoping_spec.rb +60 -0
  31. data/spec/api_client/base_spec.rb +107 -0
  32. data/spec/api_client/connection/abstract_spec.rb +21 -0
  33. data/spec/api_client/connection/basic_spec.rb +198 -0
  34. data/spec/api_client/connection/oauth_spec.rb +23 -0
  35. data/spec/api_client/connection/request/json_spec.rb +30 -0
  36. data/spec/api_client/connection/request/logger_spec.rb +18 -0
  37. data/spec/api_client/connection/request/oauth_spec.rb +26 -0
  38. data/spec/api_client/resource/base_spec.rb +97 -0
  39. data/spec/api_client/resource/name_spec.rb +19 -0
  40. data/spec/api_client/resource/scope_spec.rb +122 -0
  41. data/spec/api_client/scope_spec.rb +204 -0
  42. data/spec/api_client/utils_spec.rb +32 -0
  43. data/spec/support/matchers.rb +5 -0
  44. metadata +72 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86de6b224c93faa28969412f7d2121a53a613739959da4d0993b7e3143c2ccb3
4
- data.tar.gz: 5e51da700d497e3444e515c166459f4485e180b5705a04af3212750e3d924937
3
+ metadata.gz: 861da114f0da9d319e5e08c020e4c07f29299fb111975feeec2b926142f7d1f5
4
+ data.tar.gz: f4faf72c964e354f05ff9bafe9da81385f749453130c21877081cf3b73dae220
5
5
  SHA512:
6
- metadata.gz: 511be118de39ceefb8cf983e9798fec9de476f82c78e77e2b8924d97a2740f3e1b9daef81a763678407a08cb158de6a77d8b5ee5fdc9398fa363fbc9cd9a1fba
7
- data.tar.gz: bb9d276c8ce9c2442acee7b085801e8e728e5898a668678a37725b6ce747f7bd9a6855c46eb210009fa19b272437970ccbda655fec0ea569e47556478033bf88
6
+ metadata.gz: c928fcc0591ec30b435c8e135b583228e302282031dcc5c8122f7edcd3987010ddb43b8954b3c169a89e77a43d50ce4e898c0c3ec1ea93b539b85f1bf3c94d39
7
+ data.tar.gz: 1601de7481ad45bbdce864ec4291197721b222dafbc0b119dd739a26fc38ece6a96a728b40f038809ae886fb89498328a699245b901a798f703a9a2d23a3051c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 0.6.0
2
+
3
+ * Add support for faraday gem version >= 1.0.0 and < 2.0.0
4
+ * Drop support for Ruby < 2.3
5
+
6
+ # 0.5.26
7
+
8
+ * Add support for HTTP status code: 412 Precondition Failed
9
+
10
+ # 0.5.25
11
+
12
+ * Fix broken gem build (gemspec files)
13
+
1
14
  # 0.5.23
2
15
 
3
16
  * Add support for HTTP status code: 423 Locked
@@ -0,0 +1,77 @@
1
+ module ApiClient
2
+
3
+ class Base < Hashie::Mash
4
+
5
+ extend ApiClient::Mixins::Inheritance
6
+ extend ApiClient::Mixins::Instantiation
7
+ extend ApiClient::Mixins::Scoping
8
+ extend ApiClient::Mixins::ConnectionHooks
9
+
10
+ class << self
11
+ extend ApiClient::Mixins::Delegation
12
+ extend ApiClient::Mixins::Configuration
13
+
14
+ delegate :fetch, :get, :put, :post, :patch, :delete, :headers, :endpoint, :options, :adapter, :params, :raw, :to => :scope
15
+
16
+ dsl_accessor :format, :namespace
17
+
18
+ def subkey_class
19
+ Hashie::Mash
20
+ end
21
+
22
+ def parse(response)
23
+ if response.is_a?(Faraday::Response)
24
+ return nil if response.status == 204
25
+ response = response.body
26
+ end
27
+
28
+ if self.format == :json
29
+ MultiJson.load(response)
30
+ elsif self.format == :xml
31
+ MultiXml.parse(response)
32
+ else
33
+ response
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ # Defaults
40
+ self.format :json
41
+
42
+ def id
43
+ self['id']
44
+ end
45
+
46
+ def inspect
47
+ attributes = []
48
+ attr_keys = self.keys - ['id']
49
+ attributes.push "id: #{self.id}" if self.id
50
+ attr_keys.each do |key|
51
+ attributes.push("#{key}: #{self[key].inspect}")
52
+ end
53
+ "#<#{self.class} #{attributes.join(', ')}>"
54
+ end
55
+
56
+ private
57
+ def method_missing(method_name, *args, &blk)
58
+ if respond_to?(method_name) || has_special_ending?(method_name)
59
+ super
60
+ elsif use_strict_reader?(method_name)
61
+ fetch(method_name)
62
+ else
63
+ super
64
+ end
65
+ end
66
+
67
+ def use_strict_reader?(method_name)
68
+ respond_to?(:strict_attr_reader?) &&
69
+ self.strict_attr_reader? &&
70
+ method_name != :to_ary
71
+ end
72
+
73
+ def has_special_ending?(name)
74
+ name.to_s =~ /[?=]$/
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,81 @@
1
+ module ApiClient
2
+
3
+ module Connection
4
+
5
+ class Abstract
6
+
7
+ attr_accessor :endpoint, :handler, :options
8
+
9
+ def initialize(endpoint, options = {})
10
+ raise "Cannot instantiate abstract class" if self.class == ApiClient::Connection::Abstract
11
+ @endpoint = endpoint
12
+ @options = options
13
+ create_handler
14
+ end
15
+
16
+ def create_handler
17
+ end
18
+
19
+ #### ApiClient::Connection::Abstract#get
20
+ # Performs a GET request
21
+ # Accepts three parameters:
22
+ #
23
+ # * path - the path the request should go to
24
+ # * data - (optional) the query, passed as a hash and converted into query params
25
+ # * headers - (optional) headers sent along with the request
26
+ #
27
+ def get(path, data = {}, headers = {})
28
+ end
29
+
30
+ #### ApiClient::Connection::Abstract#post
31
+ # Performs a POST request
32
+ # Accepts three parameters:
33
+ #
34
+ # * path - the path request should go to
35
+ # * data - (optional) data sent in the request
36
+ # * headers - (optional) headers sent along in the request
37
+ #
38
+ def post(path, data = {}, headers = {})
39
+ end
40
+
41
+ #### ApiClient::Connection::Abstract#patch
42
+ # Performs a PATCH request
43
+ # Accepts three parameters:
44
+ #
45
+ # * path - the path request should go to
46
+ # * data - (optional) data sent in the request
47
+ # * headers - (optional) headers sent along in the request
48
+ #
49
+ def patch(path, data = {}, headers = {})
50
+ end
51
+
52
+ #### ApiClient::Connection::Abstract#put
53
+ # Performs a PUT request
54
+ # Accepts three parameters:
55
+ #
56
+ # * path - the path request should go to
57
+ # * data - (optional) data sent in the request
58
+ # * headers - (optional) headers sent along in the request
59
+ #
60
+ def put(path, data = {}, headers = {})
61
+ end
62
+
63
+ #### FS::Connection#delete
64
+ # Performs a DELETE request
65
+ # Accepts three parameters:
66
+ #
67
+ # * path - the path request should go to
68
+ # * data - (optional) the query, passed as a hash and converted into query params
69
+ # * headers - (optional) headers sent along in the request
70
+ #
71
+ def delete(path, data = {}, headers = {})
72
+ end
73
+
74
+ def inspect
75
+ "#<#{self.class} endpoint: \"#{endpoint}\">"
76
+ end
77
+ alias :to_s :inspect
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,131 @@
1
+ module ApiClient
2
+
3
+ module Connection
4
+
5
+ class Basic < Abstract
6
+
7
+ def create_handler
8
+ # Create and memoize the connection object
9
+ # The empty block is necessary as we don't want Faraday to
10
+ # initialize itself, we build our own stack in finalize_handler
11
+ @handler = Faraday.new(@endpoint, @options[:faraday] || {}) do end
12
+ finalize_handler
13
+ end
14
+
15
+ def finalize_handler
16
+ @handler.use Middlewares::Request::Logger, ApiClient.logger if ApiClient.logger
17
+ @handler.use Faraday::Request::UrlEncoded
18
+ @handler.adapter Faraday.default_adapter
19
+ end
20
+
21
+ #### ApiClient::Connection::Abstract#get
22
+ # Performs a GET request
23
+ # Accepts three parameters:
24
+ #
25
+ # * path - the path the request should go to
26
+ # * data - (optional) the query, passed as a hash and converted into query params
27
+ # * headers - (optional) headers sent along with the request
28
+ #
29
+ def get(path, data = {}, headers = {})
30
+ exec_request(:get, path, data, headers)
31
+ end
32
+
33
+ #### ApiClient::Connection::Abstract#post
34
+ # Performs a POST request
35
+ # Accepts three parameters:
36
+ #
37
+ # * path - the path request should go to
38
+ # * data - (optional) data sent in the request
39
+ # * headers - (optional) headers sent along in the request
40
+ #
41
+ # This method automatically adds the application token header
42
+ def post(path, data = {}, headers = {})
43
+ exec_request(:post, path, data, headers)
44
+ end
45
+
46
+ #### ApiClient::Connection::Abstract#patch
47
+ # Performs a PATCH request
48
+ # Accepts three parameters:
49
+ #
50
+ # * path - the path request should go to
51
+ # * data - (optional) data sent in the request
52
+ # * headers - (optional) headers sent along in the request
53
+ #
54
+ # This method automatically adds the application token header
55
+ def patch(path, data = {}, headers = {})
56
+ exec_request(:patch, path, data, headers)
57
+ end
58
+
59
+ #### ApiClient::Connection::Abstract#put
60
+ # Performs a PUT request
61
+ # Accepts three parameters:
62
+ #
63
+ # * path - the path request should go to
64
+ # * data - (optional) data sent in the request
65
+ # * headers - (optional) headers sent along in the request
66
+ #
67
+ # This method automatically adds the application token header
68
+ def put(path, data = {}, headers = {})
69
+ exec_request(:put, path, data, headers)
70
+ end
71
+
72
+ #### FS::Connection#delete
73
+ # Performs a DELETE request
74
+ # Accepts three parameters:
75
+ #
76
+ # * path - the path request should go to
77
+ # * data - (optional) the query, passed as a hash and converted into query params
78
+ # * headers - (optional) headers sent along in the request
79
+ #
80
+ # This method automatically adds the application token header
81
+ def delete(path, data = {}, headers = {})
82
+ exec_request(:delete, path, data, headers)
83
+ end
84
+
85
+ private
86
+
87
+ def exec_request(method, path, data, headers)
88
+ response = @handler.send(method, path, data, headers)
89
+ request = { :method => method, :path => path, :data => data}
90
+ handle_response(request, response)
91
+ rescue Faraday::ConnectionFailed => e
92
+ raise ApiClient::Errors::ConnectionFailed.new(e.message, request, response)
93
+ end
94
+
95
+ def handle_response(request, response)
96
+ raise ApiClient::Errors::ConnectionFailed.new(nil, request, response) unless response
97
+ case response.status
98
+ when 401
99
+ raise ApiClient::Errors::Unauthorized.new(nil, request, response)
100
+ when 403
101
+ raise ApiClient::Errors::Forbidden.new(nil, request, response)
102
+ when 404
103
+ raise ApiClient::Errors::NotFound.new(nil, request, response)
104
+ when 400
105
+ raise ApiClient::Errors::BadRequest.new(nil, request, response)
106
+ when 406
107
+ raise ApiClient::Errors::Unsupported.new(nil, request, response)
108
+ when 409
109
+ raise ApiClient::Errors::Conflict.new(nil, request, response)
110
+ when 410
111
+ raise ApiClient::Errors::Gone.new(nil, request, response)
112
+ when 412
113
+ raise ApiClient::Errors::PreconditionFailed.new(nil, request, response)
114
+ when 422
115
+ raise ApiClient::Errors::UnprocessableEntity.new(response.body, request, response)
116
+ when 423
117
+ raise ApiClient::Errors::Locked.new(response.body, request, response)
118
+ when 429
119
+ raise ApiClient::Errors::TooManyRequests.new(response.body, request, response)
120
+ when 300..399
121
+ raise ApiClient::Errors::Redirect.new(response['Location'], request, response)
122
+ when 500..599
123
+ raise ApiClient::Errors::ServerError.new(nil, request, response)
124
+ else
125
+ response
126
+ end
127
+ end
128
+
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,14 @@
1
+ # Exactly like Basic, but uses JSON encoding for request body
2
+ # if applicable
3
+ module ApiClient
4
+ module Connection
5
+ class Json < Basic
6
+ def finalize_handler
7
+ @handler.use Middlewares::Request::Logger, ApiClient.logger if ApiClient.logger
8
+ @handler.use Middlewares::Request::Json
9
+ @handler.use Faraday::Request::UrlEncoded
10
+ @handler.adapter Faraday.default_adapter
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ class ApiClient::Connection::Middlewares::Request::Json < Faraday::Middleware
2
+ CONTENT_TYPE = "Content-Type".freeze
3
+
4
+ class << self
5
+ attr_accessor :mime_type
6
+ end
7
+ self.mime_type = "application/json".freeze
8
+
9
+ def call(env)
10
+ match_content_type(env) do |data|
11
+ params = Faraday::Utils::ParamsHash[data]
12
+ env[:body] = MultiJson.dump(params)
13
+ end
14
+ @app.call env
15
+ end
16
+
17
+ def match_content_type(env)
18
+ if process_request?(env)
19
+ env[:request_headers][CONTENT_TYPE] ||= self.class.mime_type
20
+ yield env[:body] unless env[:body].respond_to?(:to_str)
21
+ end
22
+ end
23
+
24
+ def process_request?(env)
25
+ type = request_type(env)
26
+ env[:body] and (type.empty? or type == self.class.mime_type)
27
+ end
28
+
29
+ def request_type(env)
30
+ type = env[:request_headers][CONTENT_TYPE].to_s
31
+ type = type.split(";", 2).first if type.index(";")
32
+ type
33
+ end
34
+ end
@@ -0,0 +1,64 @@
1
+ require "logger"
2
+
3
+ class ApiClient::Connection::Middlewares::Request::Logger < Faraday::Middleware
4
+ def call(env)
5
+ debug_lines = []
6
+ should_log_details = @logger.level <= ::Logger::DEBUG
7
+
8
+ gather_request_debug_lines(env, debug_lines) if should_log_details
9
+
10
+ start = CurrentTimestamp.milis
11
+ response = @app.call(env)
12
+ taken_sec = (CurrentTimestamp.milis - start) / 1000.0
13
+
14
+ gather_response_debug_lines(response, taken_sec, debug_lines) if response && should_log_details
15
+
16
+ if should_log_details
17
+ debug_lines.each { |line| line.encode!("UTF-8", invalid: :replace, undef: :replace) }
18
+ @logger.debug { debug_lines.join("\n") }
19
+ else
20
+ @logger.info { "#{env[:method].to_s.upcase} #{env[:url]}: #{"%.4f" % taken_sec} seconds" }
21
+ end
22
+
23
+ response
24
+ end
25
+
26
+ def initialize(app, logger = nil)
27
+ @logger = logger || ::Logger.new(STDOUT)
28
+ @app = app
29
+ end
30
+
31
+ private
32
+
33
+ def gather_request_debug_lines(env, debug_lines)
34
+ debug_lines << "> #{env[:method].to_s.upcase} #{env[:url]}"
35
+ env[:request_headers].each { |k, v| debug_lines << "> #{k}: #{v}" }
36
+ debug_lines << "> "
37
+ debug_lines << "> #{env[:body]}\n> " if env[:body] && env[:body] != ""
38
+ debug_lines
39
+ end
40
+
41
+ def gather_response_debug_lines(response, taken_sec, debug_lines)
42
+ debug_lines << "< responded in #{"%.4f" % taken_sec} seconds with HTTP #{response.status}"
43
+ response.headers.each { |k, v| debug_lines << "< #{k}: #{v}" }
44
+ debug_lines << "< "
45
+ debug_lines << "< #{response.body}\n> " if response.body && response.body != ""
46
+ debug_lines
47
+ end
48
+
49
+ class CurrentTimestamp
50
+ class << self
51
+ version = RUBY_VERSION.split('.').map(&:to_i)
52
+
53
+ if (version[0] < 2) || (version[0] == 2 && version[1] < 2)
54
+ def milis
55
+ (Time.now.to_f * 1000).to_i
56
+ end
57
+ else
58
+ def milis
59
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,22 @@
1
+ # Borrowed from https://github.com/pengwynn/faraday_middleware/blob/master/lib/faraday/request/oauth.rb
2
+ class ApiClient::Connection::Middlewares::Request::OAuth < Faraday::Middleware
3
+
4
+ dependency 'simple_oauth'
5
+
6
+ def call(env)
7
+ params = env[:body] || {}
8
+ signature_params = params.reject{ |k,v| v.respond_to?(:content_type) }
9
+
10
+ header = SimpleOAuth::Header.new(env[:method], env[:url], signature_params, @options || {})
11
+
12
+ env[:request_headers]['Authorization'] = header.to_s
13
+ env[:request_headers]['User-Agent'] = "ApiClient gem v#{ApiClient::VERSION}"
14
+
15
+ @app.call(env)
16
+ end
17
+
18
+ def initialize(app, options = {})
19
+ @app, @options = app, options
20
+ end
21
+
22
+ end
@@ -0,0 +1,18 @@
1
+ module ApiClient
2
+
3
+ module Connection
4
+
5
+ class Oauth < Basic
6
+
7
+ def finalize_handler
8
+ @handler.use Middlewares::Request::Logger, ApiClient.logger if ApiClient.logger
9
+ @handler.use Middlewares::Request::OAuth, @options[:oauth]
10
+ @handler.use Faraday::Request::UrlEncoded
11
+ @handler.adapter Faraday.default_adapter
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,32 @@
1
+ module ApiClient
2
+
3
+ module Errors
4
+ class ApiClientError < StandardError
5
+ def initialize(message = nil, request = nil, response = nil)
6
+ message ||= "Status code: #{response.status}" if response
7
+ super(message)
8
+ @request = request
9
+ @response = response
10
+ end
11
+
12
+ attr_reader :request, :response
13
+ end
14
+
15
+ class ConnectionFailed < ApiClientError; end
16
+ class Config < ApiClientError; end
17
+ class Unauthorized < ApiClientError; end
18
+ class Forbidden < ApiClientError; end
19
+ class NotFound < ApiClientError; end
20
+ class Redirect < ApiClientError; end
21
+ class BadRequest < ApiClientError; end
22
+ class Unsupported < ApiClientError; end
23
+ class Conflict < ApiClientError; end
24
+ class Gone < ApiClientError; end
25
+ class ServerError < ApiClientError; end
26
+ class UnprocessableEntity < ApiClientError; end
27
+ class PreconditionFailed < ApiClientError; end
28
+ class Locked < ApiClientError; end
29
+ class TooManyRequests < ApiClientError; end
30
+ end
31
+
32
+ end
@@ -0,0 +1,24 @@
1
+ module ApiClient
2
+
3
+ module Mixins
4
+
5
+ module Configuration
6
+
7
+ def dsl_accessor(*names)
8
+ options = names.last.is_a?(Hash) ? names.pop : {}
9
+ names.each do |name|
10
+ returns = options[:return_self] ? "self" : "@#{name}"
11
+ class_eval <<-STR
12
+ def #{name}(value = nil)
13
+ value.nil? ? @#{name} : @#{name} = value
14
+ #{returns}
15
+ end
16
+ STR
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,24 @@
1
+ module ApiClient
2
+
3
+ module Mixins
4
+
5
+ module ConnectionHooks
6
+
7
+ attr_accessor :connection_hooks
8
+
9
+ def connection(&block)
10
+ @connection_hooks ||= []
11
+ @connection_hooks.push(block) if block
12
+ @connection_hooks
13
+ end
14
+
15
+ def connection_hooks
16
+ @connection_hooks || []
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,23 @@
1
+ module ApiClient
2
+
3
+ module Mixins
4
+
5
+ module Delegation
6
+
7
+ def delegate(*methods)
8
+ hash = methods.pop
9
+ to = hash[:to]
10
+ methods.each do |method|
11
+ class_eval <<-STR
12
+ def #{method}(*args, &block)
13
+ #{to}.#{method}(*args, &block)
14
+ end
15
+ STR
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,19 @@
1
+ module ApiClient
2
+
3
+ module Mixins
4
+
5
+ module Inheritance
6
+
7
+ def inherited(subclass)
8
+ subclass.default_scopes = self.default_scopes.dup
9
+ subclass.connection_hooks = self.connection_hooks.dup
10
+
11
+ subclass.namespace self.namespace
12
+ subclass.format self.format
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,29 @@
1
+ module ApiClient
2
+ module Mixins
3
+ module Instantiation
4
+ def self.extended(base)
5
+ base.instance_eval do
6
+ attr_accessor :original_scope
7
+ end
8
+ end
9
+
10
+ def build_one(hash)
11
+ instance = self.new self.namespace ? hash[namespace] : hash
12
+ instance.original_scope = self.scope.clone_only_headers
13
+ instance
14
+ end
15
+
16
+ def build_many(array)
17
+ array.collect { |one| build_one(one) }
18
+ end
19
+
20
+ def build(result_or_array)
21
+ if result_or_array.is_a?(Array)
22
+ build_many result_or_array
23
+ else
24
+ build_one result_or_array
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ module ApiClient
2
+
3
+ module Mixins
4
+
5
+ module Scoping
6
+
7
+ attr_accessor :default_scopes
8
+
9
+ # Default scoping
10
+ def always(&block)
11
+ default_scopes.push(block) if block
12
+ end
13
+
14
+ def default_scopes
15
+ @default_scopes || []
16
+ end
17
+
18
+ # Scoping
19
+ def scope(options = {})
20
+ scope_in_thread || Scope.new(self).params(options)
21
+ end
22
+
23
+ # Allow wrapping singleton methods in a scope
24
+ # Store the handler in a thread-local variable for thread safety
25
+ def scoped(scope)
26
+ Thread.current[scope_thread_attribute_name] ||= []
27
+ Thread.current[scope_thread_attribute_name].push scope
28
+ begin
29
+ yield
30
+ ensure
31
+ Thread.current[scope_thread_attribute_name] = nil
32
+ end
33
+ end
34
+
35
+ def scope_thread_attribute_name
36
+ "#{self.name}_scope"
37
+ end
38
+
39
+ def scope_in_thread
40
+ if found = Thread.current[scope_thread_attribute_name]
41
+ found.last
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end