muve 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/muve/errors.rb +18 -16
- data/lib/muve/location.rb +8 -0
- data/lib/muve/model.rb +85 -34
- data/lib/muve/movement.rb +28 -1
- data/lib/muve/place.rb +12 -0
- data/lib/muve/store.rb +26 -9
- data/lib/muve/version.rb +1 -1
- data/lib/muve.rb +8 -2
- data/spec/factories.rb +5 -0
- data/spec/location_spec.rb +5 -2
- data/spec/model_spec.rb +145 -10
- data/spec/place_spec.rb +15 -0
- data/spec/store_spec.rb +16 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86078fd9dbc468dc0837688bd28c9ffbad043681
|
4
|
+
data.tar.gz: 3de1f46815f7ec2cdb1511b8c24445277a06df7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b83a8681897f5ef214d6aaa3ed729972c38e263bd91e62fa12cc555775fa2cb6e413bad80911d704eb027c553d06c08e2e3ef9b9628fb1e2a5669d11a9a475db
|
7
|
+
data.tar.gz: b0fb6001de7b85bb2b60e9fea97f32466e5365d058bcfba4aa65548b1456fa4b14ec9202270c4c04bc4260c633ed2bc33b9a8d38475b1251ac261841d1251c57
|
data/lib/muve/errors.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
module Muve
|
2
|
+
module Error
|
3
|
+
class StandardError < StandardError; end
|
4
|
+
|
5
|
+
class IncompleteImplementation < StandardError; end
|
6
|
+
|
7
|
+
class InvalidAttribute < StandardError; end
|
8
|
+
|
9
|
+
class InvalidQuery < StandardError; end
|
10
|
+
|
11
|
+
class NotConfigured < StandardError; end
|
12
|
+
|
13
|
+
class NotFound < StandardError; end
|
14
|
+
|
15
|
+
class ValidationError < StandardError; end
|
16
|
+
|
17
|
+
class SaveError < StandardError; end
|
18
|
+
end
|
17
19
|
end
|
data/lib/muve/location.rb
CHANGED
data/lib/muve/model.rb
CHANGED
@@ -13,7 +13,7 @@ module Muve
|
|
13
13
|
# implementation
|
14
14
|
# ++
|
15
15
|
module Model
|
16
|
-
include
|
16
|
+
include Muve::Error
|
17
17
|
include Muve::Helper
|
18
18
|
|
19
19
|
def initialize(params={})
|
@@ -27,10 +27,20 @@ module Muve
|
|
27
27
|
@destroyed = false
|
28
28
|
end
|
29
29
|
|
30
|
+
def reload
|
31
|
+
self.send(:populate, extract(adaptor.get(self.class, id))) if id
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
30
35
|
def self.included(base)
|
31
36
|
base.extend ClassMethods
|
32
37
|
end
|
33
38
|
|
39
|
+
def ==(rival)
|
40
|
+
return false unless rival.kind_of? self.class
|
41
|
+
self.attributes == rival.attributes
|
42
|
+
end
|
43
|
+
|
34
44
|
# Initializes the +Muve::Model+ class. Use the +Muve::Model::init+ method
|
35
45
|
# to set a adaptor to take care of the retrieval and storage of resources.
|
36
46
|
def self.init(handler=nil)
|
@@ -53,28 +63,29 @@ module Muve
|
|
53
63
|
hash = {}
|
54
64
|
attributes.map { |k, v|
|
55
65
|
if v.respond_to? :to_hash
|
56
|
-
(hash[k] = v.to_hash(level+1, limit)) if level < limit
|
66
|
+
(hash[k.to_sym] = v.to_hash(level+1, limit)) if level < limit
|
57
67
|
else
|
58
|
-
#(raise
|
59
|
-
hash[k] = v
|
68
|
+
#(raise AssocError, "#Associated #{v.class} for #{k} must respond to #to_hash or be a Hash") unless v.kind_of? Hash
|
69
|
+
hash[k.to_sym] = v
|
60
70
|
end
|
61
71
|
}
|
62
72
|
hash
|
63
73
|
end
|
64
74
|
|
65
|
-
# Save a resource and raises an
|
75
|
+
# Save a resource and raises an SaveError on failure
|
66
76
|
def save!
|
77
|
+
raise ValidationError, "validation failed" unless valid?
|
67
78
|
create_or_update
|
68
79
|
rescue => e
|
69
|
-
|
70
|
-
raise MuveSaveError, "Save failed because #{e} was raised"
|
80
|
+
raise SaveError, "Save failed because #{e} was raised"
|
71
81
|
end
|
72
82
|
|
73
83
|
# Save a resource
|
74
84
|
def save
|
75
85
|
# TODO: be more verbose about the nature of the failure, if any
|
76
|
-
|
77
|
-
|
86
|
+
(create_or_update if valid?) or false
|
87
|
+
rescue => e
|
88
|
+
false
|
78
89
|
end
|
79
90
|
|
80
91
|
# Destroy a resource
|
@@ -115,8 +126,30 @@ module Muve
|
|
115
126
|
def id
|
116
127
|
@id
|
117
128
|
end
|
129
|
+
|
130
|
+
# The parameterized identifier of the resource
|
131
|
+
def to_param
|
132
|
+
id && id.to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
def attributes=(data)
|
136
|
+
Helper.symbolize_keys(data).each { |k, v| self.public_send("#{k}=", v) }
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns a Hash of the attributes for the current resource.
|
141
|
+
def attributes
|
142
|
+
self.class.extract_attributes(
|
143
|
+
resource: self,
|
144
|
+
fields: fields,
|
145
|
+
invalid_attributes: invalid_attributes,
|
146
|
+
id: self.id
|
147
|
+
)
|
148
|
+
end
|
118
149
|
|
119
150
|
private
|
151
|
+
# Reserved attributes. These keys are not available to the user because
|
152
|
+
# they are used to handle plumbing (library internal operations).
|
120
153
|
def invalid_attributes
|
121
154
|
%w(id adaptor)
|
122
155
|
end
|
@@ -134,25 +167,17 @@ module Muve
|
|
134
167
|
@new_record = false if details.key? :id
|
135
168
|
end
|
136
169
|
|
137
|
-
def
|
138
|
-
|
170
|
+
def storeable_attributes
|
171
|
+
hash = {}
|
172
|
+
attributes.map { |k, v| hash[k] = convert(v) }
|
173
|
+
hash
|
139
174
|
end
|
140
175
|
|
141
176
|
def create_or_update
|
142
|
-
result = new_record? ? create(
|
177
|
+
result = new_record? ? create(storeable_attributes) : update(storeable_attributes)
|
143
178
|
self
|
144
179
|
end
|
145
180
|
|
146
|
-
# NOTE: not sure we need this
|
147
|
-
def attributes
|
148
|
-
self.class.extract_attributes(
|
149
|
-
resource: self,
|
150
|
-
fields: fields,
|
151
|
-
invalid_attributes: invalid_attributes,
|
152
|
-
id: self.id
|
153
|
-
)
|
154
|
-
end
|
155
|
-
|
156
181
|
# A manifest of the fields known to the model. The model logic seeks
|
157
182
|
# counsel from this resource to determine which properties to write and
|
158
183
|
# read from the repository.
|
@@ -164,6 +189,7 @@ module Muve
|
|
164
189
|
# the new id and un-marking the new_record?
|
165
190
|
def create(attr)
|
166
191
|
@id = adaptor.create(self.class, attr)
|
192
|
+
# TODO: deal with unsuccessful #create
|
167
193
|
@new_record = false
|
168
194
|
end
|
169
195
|
|
@@ -176,9 +202,17 @@ module Muve
|
|
176
202
|
self.class.adaptor
|
177
203
|
end
|
178
204
|
|
205
|
+
def extract(storeable)
|
206
|
+
self.class.extract(storeable)
|
207
|
+
end
|
208
|
+
|
209
|
+
def convert(resource)
|
210
|
+
self.class.convert(resource)
|
211
|
+
end
|
212
|
+
|
179
213
|
# Class methods exposed to all Muve models
|
180
214
|
module ClassMethods
|
181
|
-
include
|
215
|
+
include Muve::Error
|
182
216
|
# Configure the adaptor to take care of handling persistence for this
|
183
217
|
# model. The adaptor should extend +Muve::Store+.
|
184
218
|
#
|
@@ -193,10 +227,18 @@ module Muve
|
|
193
227
|
# The adaptor currently set to handle persistence for all Muve::Model
|
194
228
|
# classes and instances
|
195
229
|
def adaptor
|
196
|
-
raise
|
230
|
+
raise NotConfigured, "the adaptor has not been set" unless (@adaptor || Model.handler)
|
197
231
|
@adaptor or Model.handler
|
198
232
|
end
|
199
233
|
|
234
|
+
def extract(storeable)
|
235
|
+
adaptor.formatter.convert_from_storeable_object(storeable)
|
236
|
+
end
|
237
|
+
|
238
|
+
def convert(resource)
|
239
|
+
adaptor.formatter.convert_to_storeable_object(resource)
|
240
|
+
end
|
241
|
+
|
200
242
|
def connection
|
201
243
|
Muve::Model.connection
|
202
244
|
end
|
@@ -204,21 +246,27 @@ module Muve
|
|
204
246
|
def database
|
205
247
|
Muve::Model.database
|
206
248
|
end
|
249
|
+
|
250
|
+
def model_name
|
251
|
+
self.to_s.split("::").last
|
252
|
+
end
|
207
253
|
|
208
254
|
# The container (e.g.: collection, tablename or anything that is analogous
|
209
255
|
# to this construct) of the resource
|
210
256
|
def container
|
211
|
-
raise
|
257
|
+
raise Muve::Error::NotConfigured, "container not defined for #{self}"
|
212
258
|
end
|
213
259
|
|
260
|
+
# Returns a Hash of the attributes for the given resource
|
261
|
+
# TODO: do we still need this?
|
214
262
|
def extract_attributes(resource: self.new, fields: [], invalid_attributes: [], id: nil)
|
215
263
|
data = {}
|
216
264
|
fields.select{ |k| k != invalid_attributes }.each { |k|
|
217
265
|
# TODO: confirm resource.respond_to? k prior to assigning
|
218
|
-
data[k.to_sym] = resource.public_send(k)
|
266
|
+
data[k.to_sym] = resource.public_send(k) if resource.respond_to? k
|
219
267
|
}
|
220
268
|
if id
|
221
|
-
data = data.merge
|
269
|
+
data = data.merge id: id
|
222
270
|
end
|
223
271
|
data
|
224
272
|
end
|
@@ -226,7 +274,7 @@ module Muve
|
|
226
274
|
# Finds a resource by id
|
227
275
|
def find(id)
|
228
276
|
result = self.new()
|
229
|
-
result.send(:populate, self.adaptor.get(self, id))
|
277
|
+
result.send(:populate, extract(self.adaptor.get(self, id)))
|
230
278
|
result
|
231
279
|
end
|
232
280
|
|
@@ -237,7 +285,7 @@ module Muve
|
|
237
285
|
(self.adaptor.get(self, nil, params) or []).each do |details|
|
238
286
|
details
|
239
287
|
result = self.new()
|
240
|
-
result.send(:populate, details)
|
288
|
+
result.send(:populate, extract(details))
|
241
289
|
item << result
|
242
290
|
end
|
243
291
|
end
|
@@ -255,15 +303,18 @@ module Muve
|
|
255
303
|
# the required private +#fields# method one may specify the known fields
|
256
304
|
# of the resource with one line of code.
|
257
305
|
def with_fields(*args)
|
306
|
+
fields = self.new.send(:fields) # TODO: Fix this sloppy mess
|
258
307
|
attr_accessor *args
|
259
|
-
class_eval "def fields; #{args}; end"
|
308
|
+
class_eval "def fields; #{Set.new(fields + args).to_a}; end"
|
260
309
|
end
|
261
310
|
|
262
311
|
# Creates a new resource and persists it to the datastore
|
263
|
-
def create(
|
264
|
-
|
265
|
-
|
266
|
-
|
312
|
+
def create(attr)
|
313
|
+
self.new(attr).save
|
314
|
+
end
|
315
|
+
|
316
|
+
def create!(attr)
|
317
|
+
self.new(attr).save!
|
267
318
|
end
|
268
319
|
|
269
320
|
def destroy_all
|
data/lib/muve/movement.rb
CHANGED
@@ -3,7 +3,25 @@ module Muve
|
|
3
3
|
include Model
|
4
4
|
|
5
5
|
with_fields :traveller, :location, :time
|
6
|
-
|
6
|
+
|
7
|
+
def latitude
|
8
|
+
location[:latitude] if location
|
9
|
+
end
|
10
|
+
|
11
|
+
def longitude
|
12
|
+
location[:longitude] if location
|
13
|
+
end
|
14
|
+
|
15
|
+
def latitude=(value)
|
16
|
+
location = {} unless location
|
17
|
+
location[:latitude]=(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def longitude=(value)
|
21
|
+
location = {} unless location
|
22
|
+
location[:longitude]=(value)
|
23
|
+
end
|
24
|
+
|
7
25
|
def valid?
|
8
26
|
assocs.each do |assoc|
|
9
27
|
return false unless !assoc.nil? && assoc.valid?
|
@@ -14,6 +32,15 @@ module Muve
|
|
14
32
|
true
|
15
33
|
end
|
16
34
|
|
35
|
+
alias_method :lat, :latitude
|
36
|
+
alias_method :lat=, :latitude=
|
37
|
+
alias_method :lon, :longitude
|
38
|
+
alias_method :lon=, :longitude=
|
39
|
+
alias_method :lng, :longitude
|
40
|
+
alias_method :lng=, :longitude=
|
41
|
+
alias_method :long, :longitude
|
42
|
+
alias_method :long=, :longitude=
|
43
|
+
|
17
44
|
private
|
18
45
|
def assocs
|
19
46
|
[
|
data/lib/muve/place.rb
ADDED
data/lib/muve/store.rb
CHANGED
@@ -10,16 +10,29 @@ module Muve
|
|
10
10
|
# Take a look at +Muve::Store::Mongo+ to find an implementation of a store
|
11
11
|
# adaptor.
|
12
12
|
module Store
|
13
|
-
include
|
13
|
+
include Muve::Error
|
14
14
|
include Muve::Helper
|
15
15
|
|
16
|
+
module Formatter
|
17
|
+
include Muve::Error
|
18
|
+
include Muve::Helper
|
19
|
+
|
20
|
+
def convert_to_storeable_object(resource)
|
21
|
+
raise IncompleteImplementation, "convertor to storeable needed"
|
22
|
+
end
|
23
|
+
|
24
|
+
def convert_from_storeable_object(storeable)
|
25
|
+
raise IncompleteImplementation, "convertor from storeable needed"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
16
29
|
# gets data from the given container matching the provided details
|
17
30
|
#
|
18
31
|
# Given a +Place+ resource the following calls may be acceptable
|
19
32
|
# - +Adaptor.get(Place, 1232) # find one resource where id = 1232+
|
20
33
|
# - +Adaptor.get(Place, nil, { city: 'NYC', rating: 5 })+ # find more
|
21
34
|
def get(resource, id=nil, details=nil)
|
22
|
-
raise
|
35
|
+
raise InvalidQuery unless (id || details)
|
23
36
|
|
24
37
|
if details
|
25
38
|
find(resource, details)
|
@@ -31,7 +44,7 @@ module Muve
|
|
31
44
|
# creates a resource containing the specified details in the repository.
|
32
45
|
# Returns the id of the created object on success, raises an error otherwise
|
33
46
|
def create(resource, details)
|
34
|
-
raise
|
47
|
+
raise IncompleteImplementation, "implement a create handler for #{self}"
|
35
48
|
end
|
36
49
|
|
37
50
|
# removes a resource matching the optional +id+ and +details+
|
@@ -39,12 +52,12 @@ module Muve
|
|
39
52
|
# A successful removal operation should returns +true+ while any other
|
40
53
|
# value is considered an error.
|
41
54
|
def delete(resource, id, details=nil)
|
42
|
-
raise
|
55
|
+
raise IncompleteImplementation, "implement a delete handler for #{self}"
|
43
56
|
end
|
44
57
|
|
45
58
|
# update a resource with the identified by +id+ with the given +details+
|
46
59
|
def update(resource, id, details)
|
47
|
-
raise
|
60
|
+
raise IncompleteImplementation, "implement a update handler for #{self}"
|
48
61
|
end
|
49
62
|
|
50
63
|
# collect a single resource from the repository that matches the given id
|
@@ -54,7 +67,7 @@ module Muve
|
|
54
67
|
#
|
55
68
|
# { id: 12, name: 'Spock', organization: 'The Enterprise' }
|
56
69
|
def fetch(resource, id, details={})
|
57
|
-
raise
|
70
|
+
raise IncompleteImplementation, "implement a fetch handler for #{self}"
|
58
71
|
end
|
59
72
|
|
60
73
|
# find resources from its repository that match the given id and details
|
@@ -70,13 +83,13 @@ module Muve
|
|
70
83
|
# end
|
71
84
|
# end
|
72
85
|
def find(resource, details)
|
73
|
-
raise
|
86
|
+
raise IncompleteImplementation, "implement a find handler for #{self}"
|
74
87
|
end
|
75
88
|
|
76
89
|
# counts the resources matching the details, if any.
|
77
90
|
# Returns a integer that represents the amount of matching entities found.
|
78
91
|
def count(resource, details={})
|
79
|
-
raise
|
92
|
+
raise IncompleteImplementation, "implement a count handler for #{self}"
|
80
93
|
end
|
81
94
|
|
82
95
|
alias_method :destroy, :delete
|
@@ -86,7 +99,11 @@ module Muve
|
|
86
99
|
# is the +id+ or +_id+ field, while in other cases the index field may be
|
87
100
|
# different. The store should take care of index naming.
|
88
101
|
def index_hash(index_values)
|
89
|
-
{}
|
102
|
+
raise IncompleteImplementation, "implement the index_hash handler for #{self}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def formatter
|
106
|
+
raise IncompleteImplementation, "specify a formatter"
|
90
107
|
end
|
91
108
|
end
|
92
109
|
end
|
data/lib/muve/version.rb
CHANGED
data/lib/muve.rb
CHANGED
@@ -18,6 +18,8 @@ module Muve
|
|
18
18
|
require "muve/helpers"
|
19
19
|
require "muve/model"
|
20
20
|
require "muve/store"
|
21
|
+
|
22
|
+
require "muve/place"
|
21
23
|
require "muve/location"
|
22
24
|
require "muve/traveller"
|
23
25
|
require "muve/movement"
|
@@ -47,7 +49,7 @@ module Muve
|
|
47
49
|
begin
|
48
50
|
@@conn
|
49
51
|
rescue => e
|
50
|
-
raise
|
52
|
+
raise NotConfigured, "the connection has not been defined"
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
@@ -56,7 +58,7 @@ module Muve
|
|
56
58
|
begin
|
57
59
|
@@db
|
58
60
|
rescue => e
|
59
|
-
raise
|
61
|
+
raise NotConfigured, "the database has not been defined"
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
@@ -69,4 +71,8 @@ module Muve
|
|
69
71
|
Model.connection = connection if connection
|
70
72
|
Model.database = database if database
|
71
73
|
end
|
74
|
+
|
75
|
+
def self.raise_something
|
76
|
+
raise NotConfigured
|
77
|
+
end
|
72
78
|
end
|
data/spec/factories.rb
CHANGED
data/spec/location_spec.rb
CHANGED
@@ -4,12 +4,15 @@ describe Muve::Location do
|
|
4
4
|
let(:latitude) { Faker::Geolocation.lat }
|
5
5
|
let(:longitude) { Faker::Geolocation.lng }
|
6
6
|
|
7
|
+
it { expect(Muve::Location.model_name).to eq("Location") }
|
8
|
+
|
7
9
|
subject { Muve::Location.new latitude: latitude, longitude: longitude }
|
8
10
|
it { expect(subject.latitude).to eq(latitude) }
|
9
11
|
it { expect(subject.longitude).to eq(longitude) }
|
10
12
|
it { expect(subject.lat).to eq(latitude) }
|
11
13
|
it { expect(subject.long).to eq(longitude) }
|
12
14
|
it { expect(subject.lng).to eq(longitude) }
|
15
|
+
it { expect{ subject.to_hash }.not_to raise_error }
|
13
16
|
|
14
17
|
it { is_expected.to respond_to(:lat) }
|
15
18
|
it { is_expected.to respond_to(:latitude) }
|
@@ -46,7 +49,7 @@ describe Muve::Location do
|
|
46
49
|
|
47
50
|
context "validation" do
|
48
51
|
before(:each) { allow_any_instance_of(Muve::Location).to receive(:create_or_update).and_return(true) }
|
49
|
-
it { expect{build(Muve::Location, longitude: -181).save}.to raise_error }
|
50
|
-
it { expect{build(Muve::Location).save}.not_to raise_error }
|
52
|
+
it { expect{build(Muve::Location, longitude: -181).save!}.to raise_error }
|
53
|
+
it { expect{build(Muve::Location).save!}.not_to raise_error }
|
51
54
|
end
|
52
55
|
end
|
data/spec/model_spec.rb
CHANGED
@@ -101,7 +101,7 @@ describe 'Model' do
|
|
101
101
|
version: 'failed clone by failed experiment'
|
102
102
|
)
|
103
103
|
)
|
104
|
-
).send(:
|
104
|
+
).send(:to_hash)).to eq(
|
105
105
|
name: 'Stewie Griffin',
|
106
106
|
version: 'our instance of the multiverse',
|
107
107
|
another: {
|
@@ -121,7 +121,7 @@ describe 'Model' do
|
|
121
121
|
version: 'failed clone by failed experiment'
|
122
122
|
)
|
123
123
|
)
|
124
|
-
).send(:
|
124
|
+
).send(:to_hash)).to eq(
|
125
125
|
name: 'Stewie Griffin',
|
126
126
|
version: 'our instance of the multiverse',
|
127
127
|
another: {
|
@@ -158,6 +158,7 @@ describe 'Model' do
|
|
158
158
|
it { expect(subject.name).to eq('Peter Griffin') }
|
159
159
|
it { expect(subject.description).to eq('Surfin-bird lover') }
|
160
160
|
it { expect(subject.version).to eq('the fat one') }
|
161
|
+
it { expect(subject.attributes).to eq({ name: 'Peter Griffin', description: 'Surfin-bird lover', version: 'the fat one', age: nil }) }
|
161
162
|
end
|
162
163
|
end
|
163
164
|
end
|
@@ -168,8 +169,12 @@ describe 'Model' do
|
|
168
169
|
expect(Muve::Model.connection).to be(object)
|
169
170
|
end
|
170
171
|
|
172
|
+
it 'allows equality checks between similar-type objects' do
|
173
|
+
expect(Resource.new(name: 'Jones')).to eq(Resource.new(name: 'Jones'))
|
174
|
+
end
|
175
|
+
|
171
176
|
it 'raises a not configured exception when connection is not set' do
|
172
|
-
configuration_error =
|
177
|
+
configuration_error = Muve::Error::NotConfigured
|
173
178
|
Muve::Model.remove_class_variable(:@@conn) if Muve::Model.class_variable_defined?(:@@conn)
|
174
179
|
|
175
180
|
expect { Muve::Model.connection }.to raise_error(configuration_error)
|
@@ -178,7 +183,7 @@ describe 'Model' do
|
|
178
183
|
end
|
179
184
|
|
180
185
|
it 'raises a not configured exception when database is not set' do
|
181
|
-
configuration_error =
|
186
|
+
configuration_error = Muve::Error::NotConfigured
|
182
187
|
Muve::Model.remove_class_variable(:@@db) if Muve::Model.class_variable_defined?(:@@db)
|
183
188
|
|
184
189
|
expect { Muve::Model.database }.to raise_error(configuration_error)
|
@@ -221,6 +226,16 @@ describe 'Model' do
|
|
221
226
|
expect(another.description).to eq('blah')
|
222
227
|
end
|
223
228
|
|
229
|
+
it 'allows the modification of attributes' do
|
230
|
+
resource = Resource.new(name: 'muve-resource', version: 0)
|
231
|
+
expect(resource.name).to eq('muve-resource')
|
232
|
+
expect(resource.version).to eq(0)
|
233
|
+
|
234
|
+
resource.attributes = { name: 'french', version: 1 }
|
235
|
+
expect(resource.name).to eq('french')
|
236
|
+
expect(resource.version).to eq(1)
|
237
|
+
end
|
238
|
+
|
224
239
|
# TODO: Study if this is desirable perhaps one would rather prefer setting
|
225
240
|
# seperate adaptors for different models
|
226
241
|
it 'shares the adaptor amongst all its instances' do
|
@@ -265,6 +280,18 @@ describe 'Model' do
|
|
265
280
|
end
|
266
281
|
end
|
267
282
|
|
283
|
+
class GenericFormatter
|
284
|
+
extend Muve::Store::Formatter
|
285
|
+
|
286
|
+
def self.convert_to_storeable_object(resource)
|
287
|
+
resource
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.convert_from_storeable_object(storeable)
|
291
|
+
storeable
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
268
295
|
class GenericAdaptor
|
269
296
|
extend Muve::Store
|
270
297
|
|
@@ -272,6 +299,10 @@ describe 'Model' do
|
|
272
299
|
hash = { :_id => index_value }
|
273
300
|
{ :_id => index_value }
|
274
301
|
end
|
302
|
+
|
303
|
+
def self.formatter
|
304
|
+
GenericFormatter
|
305
|
+
end
|
275
306
|
end
|
276
307
|
|
277
308
|
Resource.adaptor = GenericAdaptor
|
@@ -302,12 +333,37 @@ describe 'Model' do
|
|
302
333
|
subject { @res }
|
303
334
|
it_behaves_like "an ActiveRecord-like resource"
|
304
335
|
end
|
305
|
-
|
336
|
+
|
306
337
|
it 'calls the store create handler upon save' do
|
307
338
|
expect(GenericAdaptor).to receive(:create).once
|
308
339
|
@res.save
|
309
340
|
end
|
310
341
|
|
342
|
+
describe '#reload' do
|
343
|
+
it 'does' do
|
344
|
+
id = SecureRandom.hex
|
345
|
+
initial_attrs = {
|
346
|
+
id: id,
|
347
|
+
name: 'First',
|
348
|
+
version: '0',
|
349
|
+
description: 'before'
|
350
|
+
}
|
351
|
+
final_attrs = {
|
352
|
+
id: id,
|
353
|
+
name: 'Last',
|
354
|
+
version: '99',
|
355
|
+
description: 'after'
|
356
|
+
}
|
357
|
+
allow(GenericAdaptor).to receive(:fetch).and_return(initial_attrs)
|
358
|
+
resource = AnotherResource.find(id)
|
359
|
+
expect(resource.attributes).to include(initial_attrs)
|
360
|
+
|
361
|
+
allow(GenericAdaptor).to receive(:fetch).and_return(final_attrs)
|
362
|
+
expect(resource.attributes).to include(initial_attrs)
|
363
|
+
expect(resource.reload.attributes).to include(final_attrs)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
311
367
|
describe '#find' do
|
312
368
|
before(:each) do
|
313
369
|
@id = SecureRandom.hex
|
@@ -336,6 +392,11 @@ describe 'Model' do
|
|
336
392
|
it 'returns a record that is not a new record' do
|
337
393
|
expect(AnotherResource.find(@id).new_record?).to be(false)
|
338
394
|
end
|
395
|
+
|
396
|
+
it 'converts stored data to a workable object' do
|
397
|
+
expect(AnotherResource).to receive(:extract).at_least(:once)
|
398
|
+
AnotherResource.find(@id)
|
399
|
+
end
|
339
400
|
end
|
340
401
|
|
341
402
|
describe '#where' do
|
@@ -356,6 +417,11 @@ describe 'Model' do
|
|
356
417
|
expect(item).to be_a(AnotherResource)
|
357
418
|
end
|
358
419
|
end
|
420
|
+
|
421
|
+
it 'converts a stored data to a workable object' do
|
422
|
+
expect(AnotherResource).to receive(:extract).at_least(:once)
|
423
|
+
AnotherResource.where({ name: 'all' }).count
|
424
|
+
end
|
359
425
|
end
|
360
426
|
|
361
427
|
describe '::create' do
|
@@ -365,8 +431,8 @@ describe 'Model' do
|
|
365
431
|
|
366
432
|
it 'creates a new instance' do
|
367
433
|
attributes = { name: 'Bonobo' }
|
368
|
-
expect(Resource).to receive(:new).with(attributes).once
|
369
|
-
Resource.create(attributes)
|
434
|
+
expect(Resource).to receive(:new).with(attributes).and_return(Resource.new(attributes)).once
|
435
|
+
expect(Resource.create(attributes)).to be_a(Resource)
|
370
436
|
end
|
371
437
|
|
372
438
|
it 'calls the save handler' do
|
@@ -383,6 +449,7 @@ describe 'Model' do
|
|
383
449
|
describe '#save' do
|
384
450
|
before(:each) do
|
385
451
|
@res.name = 'first'
|
452
|
+
# NOTE: returning @id should not work, return should be a boolean
|
386
453
|
allow(GenericAdaptor).to receive(:create).and_return(@id = SecureRandom.hex)
|
387
454
|
end
|
388
455
|
|
@@ -402,6 +469,11 @@ describe 'Model' do
|
|
402
469
|
it 'is persisted' do
|
403
470
|
expect{ @res.save }.to change{ @res.persisted? }.to(true)
|
404
471
|
end
|
472
|
+
|
473
|
+
it 'is converted to a storage-friendly format' do
|
474
|
+
expect(GenericFormatter).to receive(:convert_to_storeable_object).at_least(1)
|
475
|
+
@res.save
|
476
|
+
end
|
405
477
|
end
|
406
478
|
|
407
479
|
describe 'on a existing record' do
|
@@ -415,6 +487,12 @@ describe 'Model' do
|
|
415
487
|
@res.name = 'second'
|
416
488
|
@res.save
|
417
489
|
end
|
490
|
+
|
491
|
+
it 'is converted to a storage-friendly format' do
|
492
|
+
expect(GenericFormatter).to receive(:convert_to_storeable_object).at_least(1)
|
493
|
+
@res.name = 'something'
|
494
|
+
@res.save
|
495
|
+
end
|
418
496
|
end
|
419
497
|
end
|
420
498
|
|
@@ -469,7 +547,7 @@ describe 'Model' do
|
|
469
547
|
Resource.find(id)
|
470
548
|
end
|
471
549
|
|
472
|
-
it '
|
550
|
+
it 'hashes a flat resource' do
|
473
551
|
expect(Resource.new(
|
474
552
|
name: 'Peter Griffin',
|
475
553
|
version: 'dumbass',
|
@@ -491,7 +569,7 @@ describe 'Model' do
|
|
491
569
|
)
|
492
570
|
end
|
493
571
|
|
494
|
-
it '
|
572
|
+
it 'hashes nested resources' do
|
495
573
|
another = AnotherResource.new
|
496
574
|
another.send :populate, {
|
497
575
|
id: SecureRandom.hex,
|
@@ -508,7 +586,7 @@ describe 'Model' do
|
|
508
586
|
name: 'Peter Griffin',
|
509
587
|
version: 'dumbass',
|
510
588
|
another: {
|
511
|
-
|
589
|
+
id: another.id,
|
512
590
|
name: 'Brian Griffin',
|
513
591
|
version: 'the pretentious one',
|
514
592
|
description: 'Canine, liberal, writer',
|
@@ -516,5 +594,62 @@ describe 'Model' do
|
|
516
594
|
}
|
517
595
|
)
|
518
596
|
end
|
597
|
+
|
598
|
+
it 'returns the attributes on request' do
|
599
|
+
another = AnotherResource.new
|
600
|
+
another.send :populate, {
|
601
|
+
id: SecureRandom.hex,
|
602
|
+
name: 'Brian Griffin',
|
603
|
+
version: 'the pretentious one',
|
604
|
+
description: 'Canine, liberal, writer',
|
605
|
+
age: 8
|
606
|
+
}
|
607
|
+
resource = Resource.new
|
608
|
+
id = SecureRandom.hex
|
609
|
+
resource.send :populate, {
|
610
|
+
id: id,
|
611
|
+
name: 'Peter Griffin',
|
612
|
+
version: 'dumbass',
|
613
|
+
another: another
|
614
|
+
}
|
615
|
+
|
616
|
+
expect(resource.attributes).to eq(
|
617
|
+
id: id,
|
618
|
+
name: 'Peter Griffin',
|
619
|
+
version: 'dumbass',
|
620
|
+
another: another
|
621
|
+
)
|
622
|
+
end
|
623
|
+
|
624
|
+
it 'converts repopulated data to resources' do
|
625
|
+
data = {
|
626
|
+
id: SecureRandom.hex,
|
627
|
+
name: 'Peter Griffin',
|
628
|
+
version: 'dumbass',
|
629
|
+
another: {
|
630
|
+
id: SecureRandom.hex,
|
631
|
+
name: 'Brian Griffin',
|
632
|
+
version: 'the pretentious one',
|
633
|
+
description: 'Canine, liberal, writer',
|
634
|
+
age: 8
|
635
|
+
}
|
636
|
+
}
|
637
|
+
resource = Resource.new
|
638
|
+
resource.send(:populate, data)
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
describe "#to_param" do
|
643
|
+
it "is equal to the stringified id" do
|
644
|
+
object = AnotherResource.new
|
645
|
+
object.send :populate, {
|
646
|
+
id: SecureRandom.hex,
|
647
|
+
name: 'Brian Griffin',
|
648
|
+
version: 'the pretentious one',
|
649
|
+
description: 'Canine, liberal, writer',
|
650
|
+
age: 8
|
651
|
+
}
|
652
|
+
expect(object.id.to_s).to eq(object.to_param)
|
653
|
+
end
|
519
654
|
end
|
520
655
|
end
|
data/spec/place_spec.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Muve::Place do
|
4
|
+
it { expect(Muve::Place.model_name).to eq "Place" }
|
5
|
+
|
6
|
+
it { is_expected.to respond_to :location }
|
7
|
+
it { is_expected.to respond_to :name }
|
8
|
+
|
9
|
+
it 'is invalid when the location is not a Location' do
|
10
|
+
expect(build Muve::Place, location: Object.new).to be_invalid
|
11
|
+
expect(build Muve::Place, location: rand).to be_invalid
|
12
|
+
expect(build Muve::Place, location: build(Muve::Location, latitude: -100)).to be_invalid
|
13
|
+
expect(build Muve::Place, location: build(Muve::Location)).to be_valid
|
14
|
+
end
|
15
|
+
end
|
data/spec/store_spec.rb
CHANGED
@@ -12,6 +12,10 @@ describe Muve::Store do
|
|
12
12
|
class GenericStore
|
13
13
|
extend Muve::Store
|
14
14
|
end
|
15
|
+
|
16
|
+
class GenericFormatter
|
17
|
+
extend Muve::Store::Formatter
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
it 'provides methods to get a resource' do
|
@@ -33,6 +37,10 @@ describe Muve::Store do
|
|
33
37
|
expect(GenericStore).to respond_to(:update)
|
34
38
|
end
|
35
39
|
|
40
|
+
it 'has a formatter' do
|
41
|
+
expect(GenericStore).to respond_to(:formatter)
|
42
|
+
end
|
43
|
+
|
36
44
|
it 'raises incomplete implementation errors on non-implemented methods' do
|
37
45
|
%w(create delete update fetch find).each do |method|
|
38
46
|
expect{
|
@@ -41,8 +49,10 @@ describe Muve::Store do
|
|
41
49
|
else
|
42
50
|
GenericStore.send(method, 'resource', nil)
|
43
51
|
end
|
44
|
-
}.to raise_error(
|
52
|
+
}.to raise_error(Muve::Error::IncompleteImplementation)
|
45
53
|
end
|
54
|
+
|
55
|
+
expect{GenericStore.send(:formatter)}.to raise_error(Muve::Error::IncompleteImplementation)
|
46
56
|
end
|
47
57
|
|
48
58
|
it 'attempts to fetch a resource if the id is given' do
|
@@ -60,4 +70,9 @@ describe Muve::Store do
|
|
60
70
|
GenericStore.get(Resource)
|
61
71
|
}.to raise_error
|
62
72
|
end
|
73
|
+
|
74
|
+
describe "formatter" do
|
75
|
+
it { expect(GenericFormatter).to respond_to(:convert_to_storeable_object) }
|
76
|
+
it { expect(GenericFormatter).to respond_to(:convert_from_storeable_object) }
|
77
|
+
end
|
63
78
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: muve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Asabina
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Basic helpers to be used with Muvement
|
14
14
|
email:
|
@@ -28,6 +28,7 @@ files:
|
|
28
28
|
- lib/muve/location.rb
|
29
29
|
- lib/muve/model.rb
|
30
30
|
- lib/muve/movement.rb
|
31
|
+
- lib/muve/place.rb
|
31
32
|
- lib/muve/store.rb
|
32
33
|
- lib/muve/traveller.rb
|
33
34
|
- lib/muve/version.rb
|
@@ -38,6 +39,7 @@ files:
|
|
38
39
|
- spec/model_spec.rb
|
39
40
|
- spec/movement_spec.rb
|
40
41
|
- spec/muve_spec.rb
|
42
|
+
- spec/place_spec.rb
|
41
43
|
- spec/spec_helper.rb
|
42
44
|
- spec/store_spec.rb
|
43
45
|
- spec/traveller_spec.rb
|
@@ -72,6 +74,7 @@ test_files:
|
|
72
74
|
- spec/model_spec.rb
|
73
75
|
- spec/movement_spec.rb
|
74
76
|
- spec/muve_spec.rb
|
77
|
+
- spec/place_spec.rb
|
75
78
|
- spec/spec_helper.rb
|
76
79
|
- spec/store_spec.rb
|
77
80
|
- spec/traveller_spec.rb
|