arrest 0.0.4 → 0.0.5
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.
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/arrest.gemspec +8 -0
- data/lib/arrest.rb +2 -0
- data/lib/arrest/abstract_resource.rb +91 -20
- data/lib/arrest/exceptions.rb +7 -0
- data/lib/arrest/helper/child_collection.rb +40 -0
- data/lib/arrest/http_source.rb +17 -7
- data/lib/arrest/mem_source.rb +90 -27
- data/lib/arrest/rest_child.rb +29 -4
- data/lib/arrest/root_resource.rb +38 -4
- data/lib/arrest/source.rb +2 -0
- data/lib/arrest/version.rb +1 -1
- data/spec/arrest_spec.rb +159 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/models/user.rb +4 -0
- data/test/models.rb +32 -0
- data/test/unit.rb +86 -25
- metadata +91 -8
data/.gitignore
CHANGED
data/.rspec
ADDED
data/arrest.gemspec
CHANGED
@@ -22,4 +22,12 @@ Gem::Specification.new do |s|
|
|
22
22
|
# s.add_development_dependency "rspec"
|
23
23
|
s.add_runtime_dependency "json"
|
24
24
|
s.add_runtime_dependency "faraday", '0.7.5'
|
25
|
+
|
26
|
+
s.add_development_dependency 'bundler', '>= 1.0.0'
|
27
|
+
s.add_development_dependency 'rake'
|
28
|
+
s.add_development_dependency 'rdoc'
|
29
|
+
s.add_development_dependency 'rspec', '~> 2'
|
30
|
+
s.add_development_dependency 'rr'
|
31
|
+
s.add_development_dependency 'simplecov'
|
32
|
+
s.add_development_dependency 'rack'
|
25
33
|
end
|
data/lib/arrest.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'arrest/string_utils'
|
3
3
|
require 'time'
|
4
|
-
class Boolean
|
5
4
|
|
6
|
-
|
5
|
+
Scope = Struct.new(:name, :block)
|
6
|
+
|
7
|
+
class Boolean
|
8
|
+
# to have a boolean type for attributes
|
9
|
+
end
|
7
10
|
|
8
11
|
module Arrest
|
9
12
|
|
@@ -61,6 +64,8 @@ module Arrest
|
|
61
64
|
class << self
|
62
65
|
|
63
66
|
attr_accessor :fields
|
67
|
+
attr_reader :scopes
|
68
|
+
|
64
69
|
|
65
70
|
def source
|
66
71
|
Arrest::Source::source
|
@@ -68,10 +73,14 @@ module Arrest
|
|
68
73
|
|
69
74
|
def body_root response
|
70
75
|
if response == nil
|
71
|
-
|
76
|
+
raise Errors::DocumentNotFoundError
|
72
77
|
end
|
73
78
|
all = JSON.parse response
|
74
|
-
all["result"]
|
79
|
+
body = all["result"]
|
80
|
+
if body == nil
|
81
|
+
raise Errors::DocumentNotFoundError
|
82
|
+
end
|
83
|
+
body
|
75
84
|
end
|
76
85
|
|
77
86
|
def build hash
|
@@ -82,8 +91,16 @@ module Arrest
|
|
82
91
|
self.new underscored_hash
|
83
92
|
end
|
84
93
|
|
94
|
+
def custom_resource_name new_name
|
95
|
+
@custom_resource_name = new_name
|
96
|
+
end
|
97
|
+
|
85
98
|
def resource_name
|
86
|
-
|
99
|
+
if @custom_resource_name
|
100
|
+
@custom_resource_name
|
101
|
+
else
|
102
|
+
StringUtils.plural self.name.sub(/.*:/,'').downcase
|
103
|
+
end
|
87
104
|
end
|
88
105
|
|
89
106
|
def has_many(*args)
|
@@ -98,15 +115,20 @@ module Arrest
|
|
98
115
|
end
|
99
116
|
end
|
100
117
|
send :define_method, method_name do
|
101
|
-
|
118
|
+
if @child_collections == nil
|
119
|
+
@child_collections = {}
|
120
|
+
end
|
121
|
+
if @child_collections[method_name] == nil
|
122
|
+
@child_collections[method_name] = ChildCollection.new(self, (StringUtils.classify clazz_name))
|
123
|
+
end
|
124
|
+
|
125
|
+
@child_collections[method_name]
|
102
126
|
end
|
103
127
|
end
|
104
128
|
|
105
129
|
def parent(*args)
|
106
130
|
method_name = args[0].to_s.to_sym
|
107
|
-
|
108
|
-
self.parent
|
109
|
-
end
|
131
|
+
class_eval "def #{method_name}; self.parent; end"
|
110
132
|
end
|
111
133
|
|
112
134
|
def add_attribute attribute
|
@@ -116,11 +138,20 @@ module Arrest
|
|
116
138
|
@fields << attribute
|
117
139
|
end
|
118
140
|
|
141
|
+
def scope name, &block
|
142
|
+
if @scopes == nil
|
143
|
+
@scopes = []
|
144
|
+
end
|
145
|
+
@scopes << Scope.new(name, &block)
|
146
|
+
end
|
147
|
+
|
119
148
|
def all_fields
|
149
|
+
self_fields = self.fields
|
150
|
+
self_fields ||= []
|
120
151
|
if self.superclass.respond_to?('fields') && self.superclass.fields != nil
|
121
|
-
|
152
|
+
self_fields + self.superclass.fields
|
122
153
|
else
|
123
|
-
|
154
|
+
self_fields
|
124
155
|
end
|
125
156
|
|
126
157
|
end
|
@@ -132,10 +163,22 @@ module Arrest
|
|
132
163
|
end
|
133
164
|
end
|
134
165
|
|
166
|
+
def attribute name, clazz
|
167
|
+
add_attribute Attribute.new(name, false, clazz)
|
168
|
+
|
169
|
+
send :define_method, "#{name}=" do |v|
|
170
|
+
self.unstub
|
171
|
+
self.instance_variable_set("@#{name}", v)
|
172
|
+
end
|
173
|
+
send :define_method, "#{name}" do
|
174
|
+
self.unstub
|
175
|
+
self.instance_variable_get("@#{name}")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
135
179
|
def attributes(args)
|
136
180
|
args.each_pair do |name, clazz|
|
137
|
-
self.
|
138
|
-
add_attribute Attribute.new(name, false, clazz)
|
181
|
+
self.attribute name, clazz
|
139
182
|
end
|
140
183
|
end
|
141
184
|
|
@@ -148,11 +191,26 @@ module Arrest
|
|
148
191
|
Arrest::Source.mod.const_get(StringUtils.classify name).find(val)
|
149
192
|
end
|
150
193
|
end
|
194
|
+
|
151
195
|
end
|
196
|
+
self.fields = []
|
152
197
|
|
153
198
|
attr_accessor :id
|
199
|
+
attr_reader :stub
|
200
|
+
|
201
|
+
def self.stub id
|
202
|
+
self.new({:id => id}, true)
|
203
|
+
end
|
154
204
|
|
155
|
-
def initialize
|
205
|
+
def initialize hash={},stubbed=false
|
206
|
+
@stub = stubbed
|
207
|
+
init_from_hash(hash) unless stubbed
|
208
|
+
self.id = hash[:id]
|
209
|
+
self.id ||= hash['id']
|
210
|
+
end
|
211
|
+
|
212
|
+
def init_from_hash as_i={}
|
213
|
+
@stub = false
|
156
214
|
as = {}
|
157
215
|
as_i.each_pair do |k,v|
|
158
216
|
as[k.to_sym] = v
|
@@ -172,8 +230,8 @@ module Arrest
|
|
172
230
|
self.send("#{field.name.to_s}=", converter.convert(value))
|
173
231
|
end
|
174
232
|
end
|
175
|
-
self.id = as[:id]
|
176
233
|
end
|
234
|
+
|
177
235
|
|
178
236
|
def to_hash
|
179
237
|
result = {}
|
@@ -189,16 +247,29 @@ module Arrest
|
|
189
247
|
end
|
190
248
|
|
191
249
|
def save
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
250
|
+
verb = new_record? ? :post : :put
|
251
|
+
!!AbstractResource::source.send(verb, self)
|
252
|
+
end
|
253
|
+
|
254
|
+
def new_record?
|
255
|
+
[nil, ''].include?(id)
|
197
256
|
end
|
198
257
|
|
199
258
|
def delete
|
200
259
|
AbstractResource::source().delete self
|
260
|
+
true
|
201
261
|
end
|
262
|
+
#
|
263
|
+
# convenicence method printing curl command
|
264
|
+
def curl
|
265
|
+
hs = ""
|
266
|
+
Arrest::Source.header_decorator.headers.each_pair do |k,v|
|
267
|
+
hs << "-H '#{k}:#{v}'"
|
268
|
+
end
|
269
|
+
|
270
|
+
"curl #{hs} -v '#{Arrest::Source.source.url}/#{self.resource_location}'"
|
271
|
+
end
|
272
|
+
|
202
273
|
|
203
274
|
end
|
204
275
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Arrest
|
2
|
+
class ChildCollection < BasicObject
|
3
|
+
def initialize parentX, clazz_name
|
4
|
+
@parent_clazz = parentX
|
5
|
+
@clazz_name = clazz_name
|
6
|
+
@children = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def build attributes = {}
|
10
|
+
resolved_class.new @parent_clazz, attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(*args, &block)
|
14
|
+
if resolved_class.respond_to?(args[0])
|
15
|
+
resolved_class.send(args[0], @parent_clazz)
|
16
|
+
else
|
17
|
+
children.send(*args, &block)
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
|
25
|
+
def children
|
26
|
+
if @children == nil
|
27
|
+
@children = resolved_class.all_for @parent_clazz
|
28
|
+
end
|
29
|
+
@children
|
30
|
+
end
|
31
|
+
|
32
|
+
def resolved_class
|
33
|
+
if @clazz == nil
|
34
|
+
@clazz = Source.mod.const_get(@clazz_name)
|
35
|
+
end
|
36
|
+
@clazz
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/arrest/http_source.rb
CHANGED
@@ -7,24 +7,35 @@ module Arrest
|
|
7
7
|
@base = base
|
8
8
|
end
|
9
9
|
|
10
|
+
def url
|
11
|
+
@base
|
12
|
+
end
|
13
|
+
|
10
14
|
def add_headers headers
|
11
15
|
Arrest::Source.header_decorator.headers.each_pair do |k,v|
|
12
16
|
headers[k.to_s] = v.to_s
|
13
17
|
end
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
20
|
+
def get_one sub, filter={}
|
17
21
|
response = self.connection().get do |req|
|
18
|
-
req.url sub
|
22
|
+
req.url sub, filter
|
19
23
|
add_headers req.headers
|
20
24
|
end
|
25
|
+
if response.env[:status] != 200
|
26
|
+
raise Errors::DocumentNotFoundError
|
27
|
+
end
|
21
28
|
response.body
|
22
29
|
end
|
23
30
|
|
31
|
+
def get_many sub, filter={}
|
32
|
+
get_one sub, filter
|
33
|
+
end
|
34
|
+
|
24
35
|
def delete rest_resource
|
25
36
|
raise "To delete an object it must have an id" unless rest_resource.respond_to?(:id) && rest_resource.id != nil
|
26
37
|
response = self.connection().delete do |req|
|
27
|
-
req.url rest_resource.
|
38
|
+
req.url rest_resource.resource_location
|
28
39
|
add_headers req.headers
|
29
40
|
end
|
30
41
|
response.env[:status] == 200
|
@@ -37,7 +48,7 @@ module Arrest
|
|
37
48
|
hash.delete("id")
|
38
49
|
|
39
50
|
response = self.connection().put do |req|
|
40
|
-
req.url rest_resource.
|
51
|
+
req.url rest_resource.resource_location
|
41
52
|
add_headers req.headers
|
42
53
|
req.body = hash.to_json
|
43
54
|
end
|
@@ -53,11 +64,9 @@ module Arrest
|
|
53
64
|
|
54
65
|
hash.delete_if{|k,v| v == nil}
|
55
66
|
|
56
|
-
puts "URL:#{rest_resource.class.resource_path}"
|
57
67
|
body = hash.to_json
|
58
|
-
puts "Body:#{body}"
|
59
68
|
response = self.connection().post do |req|
|
60
|
-
req.url rest_resource.
|
69
|
+
req.url rest_resource.resource_path
|
61
70
|
add_headers req.headers
|
62
71
|
req.body = body
|
63
72
|
end
|
@@ -65,6 +74,7 @@ module Arrest
|
|
65
74
|
location = response.env[:response_headers][:location]
|
66
75
|
id = location.gsub(/^.*\//, '')
|
67
76
|
rest_resource.id= id
|
77
|
+
true
|
68
78
|
else
|
69
79
|
puts "unable to create: #{response.env[:response_headers]} body: #{response.body} "
|
70
80
|
false
|
data/lib/arrest/mem_source.rb
CHANGED
@@ -2,14 +2,48 @@ module Arrest
|
|
2
2
|
class MemSource
|
3
3
|
|
4
4
|
attr_accessor :data
|
5
|
+
|
6
|
+
|
7
|
+
@@all_objects = {} # holds all objects of all types,
|
8
|
+
# each having a unique id
|
9
|
+
|
10
|
+
@@collections = {} # maps urls to collections of ids of objects
|
5
11
|
|
12
|
+
|
6
13
|
@@data = {}
|
7
14
|
|
15
|
+
def objects
|
16
|
+
@@all_objects
|
17
|
+
end
|
18
|
+
|
19
|
+
def collections
|
20
|
+
@@collections
|
21
|
+
end
|
22
|
+
|
8
23
|
def data
|
9
24
|
@@data
|
10
25
|
end
|
11
26
|
|
12
27
|
def initialize
|
28
|
+
@@all_objects = {} # holds all objects of all types,
|
29
|
+
|
30
|
+
@@collections = {} # maps urls to collections of ids of objects
|
31
|
+
@@random = Random.new(42)
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug s
|
36
|
+
if Arrest::Source.debug
|
37
|
+
puts s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# only to stub collection for development
|
42
|
+
#
|
43
|
+
def set_collection clazz, scope, objects
|
44
|
+
url = clazz.scoped_path scope
|
45
|
+
self.class.debug "url:#{url}"
|
46
|
+
@@data[url] = objects
|
13
47
|
end
|
14
48
|
|
15
49
|
def wrap content,count
|
@@ -20,22 +54,44 @@ module Arrest
|
|
20
54
|
|
21
55
|
end
|
22
56
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
ps =
|
27
|
-
else
|
28
|
-
ps = [sub]
|
57
|
+
def hash_to_query filters
|
58
|
+
ps = []
|
59
|
+
filters.each_pair do |k,v|
|
60
|
+
ps << "#{k}=v"
|
29
61
|
end
|
30
|
-
|
31
|
-
|
32
|
-
wrap collection_json(val.values), val.length
|
33
|
-
elsif val == nil
|
34
|
-
wrap "{}", 0
|
62
|
+
if ps.empty?
|
63
|
+
''
|
35
64
|
else
|
36
|
-
|
65
|
+
'?' + ps.join('&')
|
37
66
|
end
|
38
67
|
end
|
68
|
+
|
69
|
+
def get_many sub, filters = {}
|
70
|
+
debug sub + (hash_to_query filters)
|
71
|
+
# filters are ignored by mem impl so far
|
72
|
+
|
73
|
+
id_list = @@collections[sub] || []
|
74
|
+
objects = id_list.map do |id|
|
75
|
+
@@all_objects[id]
|
76
|
+
end
|
77
|
+
|
78
|
+
wrap collection_json(objects), id_list.length
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_one sub, filters = {}
|
83
|
+
debug sub + (hash_to_query filters)
|
84
|
+
# filters are ignored by mem impl so far
|
85
|
+
idx = sub.rindex '/'
|
86
|
+
if idx
|
87
|
+
id = sub[(idx+1)..sub.length]
|
88
|
+
end
|
89
|
+
val = @@all_objects[id]
|
90
|
+
if val == nil
|
91
|
+
raise Errors::DocumentNotFoundError
|
92
|
+
end
|
93
|
+
wrap val.to_hash.to_json, 1
|
94
|
+
end
|
39
95
|
|
40
96
|
def collection_json values
|
41
97
|
single_jsons = values.map do |v|
|
@@ -59,35 +115,42 @@ module Arrest
|
|
59
115
|
|
60
116
|
def delete rest_resource
|
61
117
|
raise "To change an object it must have an id" unless rest_resource.respond_to?(:id) && rest_resource.id != nil
|
62
|
-
@@
|
118
|
+
@@all_objects.delete(rest_resource.id)
|
119
|
+
@@collections.each_pair do |k,v|
|
120
|
+
v.reject!{ |id| id == rest_resource.id }
|
121
|
+
end
|
63
122
|
rest_resource
|
64
123
|
end
|
65
124
|
|
66
125
|
def put rest_resource
|
67
126
|
raise "To change an object it must have an id" unless rest_resource.respond_to?(:id) && rest_resource.id != nil
|
68
|
-
@@
|
69
|
-
|
127
|
+
old = @@all_objects[rest_resource.id]
|
128
|
+
|
129
|
+
rest_resource.class.all_fields.each do |f|
|
130
|
+
old.send("#{f.name}=", rest_resource.send(f.name))
|
131
|
+
end
|
132
|
+
true
|
70
133
|
end
|
71
134
|
|
72
135
|
def post rest_resource
|
73
136
|
raise "new object must have setter for id" unless rest_resource.respond_to?(:id=)
|
74
137
|
raise "new object must not have id" if rest_resource.respond_to?(:id) && rest_resource.id != nil
|
75
|
-
if @@data[rest_resource.resource_path()] != nil
|
76
|
-
last_id = @@data[rest_resource.resource_path()].values.map(&:id).sort.last
|
77
|
-
else
|
78
|
-
last_id = 42
|
79
|
-
end
|
80
|
-
if last_id.is_a?(Integer)
|
81
|
-
next_id = last_id + 1
|
82
|
-
else
|
83
|
-
next_id = "#{last_id}x"
|
84
|
-
end
|
85
138
|
rest_resource.id = next_id
|
139
|
+
@@all_objects[rest_resource.id] = rest_resource
|
86
140
|
unless @@data[rest_resource.resource_path()] != nil
|
87
141
|
@@data[rest_resource.resource_path()] = {}
|
88
142
|
end
|
89
|
-
|
90
|
-
next_id
|
143
|
+
debug "child path #{rest_resource.resource_path()}"
|
144
|
+
@@data[rest_resource.resource_path()][next_id.to_s] = rest_resource.id
|
145
|
+
if @@collections[rest_resource.resource_path] == nil
|
146
|
+
@@collections[rest_resource.resource_path] = []
|
147
|
+
end
|
148
|
+
@@collections[rest_resource.resource_path] << rest_resource.id
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
def next_id
|
153
|
+
(0...32).map{ ('a'..'z').to_a[@@random.rand(26)] }.join
|
91
154
|
end
|
92
155
|
end
|
93
156
|
end
|
data/lib/arrest/rest_child.rb
CHANGED
@@ -9,7 +9,11 @@ module Arrest
|
|
9
9
|
class << self
|
10
10
|
# class method to generate path to a child resource of aonther resource
|
11
11
|
def resource_path_for parent
|
12
|
-
"#{parent.
|
12
|
+
"#{parent.resource_location}/#{self.resource_name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def scoped_path_for parent, scope_name
|
16
|
+
(resource_path_for parent) + '/' + scope_name.to_s
|
13
17
|
end
|
14
18
|
|
15
19
|
def build parent, hash
|
@@ -19,17 +23,33 @@ module Arrest
|
|
19
23
|
|
20
24
|
def all_for parent
|
21
25
|
raise "Parent has no id yet" unless parent.id
|
22
|
-
body_root(source().
|
26
|
+
body_root(source().get_many self.resource_path_for(parent)).map do |h|
|
23
27
|
self.build(parent, h)
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
27
31
|
def find_for parent,id
|
28
|
-
r = source().
|
32
|
+
r = source().get_one "#{self.resource_path_for(parent)}/#{id}"
|
29
33
|
body = body_root(r)
|
30
34
|
self.build body
|
31
35
|
end
|
32
36
|
|
37
|
+
def scope name, &block
|
38
|
+
super name
|
39
|
+
if block_given?
|
40
|
+
send :define_singleton_method, name do |parent|
|
41
|
+
self.all_for(parent).select &block
|
42
|
+
end
|
43
|
+
else
|
44
|
+
send :define_singleton_method, name do |parent|
|
45
|
+
raise "Parent has no id yet" unless parent.id
|
46
|
+
body_root(source().get_many self.scoped_path_for(parent, name)).map do |h|
|
47
|
+
self.build(parent, h)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
33
53
|
end
|
34
54
|
|
35
55
|
# instance method to generate path to a child resource of another resource
|
@@ -38,10 +58,15 @@ module Arrest
|
|
38
58
|
end
|
39
59
|
|
40
60
|
# unique url for one instance of this class
|
41
|
-
def
|
61
|
+
def resource_location
|
42
62
|
"#{self.class.resource_path}/#{self.id.to_s}"
|
43
63
|
end
|
44
64
|
|
65
|
+
def unstub
|
66
|
+
return unless @stub
|
67
|
+
raise "stubbing for child resource isnt supported yet"
|
68
|
+
end
|
69
|
+
|
45
70
|
|
46
71
|
end
|
47
72
|
end
|
data/lib/arrest/root_resource.rb
CHANGED
@@ -7,8 +7,8 @@ module Arrest
|
|
7
7
|
"#{self.resource_name}"
|
8
8
|
end
|
9
9
|
|
10
|
-
def all
|
11
|
-
body = body_root(source().
|
10
|
+
def all filter={}
|
11
|
+
body = body_root(source().get_many self.resource_path, filter)
|
12
12
|
body ||= []
|
13
13
|
body.map do |h|
|
14
14
|
self.build h
|
@@ -16,21 +16,55 @@ module Arrest
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def find id
|
19
|
-
r = source().
|
19
|
+
r = source().get_one "#{self.resource_path}/#{id}"
|
20
20
|
body = body_root(r)
|
21
|
+
if body == nil || body.empty?
|
22
|
+
raise Errors::DocumentNotFoundError.new
|
23
|
+
end
|
21
24
|
self.build body
|
22
25
|
end
|
23
26
|
|
27
|
+
def scope name, &block
|
28
|
+
super(name)
|
29
|
+
if block_given?
|
30
|
+
send :define_singleton_method, name do
|
31
|
+
self.all.select &block
|
32
|
+
end
|
33
|
+
else
|
34
|
+
send :define_singleton_method, name do
|
35
|
+
body_root(source().get_many self.scoped_path(name)).map do |h|
|
36
|
+
self.build(h)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def scoped_path scope_name
|
44
|
+
resource_path + '/' + scope_name.to_s
|
45
|
+
end
|
24
46
|
end
|
25
47
|
|
26
48
|
def resource_path
|
27
49
|
"#{self.class.resource_name}"
|
28
50
|
end
|
29
51
|
|
30
|
-
def
|
52
|
+
def resource_location
|
31
53
|
self.class.resource_path + '/' + self.id.to_s
|
32
54
|
end
|
33
55
|
|
56
|
+
|
57
|
+
def unstub
|
58
|
+
return unless @stub
|
59
|
+
r = self.class.source().get_one "#{self.resource_path}/#{id}"
|
60
|
+
body = self.class.body_root(r)
|
61
|
+
underscored_hash = {}
|
62
|
+
body.each_pair do |k, v|
|
63
|
+
underscored_hash[StringUtils.underscore k] = v
|
64
|
+
end
|
65
|
+
init_from_hash underscored_hash
|
66
|
+
end
|
67
|
+
|
34
68
|
end
|
35
69
|
end
|
36
70
|
|
data/lib/arrest/source.rb
CHANGED
@@ -2,6 +2,7 @@ module Arrest
|
|
2
2
|
|
3
3
|
class Source
|
4
4
|
class << self
|
5
|
+
attr_accessor :debug
|
5
6
|
attr_reader :source
|
6
7
|
attr_reader :mod
|
7
8
|
attr_reader :header_decorator
|
@@ -44,4 +45,5 @@ module Arrest
|
|
44
45
|
end
|
45
46
|
Source.mod = nil
|
46
47
|
Source.header_decorator = Source
|
48
|
+
Source.debug = false
|
47
49
|
end
|
data/lib/arrest/version.rb
CHANGED
data/spec/arrest_spec.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Arrest do
|
4
|
+
let(:user) {User.new(:email => 'hans@wurst.de', :password => 'tester')}
|
5
|
+
|
6
|
+
describe 'validation' do
|
7
|
+
it 'should be implemented'
|
8
|
+
it 'should work'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#new_record?' do
|
12
|
+
it 'should return true if id is nil' do
|
13
|
+
user.id = nil
|
14
|
+
user.new_record?.should be_true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return true if id is empty' do
|
18
|
+
user.id = ''
|
19
|
+
user.new_record?.should be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should return false if id has been set' do
|
23
|
+
user.id = 'whatever'
|
24
|
+
user.new_record?.should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#save' do
|
29
|
+
it 'should return true on success' do
|
30
|
+
user.save.should eql(true)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return false on failure' do
|
34
|
+
mock(Arrest::Source.source).post(user) {false}
|
35
|
+
user.save.should eql(false)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should persist the object' do
|
39
|
+
mock(Arrest::Source.source).post(user)
|
40
|
+
user.save
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#update' do
|
45
|
+
it 'should raise an ArgumentError when called without options'
|
46
|
+
it 'should update a persisted object'
|
47
|
+
it 'should not call #save to persist the object'
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#destroy' do
|
51
|
+
it 'should destroy a persisted object'
|
52
|
+
it 'should raise an Arrest::Errors::DocumentNotPersistedError if the object was not persisted'
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '.new' do
|
56
|
+
context 'without options' do
|
57
|
+
let(:user) {User.new}
|
58
|
+
|
59
|
+
it 'should return a new object' do
|
60
|
+
user.should be_kind_of(User)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should not set undefined attributes' do
|
64
|
+
user.email.should be_nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with options' do
|
69
|
+
let(:user) {User.new(:email => 'hans@wurst.de')}
|
70
|
+
|
71
|
+
it 'should return a new object' do
|
72
|
+
user.should be_kind_of(User)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should set defined attributes' do
|
76
|
+
user.email.should eq('hans@wurst.de')
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should not set undefined attributes' do
|
80
|
+
user.password.should be_nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '.create' do
|
86
|
+
it 'should initialize and persist an object'
|
87
|
+
it 'should return the persisted object'
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '.destroy' do
|
91
|
+
it 'should raise an ArgumentError when called without options'
|
92
|
+
it 'should destroy a persisted object with given id'
|
93
|
+
it 'should call .find to get the destroyable object'
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '.find' do
|
97
|
+
let(:user) do
|
98
|
+
User.new(:email => 'hans@wurst.de', :password => 'tester').tap do |u|
|
99
|
+
u.save
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should return a persisted object' do
|
104
|
+
User.find(user.id).id.should eq(user.id)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should raise an Arrest::Errors::DocumentNotFoundError if an object cannot be found' do
|
108
|
+
expect {User.find('whatever')}.to raise_error(Arrest::Errors::DocumentNotFoundError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should raise an ArgumentError when called without options' do
|
112
|
+
expect {User.find}.to raise_error(ArgumentError)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '.all' do
|
117
|
+
let(:user) do
|
118
|
+
User.new(:email => 'hans@wurst.de', :password => 'tester').tap {|u| u.save}
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:another_user) do
|
122
|
+
User.new(:email => 'fritz@cola.de', :password => 'blub').tap {|u| u.save}
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'without options' do
|
126
|
+
context 'and without objects being persisted' do
|
127
|
+
before do
|
128
|
+
Arrest::Source.source = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should return an empty array' do
|
132
|
+
User.all.should eq([])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'and with objects being persisted' do
|
137
|
+
before do
|
138
|
+
Arrest::Source.source = nil
|
139
|
+
user
|
140
|
+
another_user
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should return all persisted objects' do
|
144
|
+
User.all.size.should eql(2)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should return objects of same type' do
|
148
|
+
User.all.each do |user|
|
149
|
+
user.should be_kind_of(User)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should return objects in order they have been persisted' do
|
154
|
+
User.all.map {|user| user.email}.should eq(['hans@wurst.de', 'fritz@cola.de'])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'rr'
|
6
|
+
require 'rack'
|
7
|
+
|
8
|
+
require 'arrest'
|
9
|
+
|
10
|
+
Dir["#{File.dirname(__FILE__)}/../spec/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
Arrest::Source.source = nil
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.mock_with :rr
|
16
|
+
config.before(:each) do
|
17
|
+
Arrest::MemSource.class_variable_set('@@data', {})
|
18
|
+
end
|
19
|
+
end
|
data/test/models.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'arrest'
|
2
|
+
|
3
|
+
class Zoo < Arrest::RootResource
|
4
|
+
attributes({ :name => String , :open => Boolean})
|
5
|
+
read_only_attributes({ :ro1 => String})
|
6
|
+
has_many :animals
|
7
|
+
|
8
|
+
scope :server_scope
|
9
|
+
scope(:open) { |z| z.open }
|
10
|
+
end
|
11
|
+
|
12
|
+
class Animal < Arrest::RestChild
|
13
|
+
attribute :kind, String
|
14
|
+
attribute :age, Integer
|
15
|
+
attribute :male, Boolean
|
16
|
+
|
17
|
+
parent :zoo
|
18
|
+
|
19
|
+
scope :server_males_only
|
20
|
+
scope(:males_only){|a| a.male}
|
21
|
+
end
|
22
|
+
|
23
|
+
class SpecialZoo < Zoo
|
24
|
+
custom_resource_name :zoo3000
|
25
|
+
read_only_attributes({ :ro2 => String})
|
26
|
+
attributes({
|
27
|
+
:is_magic => Boolean,
|
28
|
+
:opened_at => Time
|
29
|
+
})
|
30
|
+
|
31
|
+
end
|
32
|
+
|
data/test/unit.rb
CHANGED
@@ -1,33 +1,11 @@
|
|
1
|
-
require 'arrest'
|
2
1
|
require 'test/unit'
|
3
|
-
|
4
|
-
class Zoo < Arrest::RootResource
|
5
|
-
attributes({ :name => String })
|
6
|
-
read_only_attributes({ :ro1 => String})
|
7
|
-
has_many :animals
|
8
|
-
end
|
9
|
-
|
10
|
-
class Animal < Arrest::RestChild
|
11
|
-
attributes({
|
12
|
-
:kind => String,
|
13
|
-
:age => Integer
|
14
|
-
})
|
15
|
-
parent :zoo
|
16
|
-
end
|
17
|
-
|
18
|
-
class SpecialZoo < Zoo
|
19
|
-
read_only_attributes({ :ro2 => String})
|
20
|
-
attributes({
|
21
|
-
:is_magic => Boolean,
|
22
|
-
:opened_at => Time
|
23
|
-
})
|
24
|
-
|
25
|
-
end
|
2
|
+
load 'test/models.rb'
|
26
3
|
|
27
4
|
class FirstTest < Test::Unit::TestCase
|
28
5
|
|
29
6
|
def setup
|
30
7
|
Arrest::Source.source = nil
|
8
|
+
#Arrest::Source.debug = true
|
31
9
|
end
|
32
10
|
|
33
11
|
def test_mem_src
|
@@ -119,6 +97,7 @@ class FirstTest < Test::Unit::TestCase
|
|
119
97
|
|
120
98
|
|
121
99
|
assert_equal 1, zoo_reloaded.animals.length
|
100
|
+
assert_equal Animal, zoo_reloaded.animals.first.class
|
122
101
|
assert_equal 42, zoo_reloaded.animals.first.age
|
123
102
|
|
124
103
|
animal_reloaded = zoo_reloaded.animals.first
|
@@ -157,6 +136,8 @@ class FirstTest < Test::Unit::TestCase
|
|
157
136
|
end
|
158
137
|
|
159
138
|
def test_inheritance_update
|
139
|
+
assert_equal :zoo3000, SpecialZoo.resource_name
|
140
|
+
|
160
141
|
new_zoo = SpecialZoo.new({:name => "Foo", :is_magic => true})
|
161
142
|
new_zoo.save
|
162
143
|
|
@@ -167,12 +148,13 @@ class FirstTest < Test::Unit::TestCase
|
|
167
148
|
|
168
149
|
new_name = "Bar"
|
169
150
|
zoo_reloaded.name = new_name
|
151
|
+
old_is_magic = zoo_reloaded.is_magic
|
170
152
|
zoo_reloaded.is_magic = !zoo_reloaded.is_magic
|
171
153
|
zoo_reloaded.save
|
172
154
|
|
173
155
|
updated_zoo = SpecialZoo.find(zoo_reloaded.id)
|
174
156
|
assert_equal new_name, updated_zoo.name
|
175
|
-
assert_equal !
|
157
|
+
assert_equal !old_is_magic, updated_zoo.is_magic
|
176
158
|
end
|
177
159
|
|
178
160
|
def test_read_only_attributes
|
@@ -198,5 +180,84 @@ class FirstTest < Test::Unit::TestCase
|
|
198
180
|
assert_equal now, zoo.opened_at
|
199
181
|
|
200
182
|
end
|
183
|
+
|
184
|
+
def test_stub_delayed_load
|
185
|
+
new_zoo = Zoo.new({:name => "Foo"})
|
186
|
+
new_zoo.save
|
187
|
+
|
188
|
+
assert_not_nil new_zoo.id
|
189
|
+
|
190
|
+
stubbed = Zoo.stub(new_zoo.id)
|
191
|
+
assert stubbed.stub, "Zoo should be a stub, so not loaded yet"
|
192
|
+
new_name = stubbed.name
|
193
|
+
assert !stubbed.stub, "Zoo should not be a stub, so loaded now"
|
194
|
+
|
195
|
+
assert_equal "Foo", new_name
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_stub_not_load_for_child_access
|
199
|
+
new_zoo = Zoo.new({:name => "Foo"})
|
200
|
+
new_zoo.save
|
201
|
+
|
202
|
+
assert_not_nil new_zoo.id
|
203
|
+
|
204
|
+
# this is where the magic hapens
|
205
|
+
stubbed = Zoo.stub(new_zoo.id)
|
206
|
+
|
207
|
+
new_animal = Animal.new new_zoo, {:kind => "foo", :age => 42}
|
208
|
+
new_animal.save
|
209
|
+
|
210
|
+
assert stubbed.stub, "Zoo should be a stub, so not loaded yet"
|
211
|
+
|
212
|
+
animals = stubbed.animals
|
213
|
+
|
214
|
+
assert stubbed.stub, "Zoo should still be a stub, so not loaded yet"
|
215
|
+
assert_equal 1, animals.length
|
216
|
+
|
217
|
+
new_name = stubbed.name
|
218
|
+
assert !stubbed.stub, "Zoo should not be a stub, so loaded now"
|
219
|
+
|
220
|
+
assert_equal "Foo", new_name
|
221
|
+
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_root_scope
|
226
|
+
assert_not_nil Zoo.server_scope
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_child_scope
|
230
|
+
new_zoo = Zoo.new({:name => "Foo"})
|
231
|
+
new_zoo.save
|
232
|
+
|
233
|
+
assert_not_nil new_zoo.id
|
234
|
+
|
235
|
+
assert_not_nil new_zoo.animals.server_males_only
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_local_scope
|
239
|
+
|
240
|
+
zoo_false = Zoo.new({:name => "Foo", :open => false})
|
241
|
+
zoo_false.save
|
242
|
+
zoo_true = Zoo.new({:name => "Foo", :open => true})
|
243
|
+
zoo_true.save
|
244
|
+
|
245
|
+
assert_equal 1, Zoo.open.length
|
246
|
+
assert_equal true, Zoo.open.first.open
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_local_child_scope
|
250
|
+
new_zoo = Zoo.new({:name => "Foo"})
|
251
|
+
new_zoo.save
|
252
|
+
|
253
|
+
animal_kind = "mouse"
|
254
|
+
Animal.new(new_zoo, {:kind => animal_kind, :age => 42, :male => true}).save
|
255
|
+
Animal.new(new_zoo, {:kind => animal_kind, :age => 42, :male => false}).save
|
256
|
+
|
257
|
+
assert_equal 2, Zoo.all.first.animals.length
|
258
|
+
assert_equal 1, Zoo.all.first.animals.males_only.length
|
259
|
+
assert_equal true, Zoo.all.first.animals.males_only.first.male
|
260
|
+
|
261
|
+
end
|
201
262
|
end
|
202
263
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-11-
|
12
|
+
date: 2011-11-25 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
16
|
-
requirement: &
|
16
|
+
requirement: &10579480 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *10579480
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: faraday
|
27
|
-
requirement: &
|
27
|
+
requirement: &10578980 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - =
|
@@ -32,7 +32,84 @@ dependencies:
|
|
32
32
|
version: 0.7.5
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *10578980
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &10578480 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *10578480
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &10578100 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *10578100
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rdoc
|
60
|
+
requirement: &10577640 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *10577640
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: &10577140 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *10577140
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rr
|
82
|
+
requirement: &10576720 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *10576720
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: simplecov
|
93
|
+
requirement: &10576260 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *10576260
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: rack
|
104
|
+
requirement: &10575840 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *10575840
|
36
113
|
description: Consume a rest API in a AR like fashion
|
37
114
|
email:
|
38
115
|
- axel.tetzlaff@fortytools.com
|
@@ -41,12 +118,15 @@ extensions: []
|
|
41
118
|
extra_rdoc_files: []
|
42
119
|
files:
|
43
120
|
- .gitignore
|
121
|
+
- .rspec
|
44
122
|
- Gemfile
|
45
123
|
- README.md
|
46
124
|
- Rakefile
|
47
125
|
- arrest.gemspec
|
48
126
|
- lib/arrest.rb
|
49
127
|
- lib/arrest/abstract_resource.rb
|
128
|
+
- lib/arrest/exceptions.rb
|
129
|
+
- lib/arrest/helper/child_collection.rb
|
50
130
|
- lib/arrest/http_source.rb
|
51
131
|
- lib/arrest/mem_source.rb
|
52
132
|
- lib/arrest/rest_child.rb
|
@@ -54,6 +134,10 @@ files:
|
|
54
134
|
- lib/arrest/source.rb
|
55
135
|
- lib/arrest/string_utils.rb
|
56
136
|
- lib/arrest/version.rb
|
137
|
+
- spec/arrest_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
- spec/support/models/user.rb
|
140
|
+
- test/models.rb
|
57
141
|
- test/unit.rb
|
58
142
|
homepage: ''
|
59
143
|
licenses: []
|
@@ -79,5 +163,4 @@ rubygems_version: 1.8.10
|
|
79
163
|
signing_key:
|
80
164
|
specification_version: 3
|
81
165
|
summary: Another ruby rest client
|
82
|
-
test_files:
|
83
|
-
- test/unit.rb
|
166
|
+
test_files: []
|