api_client 0.5.24-java → 0.6.0-java

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 (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