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 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: []