arrest 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@
3
3
  Gemfile.lock
4
4
  pkg/*
5
5
  *.swp
6
+ coverage
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format nested
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,6 +1,8 @@
1
1
  require "arrest/version"
2
2
 
3
3
  require "arrest/source"
4
+ require "arrest/helper/child_collection"
5
+ require "arrest/exceptions"
4
6
  require "arrest/http_source"
5
7
  require "arrest/mem_source"
6
8
  require "arrest/abstract_resource"
@@ -1,9 +1,12 @@
1
1
  require 'json'
2
2
  require 'arrest/string_utils'
3
3
  require 'time'
4
- class Boolean
5
4
 
6
- end
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
- return nil
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
- StringUtils.plural self.name.sub(/.*:/,'').downcase
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
- Arrest::Source.mod.const_get(StringUtils.classify clazz_name).all_for self
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
- send :define_method, method_name do
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
- self.fields + self.superclass.fields
152
+ self_fields + self.superclass.fields
122
153
  else
123
- self.fields
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.send :attr_accessor,name
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 as_i
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
- if self.respond_to?(:id) && self.id != nil
193
- AbstractResource::source().put self
194
- else
195
- AbstractResource::source().post self
196
- end
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,7 @@
1
+ module Arrest
2
+ module Errors
3
+ class DocumentNotFoundError < StandardError
4
+
5
+ end
6
+ end
7
+ 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
@@ -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 get sub
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.location
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.location
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.class.resource_path
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
@@ -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 get sub
24
- idx = sub.rindex(/\/[0-9]*$/)
25
- if idx
26
- ps = [sub[0..(idx-1)], sub[(idx+1)..sub.length]]
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
- val = traverse @@data,ps
31
- if val.is_a?(Hash)
32
- wrap collection_json(val.values), val.length
33
- elsif val == nil
34
- wrap "{}", 0
62
+ if ps.empty?
63
+ ''
35
64
  else
36
- wrap val.to_hash.to_json, 1
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
- @@data[rest_resource.resource_path()].delete(rest_resource.id.to_s)
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
- @@data[rest_resource.resource_path()][rest_resource.id.to_s] = rest_resource
69
- rest_resource
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
- @@data[rest_resource.resource_path()][next_id.to_s] = rest_resource
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
@@ -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.location}/#{self.resource_name}"
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().get self.resource_path_for(parent)).map do |h|
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().get "#{self.resource_path_for(parent)}/#{id}"
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 location
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
@@ -7,8 +7,8 @@ module Arrest
7
7
  "#{self.resource_name}"
8
8
  end
9
9
 
10
- def all
11
- body = body_root(source().get self.resource_path)
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().get "#{self.resource_path}/#{id}"
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 location
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
@@ -1,3 +1,3 @@
1
1
  module Arrest
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ class User < Arrest::RootResource
2
+ attribute :email, String
3
+ attribute :password, String
4
+ 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 !new_zoo.is_magic, updated_zoo.is_magic
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
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-10 00:00:00.000000000Z
12
+ date: 2011-11-25 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &2154642500 !ruby/object:Gem::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: *2154642500
24
+ version_requirements: *10579480
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: faraday
27
- requirement: &2154641400 !ruby/object:Gem::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: *2154641400
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: []