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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/api_client/base.rb +77 -0
- data/lib/api_client/connection/abstract.rb +81 -0
- data/lib/api_client/connection/basic.rb +131 -0
- data/lib/api_client/connection/json.rb +14 -0
- data/lib/api_client/connection/middlewares/request/json.rb +34 -0
- data/lib/api_client/connection/middlewares/request/logger.rb +64 -0
- data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
- data/lib/api_client/connection/oauth.rb +18 -0
- data/lib/api_client/errors.rb +32 -0
- data/lib/api_client/mixins/configuration.rb +24 -0
- data/lib/api_client/mixins/connection_hooks.rb +24 -0
- data/lib/api_client/mixins/delegation.rb +23 -0
- data/lib/api_client/mixins/inheritance.rb +19 -0
- data/lib/api_client/mixins/instantiation.rb +29 -0
- data/lib/api_client/mixins/scoping.rb +49 -0
- data/lib/api_client/resource/base.rb +67 -0
- data/lib/api_client/resource/name_resolver.rb +37 -0
- data/lib/api_client/resource/scope.rb +73 -0
- data/lib/api_client/scope.rb +125 -0
- data/lib/api_client/utils.rb +18 -0
- data/lib/api_client/version.rb +3 -0
- data/spec/api_client/base/connection_hook_spec.rb +18 -0
- data/spec/api_client/base/delegation_spec.rb +15 -0
- data/spec/api_client/base/inheritance_spec.rb +44 -0
- data/spec/api_client/base/instantiation_spec.rb +55 -0
- data/spec/api_client/base/marshalling_spec.rb +33 -0
- data/spec/api_client/base/parsing_spec.rb +38 -0
- data/spec/api_client/base/scoping_spec.rb +60 -0
- data/spec/api_client/base_spec.rb +107 -0
- data/spec/api_client/connection/abstract_spec.rb +21 -0
- data/spec/api_client/connection/basic_spec.rb +198 -0
- data/spec/api_client/connection/oauth_spec.rb +23 -0
- data/spec/api_client/connection/request/json_spec.rb +30 -0
- data/spec/api_client/connection/request/logger_spec.rb +18 -0
- data/spec/api_client/connection/request/oauth_spec.rb +26 -0
- data/spec/api_client/resource/base_spec.rb +97 -0
- data/spec/api_client/resource/name_spec.rb +19 -0
- data/spec/api_client/resource/scope_spec.rb +122 -0
- data/spec/api_client/scope_spec.rb +204 -0
- data/spec/api_client/utils_spec.rb +32 -0
- data/spec/support/matchers.rb +5 -0
- metadata +72 -11
@@ -0,0 +1,67 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Resource
|
4
|
+
|
5
|
+
class Base < ApiClient::Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
extend ApiClient::Mixins::Delegation
|
9
|
+
extend ApiClient::Mixins::Configuration
|
10
|
+
|
11
|
+
delegate :find_all, :find, :create, :update, :destroy, :path, :to => :scope
|
12
|
+
|
13
|
+
dsl_accessor :prefix
|
14
|
+
|
15
|
+
def inherited(subclass)
|
16
|
+
super
|
17
|
+
small_name = NameResolver.resolve(subclass.name)
|
18
|
+
subclass.namespace small_name
|
19
|
+
subclass.prefix self.prefix
|
20
|
+
subclass.always do
|
21
|
+
name = small_name
|
22
|
+
pre_fix = prefix
|
23
|
+
path ["", prefix, "#{name}s"].compact.join('/')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def scope(options = {})
|
28
|
+
scope_in_thread || ApiClient::Resource::Scope.new(self).params(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def persisted?
|
34
|
+
!!self.id
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
self.persisted? ? remote_update : remote_create
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy
|
42
|
+
get_scope.destroy(self.id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def payload
|
46
|
+
hash = self.to_hash
|
47
|
+
hash.delete('id') # This key is never required
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def remote_update
|
52
|
+
get_scope.update(self.id, payload)
|
53
|
+
end
|
54
|
+
|
55
|
+
def remote_create
|
56
|
+
get_scope.create(payload)
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_scope
|
60
|
+
original_scope || self.class
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ApiClient
|
2
|
+
module Resource
|
3
|
+
class NameResolver
|
4
|
+
def self.resolve(ruby_path)
|
5
|
+
new(ruby_path).resolve
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve
|
15
|
+
select_last_item
|
16
|
+
underscorize
|
17
|
+
lowercase
|
18
|
+
name
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def select_last_item
|
23
|
+
@name = @name.split('::').last
|
24
|
+
end
|
25
|
+
|
26
|
+
#Inspired by ActiveSupport::Inflector#underscore
|
27
|
+
def underscorize
|
28
|
+
@name.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
29
|
+
@name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
30
|
+
end
|
31
|
+
|
32
|
+
def lowercase
|
33
|
+
@name.downcase!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# This class includes methods for calling restful APIs
|
2
|
+
module ApiClient
|
3
|
+
|
4
|
+
module Resource
|
5
|
+
|
6
|
+
class Scope < ApiClient::Scope
|
7
|
+
|
8
|
+
dsl_accessor :path, :return_self => true
|
9
|
+
|
10
|
+
def format
|
11
|
+
@scopeable.format
|
12
|
+
end
|
13
|
+
|
14
|
+
def append_format(path)
|
15
|
+
format ? [path, format].join('.') : path
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(id)
|
19
|
+
path = [@path, id].join('/')
|
20
|
+
path = append_format(path)
|
21
|
+
response = get(path)
|
22
|
+
scoped(self) do
|
23
|
+
raw? ? response : @scopeable.build(response)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_all(params = {})
|
28
|
+
path = append_format(@path)
|
29
|
+
response = get(path, params)
|
30
|
+
scoped(self) do
|
31
|
+
raw? ? response : @scopeable.build(response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create(params = {})
|
36
|
+
path = append_format(@path)
|
37
|
+
hash = if @scopeable.namespace
|
38
|
+
{ @scopeable.namespace => params }
|
39
|
+
else
|
40
|
+
params
|
41
|
+
end
|
42
|
+
response = post(path, hash)
|
43
|
+
scoped(self) do
|
44
|
+
raw? ? response : @scopeable.build(response)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(id, params = {})
|
49
|
+
path = [@path, id].join('/')
|
50
|
+
path = append_format(path)
|
51
|
+
hash = if @scopeable.namespace
|
52
|
+
{ @scopeable.namespace => params }
|
53
|
+
else
|
54
|
+
params
|
55
|
+
end
|
56
|
+
response = put(path, hash)
|
57
|
+
scoped(self) do
|
58
|
+
raw? ? response : @scopeable.build(response)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy(id)
|
63
|
+
path = [@path, id].join('/')
|
64
|
+
path = append_format(path)
|
65
|
+
delete(path)
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
class Scope
|
4
|
+
extend ApiClient::Mixins::Configuration
|
5
|
+
extend ApiClient::Mixins::Delegation
|
6
|
+
|
7
|
+
delegate :prefix, :scoped, :to => :scopeable
|
8
|
+
|
9
|
+
dsl_accessor :endpoint, :adapter, :return_self => true
|
10
|
+
|
11
|
+
attr_reader :scopeable
|
12
|
+
|
13
|
+
def initialize(scopeable)
|
14
|
+
@scopeable = scopeable
|
15
|
+
@params = {}
|
16
|
+
@headers = {}
|
17
|
+
@options = {}
|
18
|
+
@scopeable.default_scopes.each do |default_scope|
|
19
|
+
self.instance_eval(&default_scope)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection
|
24
|
+
klass = Connection.const_get((@adapter || Connection.default).to_s.capitalize)
|
25
|
+
@connection = klass.new(@endpoint , @options || {})
|
26
|
+
hooks = @scopeable.connection_hooks || []
|
27
|
+
hooks.each { |hook| hook.call(@connection, self) }
|
28
|
+
@connection
|
29
|
+
end
|
30
|
+
|
31
|
+
def raw
|
32
|
+
@raw = true
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def raw?
|
37
|
+
!!@raw
|
38
|
+
end
|
39
|
+
|
40
|
+
# 3 Pillars of scoping
|
41
|
+
# options - passed on the the adapter
|
42
|
+
# params - converted to query or request body
|
43
|
+
# headers - passed on to the request
|
44
|
+
def options(new_options = nil)
|
45
|
+
return @options if new_options.nil?
|
46
|
+
ApiClient::Utils.deep_merge(@options, new_options)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def params(options = nil)
|
51
|
+
return @params if options.nil?
|
52
|
+
ApiClient::Utils.deep_merge(@params, options) if options
|
53
|
+
self
|
54
|
+
end
|
55
|
+
alias :scope :params
|
56
|
+
|
57
|
+
def headers(options = nil)
|
58
|
+
return @headers if options.nil?
|
59
|
+
ApiClient::Utils.deep_merge(@headers, options) if options
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def raw_body(options = nil)
|
64
|
+
return @raw_body if options.nil?
|
65
|
+
@raw_body = options
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def clone_only_headers
|
70
|
+
self.class.new(self.scopeable).headers(self.headers)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Half-level :)
|
74
|
+
# This is a swiss-army knife kind of method, extremely useful
|
75
|
+
def fetch(path, options = {})
|
76
|
+
scoped(self) do
|
77
|
+
@scopeable.build get(path, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Low-level connection methods
|
82
|
+
|
83
|
+
def request(method, path, options = {})
|
84
|
+
options = options.dup
|
85
|
+
|
86
|
+
raw = raw? || options.delete(:raw)
|
87
|
+
params(options)
|
88
|
+
response = connection.send method, path, (@raw_body || @params), @headers
|
89
|
+
raw ? response : @scopeable.parse(response)
|
90
|
+
end
|
91
|
+
|
92
|
+
def get(path, options = {})
|
93
|
+
request(:get, path, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def post(path, options = {})
|
97
|
+
request(:post, path, options)
|
98
|
+
end
|
99
|
+
|
100
|
+
def patch(path, options = {})
|
101
|
+
request(:patch, path, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
def put(path, options = {})
|
105
|
+
request(:put, path, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def delete(path, options = {})
|
109
|
+
request(:delete, path, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Dynamic delegation of scopeable methods
|
113
|
+
def method_missing(method, *args, &block)
|
114
|
+
if @scopeable.respond_to?(method)
|
115
|
+
@scopeable.scoped(self) do
|
116
|
+
@scopeable.send(method, *args, &block)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
def self.deep_merge(hash, other_hash)
|
6
|
+
other_hash.each_pair do |key,v|
|
7
|
+
if hash[key].is_a?(::Hash) and v.is_a?(::Hash)
|
8
|
+
deep_merge hash[key], v
|
9
|
+
else
|
10
|
+
hash[key] = v
|
11
|
+
end
|
12
|
+
end
|
13
|
+
hash
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Base do
|
4
|
+
|
5
|
+
describe '.connection' do
|
6
|
+
|
7
|
+
it "registers a new connection_hook" do
|
8
|
+
ConnectionHookTestProc = lambda {}
|
9
|
+
class ConnectionHookTest < ApiClient::Base
|
10
|
+
connection &ConnectionHookTestProc
|
11
|
+
end
|
12
|
+
ConnectionHookTest.connection_hooks.size.should == 1
|
13
|
+
ConnectionHookTest.connection_hooks.should == [ConnectionHookTestProc]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Base do
|
4
|
+
|
5
|
+
it "delegates methods to scope" do
|
6
|
+
scope = double
|
7
|
+
ApiClient::Base.stub(:scope).and_return(scope)
|
8
|
+
[:fetch, :get, :put, :post, :patch, :delete, :headers, :endpoint, :options, :adapter, :params, :raw].each do |method|
|
9
|
+
scope.should_receive(method)
|
10
|
+
ApiClient::Base.send(method)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Base do
|
4
|
+
|
5
|
+
describe "subclasses" do
|
6
|
+
|
7
|
+
it "inherit scopes, hooks, namespace and format" do
|
8
|
+
|
9
|
+
class Level1InheritanceTest < ApiClient::Base
|
10
|
+
end
|
11
|
+
|
12
|
+
Level1InheritanceTest.default_scopes.should == []
|
13
|
+
Level1InheritanceTest.connection_hooks.should == []
|
14
|
+
Level1InheritanceTest.namespace.should == nil
|
15
|
+
Level1InheritanceTest.format.should == :json
|
16
|
+
|
17
|
+
Level1InheritanceTest.default_scopes = ['scope1']
|
18
|
+
Level1InheritanceTest.connection_hooks = ['hook1']
|
19
|
+
Level1InheritanceTest.namespace 'level1'
|
20
|
+
Level1InheritanceTest.format :xml
|
21
|
+
|
22
|
+
ApiClient::Base.default_scopes.should == []
|
23
|
+
ApiClient::Base.connection_hooks.should == []
|
24
|
+
ApiClient::Base.namespace.should == nil
|
25
|
+
ApiClient::Base.format.should == :json
|
26
|
+
|
27
|
+
class Level2InheritanceTest < Level1InheritanceTest
|
28
|
+
namespace "level2"
|
29
|
+
format :yaml
|
30
|
+
end
|
31
|
+
|
32
|
+
Level2InheritanceTest.default_scopes.should == ['scope1']
|
33
|
+
Level2InheritanceTest.connection_hooks.should == ['hook1']
|
34
|
+
Level2InheritanceTest.namespace.should == 'level2'
|
35
|
+
Level2InheritanceTest.format.should == :yaml
|
36
|
+
|
37
|
+
Level1InheritanceTest.namespace.should == 'level1'
|
38
|
+
Level1InheritanceTest.format.should == :xml
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Base do
|
4
|
+
|
5
|
+
describe "build" do
|
6
|
+
|
7
|
+
it "instantiates an array of objects and returns an array if passed an array" do
|
8
|
+
result = ApiClient::Base.build [{ :id => 1 }, { :id => 2}]
|
9
|
+
result.should be_an_instance_of(Array)
|
10
|
+
result.first.should be_an_instance_of(ApiClient::Base)
|
11
|
+
result.last.should be_an_instance_of(ApiClient::Base)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "instantiates an object and returns an object if passed an object" do
|
15
|
+
result = ApiClient::Base.build({ :id => 1 })
|
16
|
+
result.should be_an_instance_of(ApiClient::Base)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "build_one" do
|
22
|
+
|
23
|
+
it "extracts the attributes from a namespace if a namespace is provided" do
|
24
|
+
ApiClient::Base.stub(:namespace).and_return("base")
|
25
|
+
result = ApiClient::Base.build({ "base" => { :id => 1 } })
|
26
|
+
result.should be_an_instance_of(ApiClient::Base)
|
27
|
+
result.keys.should == ['id']
|
28
|
+
result.id.should == 1
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "sub hashes" do
|
34
|
+
|
35
|
+
it "are Hashie::Mashes" do
|
36
|
+
result = ApiClient::Base.build({ :id => 1, :subhash => { :foo => 1 } })
|
37
|
+
result.subhash.should be_a(Hashie::Mash)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "original_scope" do
|
43
|
+
|
44
|
+
it "holds the original scope it was created from" do
|
45
|
+
scope = ApiClient::Base.params(:foo => 1).headers('token' => 'aaa').options("some" => "option")
|
46
|
+
|
47
|
+
instance = scope.build :key => 'value'
|
48
|
+
instance.original_scope.headers.should == {'token' => 'aaa'}
|
49
|
+
instance.original_scope.params.should be_empty
|
50
|
+
instance.original_scope.options.should be_empty
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Marshaling of ApiClient objects" do
|
4
|
+
ConnectionProc = Proc.new {}
|
5
|
+
AlwaysProc = Proc.new {}
|
6
|
+
|
7
|
+
class Entity < ApiClient::Base
|
8
|
+
connection &ConnectionProc
|
9
|
+
always &AlwaysProc
|
10
|
+
|
11
|
+
def mutated_state?
|
12
|
+
@state == "mutated"
|
13
|
+
end
|
14
|
+
|
15
|
+
def mutate_state!
|
16
|
+
@state = "mutated"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "is marshallable by default" do
|
21
|
+
scope = Entity.params(:foo => 1).headers("token" => "aaa").options("some" => "option")
|
22
|
+
entity = scope.build :key => "value"
|
23
|
+
|
24
|
+
entity.mutated_state?.should == false
|
25
|
+
entity.mutate_state!
|
26
|
+
entity.mutated_state?.should == true
|
27
|
+
|
28
|
+
reloaded = Marshal.load(Marshal.dump(entity))
|
29
|
+
|
30
|
+
reloaded.should == entity
|
31
|
+
reloaded.mutated_state?.should == true
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "multi_xml"
|
4
|
+
|
5
|
+
describe ApiClient::Base do
|
6
|
+
|
7
|
+
it "parses json if json is set as format" do
|
8
|
+
ApiClient::Base.stub(:format).and_return(:json)
|
9
|
+
parsed = ApiClient::Base.parse('{"a":"1"}')
|
10
|
+
parsed.should == {"a"=> "1"}
|
11
|
+
end
|
12
|
+
|
13
|
+
it "parses xml if xml is set as format" do
|
14
|
+
ApiClient::Base.stub(:format).and_return(:xml)
|
15
|
+
parsed = ApiClient::Base.parse('<a>1</a>')
|
16
|
+
parsed.should == {"a"=> "1"}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns the string if parser is not found" do
|
20
|
+
ApiClient::Base.stub(:format).and_return(:unknown)
|
21
|
+
parsed = ApiClient::Base.parse('a:1')
|
22
|
+
parsed.should == "a:1"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "extracts the body of a Faraday::Response if it is provided" do
|
26
|
+
response = Faraday::Response.new(:body => '{"a": "1"}')
|
27
|
+
ApiClient::Base.stub(:format).and_return(:json)
|
28
|
+
parsed = ApiClient::Base.parse(response)
|
29
|
+
parsed.should == {"a"=> "1"}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns nil if the response is 204" do
|
33
|
+
response = Faraday::Response.new(:body => nil, :status => 204)
|
34
|
+
ApiClient::Base.stub(:format).and_return(:json)
|
35
|
+
parsed = ApiClient::Base.parse(response)
|
36
|
+
parsed.should == nil
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Base do
|
4
|
+
|
5
|
+
describe '.always' do
|
6
|
+
|
7
|
+
it "registers a new default_scope" do
|
8
|
+
AlwaysTestProc = lambda {}
|
9
|
+
class AlwaysTest < ApiClient::Base
|
10
|
+
always &AlwaysTestProc
|
11
|
+
end
|
12
|
+
AlwaysTest.default_scopes.size.should == 1
|
13
|
+
AlwaysTest.default_scopes.should == [AlwaysTestProc]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.scope' do
|
19
|
+
|
20
|
+
it "returns a ApiClient::Scope instance" do
|
21
|
+
ApiClient::Base.scope.should be_an_instance_of(ApiClient::Scope)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.scope_thread_attribute_name' do
|
27
|
+
|
28
|
+
it "returns the key under which all .scoped calls should be stored" do
|
29
|
+
ApiClient::Base.scope_thread_attribute_name.should == "ApiClient::Base_scope"
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '.scoped' do
|
35
|
+
|
36
|
+
it "stores the scope in the thread context, attached to class name" do
|
37
|
+
mock_scope3 = double
|
38
|
+
ApiClient::Base.scoped(mock_scope3) do
|
39
|
+
Thread.new {
|
40
|
+
mock_scope2 = double
|
41
|
+
ApiClient::Base.scoped(mock_scope2) do
|
42
|
+
ApiClient::Base.scope.should == mock_scope2
|
43
|
+
Thread.current[ApiClient::Base.scope_thread_attribute_name].should == [mock_scope2]
|
44
|
+
end
|
45
|
+
}
|
46
|
+
ApiClient::Base.scope.should == mock_scope3
|
47
|
+
Thread.current[ApiClient::Base.scope_thread_attribute_name].should == [mock_scope3]
|
48
|
+
end
|
49
|
+
Thread.new {
|
50
|
+
mock_scope = double
|
51
|
+
ApiClient::Base.scoped(mock_scope) do
|
52
|
+
ApiClient::Base.scope.should == mock_scope
|
53
|
+
Thread.current[ApiClient::Base.scope_thread_attribute_name].should == [mock_scope]
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ApiClient::Base do
|
4
|
+
|
5
|
+
it "is a subclass of Hashie::Mash" do
|
6
|
+
ApiClient::Base.should inherit_from(Hashie::Mash)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "responds to #id" do
|
10
|
+
subject.should respond_to("id")
|
11
|
+
end
|
12
|
+
|
13
|
+
class StrictApi < ApiClient::Base
|
14
|
+
def accessor_of_x
|
15
|
+
self.x
|
16
|
+
end
|
17
|
+
|
18
|
+
def strict_attr_reader?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class StrictDescendant < StrictApi
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when used in an array" do
|
27
|
+
|
28
|
+
subject { [StrictApi.new] }
|
29
|
+
|
30
|
+
it "does not break Array#flatten" do
|
31
|
+
lambda { subject.flatten }.should_not raise_error
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#inspect" do
|
37
|
+
|
38
|
+
it "has a nice inspect" do
|
39
|
+
subject.update(:id => 1).inspect.should == '#<ApiClient::Base id: 1>'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "presents all fields in inspect" do
|
43
|
+
subject.update(:id => 1, :foo => 'OMG')
|
44
|
+
subject.inspect.should == '#<ApiClient::Base id: 1, foo: "OMG">'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "inspects subobjects properly" do
|
48
|
+
subject.update(:id => 1, :sub => [1,2])
|
49
|
+
subject.inspect.should == '#<ApiClient::Base id: 1, sub: #<Hashie::Array [1, 2]>>'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "makes sure id is the first key" do
|
53
|
+
|
54
|
+
subject.update(:foo => 'OMG', :id => 1)
|
55
|
+
subject.inspect.should == '#<ApiClient::Base id: 1, foo: "OMG">'
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "strict_read" do
|
61
|
+
it "fails if the key is missing and strict_read is set" do
|
62
|
+
api = StrictApi.new
|
63
|
+
lambda { api.missing }.should raise_error do |error|
|
64
|
+
error_type = error.class.to_s
|
65
|
+
error_type.should match(/KeyError|IndexError/)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "doesn't fail if strict_read is not set" do
|
70
|
+
api = ApiClient::Base.new
|
71
|
+
api.missing
|
72
|
+
end
|
73
|
+
|
74
|
+
it "doesn't fail if the key was set after object was created" do
|
75
|
+
api = StrictApi.new
|
76
|
+
lambda { api.not_missing = 1 }.should_not raise_error
|
77
|
+
api.not_missing.should == 1
|
78
|
+
end
|
79
|
+
|
80
|
+
it "doesn't fail for predicate methods if key is not set" do
|
81
|
+
api = StrictApi.new
|
82
|
+
lambda { api.missing? }.should_not raise_error
|
83
|
+
api.missing?.should be_false
|
84
|
+
end
|
85
|
+
|
86
|
+
it "allows to call methods" do
|
87
|
+
api = StrictApi.new(:x => 14)
|
88
|
+
api.accessor_of_x.should == 14
|
89
|
+
end
|
90
|
+
|
91
|
+
it "calling method which asks for missing attribute fails" do
|
92
|
+
api = StrictApi.new
|
93
|
+
lambda { api.accessor_of_x }.should raise_error do |error|
|
94
|
+
error_type = error.class.to_s
|
95
|
+
error_type.should match(/KeyError|IndexError/)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "passes strict api configuration to subclasses" do
|
100
|
+
api = StrictDescendant.new
|
101
|
+
lambda { api.missing }.should raise_error do |error|
|
102
|
+
error_type = error.class.to_s
|
103
|
+
error_type.should match(/KeyError|IndexError/)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|