muve 1.0.1 → 1.1.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.rb +2 -2
- data/lib/muve/errors.rb +10 -12
- data/lib/muve/helpers.rb +2 -1
- data/lib/muve/location.rb +4 -10
- data/lib/muve/model.rb +87 -17
- data/lib/muve/movement.rb +2 -12
- data/lib/muve/store.rb +13 -0
- data/lib/muve/traveller.rb +3 -7
- data/lib/muve/version.rb +1 -1
- data/spec/factories.rb +4 -4
- data/spec/location_spec.rb +27 -6
- data/spec/model_spec.rb +296 -35
- data/spec/movement_spec.rb +1 -1
- data/spec/store_spec.rb +1 -0
- data/spec/traveller_spec.rb +12 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d2c799eb30443546d3054c9dfb9b04069fa8ed21
|
4
|
+
data.tar.gz: e47754d72235cafaaaf41e5075319e0b7aab1c84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58c9bd11811f3b4181e8ff738963f275fffe8b36244d73a3013e0d5fdaaeef7bcdc8e1ca94d9d9f018995274e79680b6471388f0dd3d7dd556fb1f7b07eb7d55
|
7
|
+
data.tar.gz: abd202a42a8d6da4ebac3409657f9239f4313656028ab64cac55657e57cfd6bf2a632dc02e3453426985232a33e6d811177c3c10948c2c6c077055c4183155ad
|
data/lib/muve.rb
CHANGED
@@ -66,7 +66,7 @@ module Muve
|
|
66
66
|
# connection, an adaptor will be needed to actually handle the interaction
|
67
67
|
# between the models and the datastore through the given connection.
|
68
68
|
def self.init(connection=nil, database=nil)
|
69
|
-
Model.connection =connection
|
70
|
-
Model.database = database
|
69
|
+
Model.connection = connection if connection
|
70
|
+
Model.database = database if database
|
71
71
|
end
|
72
72
|
end
|
data/lib/muve/errors.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
module MuveError
|
2
|
-
class MuveStandardError < StandardError
|
3
|
-
end
|
2
|
+
class MuveStandardError < StandardError; end
|
4
3
|
|
5
|
-
class
|
6
|
-
end
|
4
|
+
class MuveIncompleteImplementation < MuveStandardError; end
|
7
5
|
|
8
|
-
class
|
9
|
-
end
|
6
|
+
class MuveInvalidAttributes < MuveStandardError; end
|
10
7
|
|
11
|
-
class
|
12
|
-
end
|
8
|
+
class MuveInvalidQuery < MuveStandardError; end
|
13
9
|
|
14
|
-
class
|
15
|
-
end
|
10
|
+
class MuveNotConfigured < MuveStandardError; end
|
16
11
|
|
17
|
-
class
|
18
|
-
|
12
|
+
class MuveNotFound < MuveStandardError; end
|
13
|
+
|
14
|
+
class MuveValidationError < MuveStandardError; end
|
15
|
+
|
16
|
+
class MuveSaveError < MuveStandardError; end
|
19
17
|
end
|
data/lib/muve/helpers.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Muve
|
2
2
|
module Helper
|
3
|
-
def self.symbolize_keys(hash)
|
3
|
+
def self.symbolize_keys(hash = {})
|
4
|
+
raise ArgumentError.new("argument must be a Hash but was a #{hash.class} #{hash}") unless hash.kind_of? Hash
|
4
5
|
hash.map { |k, v| { k.to_sym => v } }.inject(&:merge)
|
5
6
|
end
|
6
7
|
end
|
data/lib/muve/location.rb
CHANGED
@@ -2,29 +2,23 @@ module Muve
|
|
2
2
|
class Location
|
3
3
|
include Model
|
4
4
|
|
5
|
-
|
5
|
+
with_fields :latitude, :longitude
|
6
6
|
|
7
7
|
alias_method :lat, :latitude
|
8
8
|
alias_method :lat=, :latitude=
|
9
|
-
|
9
|
+
alias_method :lon, :longitude
|
10
|
+
alias_method :lon=, :longitude=
|
10
11
|
alias_method :lng, :longitude
|
11
12
|
alias_method :lng=, :longitude=
|
12
13
|
alias_method :long, :longitude
|
13
14
|
alias_method :long=, :longitude=
|
14
15
|
|
15
|
-
def initialize(latitude=nil, longitude=nil, type=:wgs84)
|
16
|
-
@latitude, @longitude = latitude, longitude
|
17
|
-
end
|
18
|
-
|
19
16
|
def valid?
|
17
|
+
return false unless latitude && longitude
|
20
18
|
return false unless latitude.abs <= 90 && longitude.abs <= 180
|
21
19
|
true
|
22
20
|
end
|
23
21
|
|
24
|
-
def invalid?
|
25
|
-
!valid?
|
26
|
-
end
|
27
|
-
|
28
22
|
def random(center, range)
|
29
23
|
end
|
30
24
|
end
|
data/lib/muve/model.rb
CHANGED
@@ -33,32 +33,53 @@ module Muve
|
|
33
33
|
|
34
34
|
# Initializes the +Muve::Model+ class. Use the +Muve::Model::init+ method
|
35
35
|
# to set a adaptor to take care of the retrieval and storage of resources.
|
36
|
-
def self.init(
|
37
|
-
|
36
|
+
def self.init(handler=nil)
|
37
|
+
@handler = handler if handler
|
38
38
|
end
|
39
39
|
|
40
|
-
# Returns the adaptor set to handle retrieval and storage of resources
|
41
40
|
def self.handler
|
42
|
-
|
41
|
+
@handler
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a hash of the object and subsequently
|
45
|
+
# containing objects that respond to #to_hash.
|
46
|
+
# In order to avoid circular reference hell you
|
47
|
+
# can set the limit.
|
48
|
+
#
|
49
|
+
# By default on the the root object and its
|
50
|
+
# children a explored. Everything beyond that
|
51
|
+
# range is discarded.
|
52
|
+
def to_hash(level=0, limit=1)
|
53
|
+
hash = {}
|
54
|
+
attributes.map { |k, v|
|
55
|
+
if v.respond_to? :to_hash
|
56
|
+
(hash[k] = v.to_hash(level+1, limit)) if level < limit
|
57
|
+
else
|
58
|
+
#(raise MuveAssocError, "#Associated #{v.class} for #{k} must respond to #to_hash or be a Hash") unless v.kind_of? Hash
|
59
|
+
hash[k] = v
|
60
|
+
end
|
61
|
+
}
|
62
|
+
hash
|
43
63
|
end
|
44
64
|
|
45
65
|
# Save a resource and raises an MuveSaveError on failure
|
46
66
|
def save!
|
47
67
|
create_or_update
|
48
68
|
rescue => e
|
69
|
+
e.backtrace.each { |err| p err }
|
49
70
|
raise MuveSaveError, "Save failed because #{e} was raised"
|
50
71
|
end
|
51
72
|
|
52
73
|
# Save a resource
|
53
74
|
def save
|
75
|
+
# TODO: be more verbose about the nature of the failure, if any
|
76
|
+
raise MuveValidationError, "validation failed" unless valid?
|
54
77
|
create_or_update
|
55
|
-
rescue => e
|
56
|
-
false
|
57
78
|
end
|
58
79
|
|
59
80
|
# Destroy a resource
|
60
81
|
def destroy
|
61
|
-
if adaptor.delete(self.class
|
82
|
+
if adaptor.delete(self.class, id) == true
|
62
83
|
@destroyed = true
|
63
84
|
end
|
64
85
|
end
|
@@ -113,20 +134,28 @@ module Muve
|
|
113
134
|
@new_record = false if details.key? :id
|
114
135
|
end
|
115
136
|
|
137
|
+
def serialized_attributes
|
138
|
+
to_hash
|
139
|
+
end
|
140
|
+
|
116
141
|
def create_or_update
|
117
|
-
result = new_record? ? create(
|
142
|
+
result = new_record? ? create(serialized_attributes) : update(serialized_attributes)
|
118
143
|
self
|
119
144
|
end
|
120
145
|
|
121
146
|
# NOTE: not sure we need this
|
122
147
|
def attributes
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
148
|
+
self.class.extract_attributes(
|
149
|
+
resource: self,
|
150
|
+
fields: fields,
|
151
|
+
invalid_attributes: invalid_attributes,
|
152
|
+
id: self.id
|
153
|
+
)
|
128
154
|
end
|
129
155
|
|
156
|
+
# A manifest of the fields known to the model. The model logic seeks
|
157
|
+
# counsel from this resource to determine which properties to write and
|
158
|
+
# read from the repository.
|
130
159
|
def fields
|
131
160
|
[]
|
132
161
|
end
|
@@ -134,13 +163,13 @@ module Muve
|
|
134
163
|
# Creates the record and performs the necessary housekeeping (e.g.: setting
|
135
164
|
# the new id and un-marking the new_record?
|
136
165
|
def create(attr)
|
137
|
-
@id = adaptor.create(self.class
|
166
|
+
@id = adaptor.create(self.class, attr)
|
138
167
|
@new_record = false
|
139
168
|
end
|
140
169
|
|
141
170
|
# TODO: Update the record and return the number of modified rows
|
142
171
|
def update(attr)
|
143
|
-
adaptor.update(self.class
|
172
|
+
adaptor.update(self.class, id, attr)
|
144
173
|
end
|
145
174
|
|
146
175
|
def adaptor
|
@@ -149,6 +178,7 @@ module Muve
|
|
149
178
|
|
150
179
|
# Class methods exposed to all Muve models
|
151
180
|
module ClassMethods
|
181
|
+
include MuveError
|
152
182
|
# Configure the adaptor to take care of handling persistence for this
|
153
183
|
# model. The adaptor should extend +Muve::Store+.
|
154
184
|
#
|
@@ -157,13 +187,14 @@ module Muve
|
|
157
187
|
# an adaptor for another in order to support another database technology
|
158
188
|
# (.e.g: swithing between document databases or relational databases)
|
159
189
|
def adaptor=(adaptor)
|
160
|
-
|
190
|
+
@adaptor = adaptor
|
161
191
|
end
|
162
192
|
|
163
193
|
# The adaptor currently set to handle persistence for all Muve::Model
|
164
194
|
# classes and instances
|
165
195
|
def adaptor
|
166
|
-
|
196
|
+
raise MuveNotConfigured, "the adaptor has not been set" unless (@adaptor || Model.handler)
|
197
|
+
@adaptor or Model.handler
|
167
198
|
end
|
168
199
|
|
169
200
|
def connection
|
@@ -180,6 +211,18 @@ module Muve
|
|
180
211
|
raise MuveError::MuveNotConfigured, "container not defined for #{self}"
|
181
212
|
end
|
182
213
|
|
214
|
+
def extract_attributes(resource: self.new, fields: [], invalid_attributes: [], id: nil)
|
215
|
+
data = {}
|
216
|
+
fields.select{ |k| k != invalid_attributes }.each { |k|
|
217
|
+
# TODO: confirm resource.respond_to? k prior to assigning
|
218
|
+
data[k.to_sym] = resource.public_send(k)
|
219
|
+
}
|
220
|
+
if id
|
221
|
+
data = data.merge(resource.class.adaptor.index_hash id)
|
222
|
+
end
|
223
|
+
data
|
224
|
+
end
|
225
|
+
|
183
226
|
# Finds a resource by id
|
184
227
|
def find(id)
|
185
228
|
result = self.new()
|
@@ -199,6 +242,33 @@ module Muve
|
|
199
242
|
end
|
200
243
|
end
|
201
244
|
end
|
245
|
+
|
246
|
+
# Counts the amount of records that match the parameters
|
247
|
+
def count(params={})
|
248
|
+
self.adaptor.count(self, params)
|
249
|
+
end
|
250
|
+
|
251
|
+
# The +with_field+ helper allows one to declare a functioning model
|
252
|
+
# with less lines of code.
|
253
|
+
#
|
254
|
+
# Instead of declaring +attr_accessor :name, :age, :hat_size+ along with
|
255
|
+
# the required private +#fields# method one may specify the known fields
|
256
|
+
# of the resource with one line of code.
|
257
|
+
def with_fields(*args)
|
258
|
+
attr_accessor *args
|
259
|
+
class_eval "def fields; #{args}; end"
|
260
|
+
end
|
261
|
+
|
262
|
+
# Creates a new resource and persists it to the datastore
|
263
|
+
def create(attributes)
|
264
|
+
resource = self.new(attributes)
|
265
|
+
resource.save if resource
|
266
|
+
resource
|
267
|
+
end
|
268
|
+
|
269
|
+
def destroy_all
|
270
|
+
warn "Destroying of all entities for a resource is not implemented"
|
271
|
+
end
|
202
272
|
end
|
203
273
|
end
|
204
274
|
end
|
data/lib/muve/movement.rb
CHANGED
@@ -2,17 +2,13 @@ module Muve
|
|
2
2
|
class Movement
|
3
3
|
include Model
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(traveller=nil, location=nil, time=Time.now)
|
8
|
-
@traveller, @location, @time = traveller, location, time
|
9
|
-
end
|
5
|
+
with_fields :traveller, :location, :time
|
10
6
|
|
11
7
|
def valid?
|
12
8
|
assocs.each do |assoc|
|
13
9
|
return false unless !assoc.nil? && assoc.valid?
|
14
10
|
end
|
15
|
-
|
11
|
+
fields.each do |field|
|
16
12
|
return false unless time
|
17
13
|
end
|
18
14
|
true
|
@@ -25,11 +21,5 @@ module Muve
|
|
25
21
|
@location
|
26
22
|
]
|
27
23
|
end
|
28
|
-
|
29
|
-
def flds
|
30
|
-
[
|
31
|
-
@time
|
32
|
-
]
|
33
|
-
end
|
34
24
|
end
|
35
25
|
end
|
data/lib/muve/store.rb
CHANGED
@@ -73,7 +73,20 @@ module Muve
|
|
73
73
|
raise MuveIncompleteImplementation, "implement a find handler for #{self}"
|
74
74
|
end
|
75
75
|
|
76
|
+
# counts the resources matching the details, if any.
|
77
|
+
# Returns a integer that represents the amount of matching entities found.
|
78
|
+
def count(resource, details={})
|
79
|
+
raise MuveIncompleteImplementation, "implement a count handler for #{self}"
|
80
|
+
end
|
81
|
+
|
76
82
|
alias_method :destroy, :delete
|
77
83
|
alias_method :remove, :delete
|
84
|
+
|
85
|
+
# composes the id hash for the used store. Some in some cases the index
|
86
|
+
# is the +id+ or +_id+ field, while in other cases the index field may be
|
87
|
+
# different. The store should take care of index naming.
|
88
|
+
def index_hash(index_values)
|
89
|
+
{}
|
90
|
+
end
|
78
91
|
end
|
79
92
|
end
|
data/lib/muve/traveller.rb
CHANGED
data/lib/muve/version.rb
CHANGED
data/spec/factories.rb
CHANGED
@@ -5,12 +5,12 @@ FactoryGirl.define do
|
|
5
5
|
end
|
6
6
|
|
7
7
|
factory Muve::Traveller do
|
8
|
-
|
8
|
+
name { Faker::Name.name }
|
9
9
|
end
|
10
10
|
|
11
11
|
factory Muve::Movement do
|
12
|
-
traveller { build
|
13
|
-
|
14
|
-
|
12
|
+
traveller { build Muve::Traveller }
|
13
|
+
location { build Muve::Location }
|
14
|
+
time { Time.now - rand(500000) }
|
15
15
|
end
|
16
16
|
end
|
data/spec/location_spec.rb
CHANGED
@@ -4,28 +4,49 @@ describe Muve::Location do
|
|
4
4
|
let(:latitude) { Faker::Geolocation.lat }
|
5
5
|
let(:longitude) { Faker::Geolocation.lng }
|
6
6
|
|
7
|
-
subject { Muve::Location.new
|
7
|
+
subject { Muve::Location.new latitude: latitude, longitude: longitude }
|
8
8
|
it { expect(subject.latitude).to eq(latitude) }
|
9
9
|
it { expect(subject.longitude).to eq(longitude) }
|
10
10
|
it { expect(subject.lat).to eq(latitude) }
|
11
11
|
it { expect(subject.long).to eq(longitude) }
|
12
12
|
it { expect(subject.lng).to eq(longitude) }
|
13
13
|
|
14
|
+
it { is_expected.to respond_to(:lat) }
|
15
|
+
it { is_expected.to respond_to(:latitude) }
|
16
|
+
|
17
|
+
it { is_expected.to respond_to(:lat=) }
|
18
|
+
it { is_expected.to respond_to(:latitude=) }
|
19
|
+
|
20
|
+
it { is_expected.to respond_to(:lon) }
|
21
|
+
it { is_expected.to respond_to(:long) }
|
22
|
+
it { is_expected.to respond_to(:longitude) }
|
23
|
+
|
24
|
+
it { is_expected.to respond_to(:lon=) }
|
25
|
+
it { is_expected.to respond_to(:long=) }
|
26
|
+
it { is_expected.to respond_to(:longitude=) }
|
27
|
+
|
14
28
|
let(:new_latitude) { Faker::Geolocation.lat }
|
15
29
|
let(:new_longitude) { Faker::Geolocation.lng }
|
16
|
-
|
30
|
+
# FIX: sometimes the new_* returns the same value as the already set value which fails when pulled through the change matcher
|
31
|
+
it { expect { subject.latitude = new_latitude }.to(change{subject.latitude}.to(new_latitude), "expected #{subject.latitude} to be #{new_latitude}") }
|
17
32
|
it { expect { subject.lat = new_latitude }.to change{subject.latitude}.to(new_latitude) }
|
18
33
|
it { expect { subject.longitude = new_longitude }.to change{subject.longitude}.to(new_longitude) }
|
19
34
|
it { expect { subject.long = new_longitude }.to change{subject.longitude}.to(new_longitude) }
|
20
35
|
it { expect { subject.lng = new_longitude }.to change{subject.longitude}.to(new_longitude) }
|
21
36
|
|
22
37
|
it 'is invalid when latitude exceeds bounds' do
|
23
|
-
expect(Muve::Location
|
24
|
-
expect(Muve::Location
|
38
|
+
expect(build Muve::Location, latitude: -91).to be_invalid
|
39
|
+
expect(build Muve::Location, latitude: 91).to be_invalid
|
25
40
|
end
|
26
41
|
|
27
42
|
it 'is invalid when longitude exceeds bounds' do
|
28
|
-
expect(Muve::Location
|
29
|
-
expect(Muve::Location
|
43
|
+
expect(build Muve::Location, longitude: -181).to be_invalid
|
44
|
+
expect(build Muve::Location, longitude: 181).to be_invalid
|
45
|
+
end
|
46
|
+
|
47
|
+
context "validation" do
|
48
|
+
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 }
|
30
51
|
end
|
31
52
|
end
|
data/spec/model_spec.rb
CHANGED
@@ -1,30 +1,163 @@
|
|
1
1
|
describe 'Model' do
|
2
|
-
before do
|
2
|
+
before(:each) do
|
3
3
|
class Resource
|
4
4
|
include Muve::Model
|
5
|
-
|
5
|
+
with_fields :name, :version, :another
|
6
6
|
|
7
7
|
def self.container
|
8
8
|
'resources'
|
9
9
|
end
|
10
|
-
|
11
|
-
private
|
12
|
-
def fields
|
13
|
-
[:name, :version]
|
14
|
-
end
|
15
10
|
end
|
16
11
|
|
17
12
|
class AnotherResource
|
18
13
|
include Muve::Model
|
19
|
-
|
14
|
+
with_fields :name, :version, :description, :age
|
20
15
|
|
21
16
|
def self.container
|
22
17
|
'other_resources'
|
23
18
|
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class SomeAdaptor
|
22
|
+
extend Muve::Store
|
23
|
+
|
24
|
+
def self.index_hash(index_value)
|
25
|
+
{ :_id => index_value }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class AnotherAdaptor
|
30
|
+
extend Muve::Store
|
24
31
|
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
def self.index_hash(index_value)
|
33
|
+
{ :_id => index_value }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Resource.remove_instance_variable(:@handler) if Resource.instance_variable_defined?(:@handler)
|
38
|
+
AnotherResource.remove_instance_variable(:@handler) if AnotherResource.instance_variable_defined?(:@handler)
|
39
|
+
|
40
|
+
Muve::Model.init SomeAdaptor
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'AnotherResource' do
|
44
|
+
subject { AnotherResource }
|
45
|
+
it { is_expected.to respond_to(:extract_attributes) }
|
46
|
+
it { expect(subject.send(:extract_attributes,
|
47
|
+
resource: subject.new(
|
48
|
+
name: 'Stewie Griffin',
|
49
|
+
version: 'our instance of the multiverse',
|
50
|
+
description: 'Evil baby with a lot of heart',
|
51
|
+
age: 1
|
52
|
+
),
|
53
|
+
fields: subject.new.send(:fields),
|
54
|
+
invalid_attributes: subject.new.send(:invalid_attributes),
|
55
|
+
id: SecureRandom.hex
|
56
|
+
)).to include :name, :version, :description, :age }
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'Resource' do
|
60
|
+
before(:each) {
|
61
|
+
Muve::Model.init(SomeAdaptor)
|
62
|
+
}
|
63
|
+
|
64
|
+
subject { Resource }
|
65
|
+
it { is_expected.to respond_to(:extract_attributes) }
|
66
|
+
it { expect(subject.send(:extract_attributes,
|
67
|
+
resource: subject.new(
|
68
|
+
name: 'Stewie Griffin',
|
69
|
+
version: 'our instance of the multiverse',
|
70
|
+
another: AnotherResource.new
|
71
|
+
),
|
72
|
+
fields: subject.new.send(:fields),
|
73
|
+
invalid_attributes: subject.new.send(:invalid_attributes),
|
74
|
+
id: SecureRandom.hex
|
75
|
+
)).to include :name, :version, :another }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'Resource' do
|
79
|
+
subject { Resource }
|
80
|
+
it { is_expected.to respond_to(:extract_attributes) }
|
81
|
+
it { expect(subject.send(:extract_attributes,
|
82
|
+
resource: subject.new(
|
83
|
+
name: 'Stewie Griffin',
|
84
|
+
version: 'our instance of the multiverse',
|
85
|
+
another: AnotherResource.new
|
86
|
+
),
|
87
|
+
fields: subject.new.send(:fields),
|
88
|
+
invalid_attributes: subject.new.send(:invalid_attributes),
|
89
|
+
id: SecureRandom.hex
|
90
|
+
)).to include(:name, :version, :another) }
|
91
|
+
|
92
|
+
it {
|
93
|
+
expect(subject.new(
|
94
|
+
name: 'Stewie Griffin',
|
95
|
+
version: 'our instance of the multiverse',
|
96
|
+
another: subject.new(
|
97
|
+
name: 'Bitch Stewie',
|
98
|
+
version: 'failed experiment',
|
99
|
+
another: subject.new(
|
100
|
+
name: 'Bitch-Brian',
|
101
|
+
version: 'failed clone by failed experiment'
|
102
|
+
)
|
103
|
+
)
|
104
|
+
).send(:serialized_attributes)).to eq(
|
105
|
+
name: 'Stewie Griffin',
|
106
|
+
version: 'our instance of the multiverse',
|
107
|
+
another: {
|
108
|
+
name: 'Bitch Stewie',
|
109
|
+
version: 'failed experiment'
|
110
|
+
}
|
111
|
+
) }
|
112
|
+
|
113
|
+
it { expect(subject.new(
|
114
|
+
name: 'Stewie Griffin',
|
115
|
+
version: 'our instance of the multiverse',
|
116
|
+
another: subject.new(
|
117
|
+
name: 'Bitch Stewie',
|
118
|
+
version: 'failed experiment',
|
119
|
+
another: subject.new(
|
120
|
+
name: 'Bitch-Brian',
|
121
|
+
version: 'failed clone by failed experiment'
|
122
|
+
)
|
123
|
+
)
|
124
|
+
).send(:serialized_attributes)).to eq(
|
125
|
+
name: 'Stewie Griffin',
|
126
|
+
version: 'our instance of the multiverse',
|
127
|
+
another: {
|
128
|
+
name: 'Bitch Stewie',
|
129
|
+
version: 'failed experiment'
|
130
|
+
}
|
131
|
+
) }
|
132
|
+
end
|
133
|
+
|
134
|
+
context "instantiated AnotherResource" do
|
135
|
+
subject { AnotherResource.new }
|
136
|
+
it { is_expected.to respond_to(:description) }
|
137
|
+
it { is_expected.to respond_to(:name) }
|
138
|
+
it { is_expected.to respond_to(:to_hash) }
|
139
|
+
it { is_expected.to respond_to(:version) }
|
140
|
+
it { is_expected.to respond_to(:valid?) }
|
141
|
+
it { expect(subject.send(:fields)).to include :name, :version, :description, :age }
|
142
|
+
it { expect(subject.send(:attributes)).to include :name, :version, :description, :age }
|
143
|
+
|
144
|
+
context "populating" do
|
145
|
+
context "with nothing" do
|
146
|
+
before { subject.send(:populate, {}) }
|
147
|
+
it { expect(subject.name).to eq(nil) }
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with unknown fields" do
|
151
|
+
before { subject.send(:populate, { name: 'Jack Sparrow', occupation: 'pirate' }) }
|
152
|
+
it { expect(subject.name).to eq('Jack Sparrow') }
|
153
|
+
it { expect{ subject.occupation }.to raise_error }
|
154
|
+
end
|
155
|
+
|
156
|
+
context "with known fields" do
|
157
|
+
before { subject.send(:populate, { name: 'Peter Griffin', description: 'Surfin-bird lover', version: 'the fat one' }) }
|
158
|
+
it { expect(subject.name).to eq('Peter Griffin') }
|
159
|
+
it { expect(subject.description).to eq('Surfin-bird lover') }
|
160
|
+
it { expect(subject.version).to eq('the fat one') }
|
28
161
|
end
|
29
162
|
end
|
30
163
|
end
|
@@ -36,18 +169,21 @@ describe 'Model' do
|
|
36
169
|
end
|
37
170
|
|
38
171
|
it 'raises a not configured exception when connection is not set' do
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}.to raise_error(
|
43
|
-
|
44
|
-
expect {
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
172
|
+
configuration_error = MuveError::MuveNotConfigured
|
173
|
+
Muve::Model.remove_class_variable(:@@conn) if Muve::Model.class_variable_defined?(:@@conn)
|
174
|
+
|
175
|
+
expect { Muve::Model.connection }.to raise_error(configuration_error)
|
176
|
+
expect { Resource.connection }.to raise_error(configuration_error)
|
177
|
+
expect { AnotherResource.connection }.to raise_error(configuration_error)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'raises a not configured exception when database is not set' do
|
181
|
+
configuration_error = MuveError::MuveNotConfigured
|
182
|
+
Muve::Model.remove_class_variable(:@@db) if Muve::Model.class_variable_defined?(:@@db)
|
183
|
+
|
184
|
+
expect { Muve::Model.database }.to raise_error(configuration_error)
|
185
|
+
expect { Resource.database }.to raise_error(configuration_error)
|
186
|
+
expect { AnotherResource.database }.to raise_error(configuration_error)
|
51
187
|
end
|
52
188
|
|
53
189
|
it 'knows the identifier of its repository' do
|
@@ -56,16 +192,22 @@ describe 'Model' do
|
|
56
192
|
end
|
57
193
|
|
58
194
|
it 'allows the setting of the adaptor' do
|
59
|
-
adaptor =
|
60
|
-
Resource.adaptor
|
61
|
-
|
195
|
+
Resource.adaptor = SomeAdaptor
|
196
|
+
expect(Resource.adaptor).to be(SomeAdaptor)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'allows different adaptors for different resources' do
|
200
|
+
Resource.adaptor = SomeAdaptor
|
201
|
+
AnotherResource.adaptor = AnotherAdaptor
|
202
|
+
|
203
|
+
expect(Resource.adaptor).to be(SomeAdaptor)
|
204
|
+
expect(AnotherResource.adaptor).to be(AnotherAdaptor)
|
62
205
|
end
|
63
206
|
|
64
207
|
it 'allows the setting of the adaptor through init' do
|
65
|
-
adaptor =
|
66
|
-
|
67
|
-
expect(
|
68
|
-
expect(Muve::Model.handler).to be(adaptor)
|
208
|
+
Muve::Model.init(adaptor = SomeAdaptor)
|
209
|
+
expect(Resource::adaptor).to be(SomeAdaptor)
|
210
|
+
expect(Resource.adaptor).to be(SomeAdaptor)
|
69
211
|
end
|
70
212
|
|
71
213
|
it 'sets the attributes of the resource at initialization' do
|
@@ -82,11 +224,31 @@ describe 'Model' do
|
|
82
224
|
# TODO: Study if this is desirable perhaps one would rather prefer setting
|
83
225
|
# seperate adaptors for different models
|
84
226
|
it 'shares the adaptor amongst all its instances' do
|
85
|
-
|
86
|
-
|
227
|
+
Resource.remove_instance_variable(:@adaptor) if Resource.instance_variable_defined?(:@adaptor)
|
228
|
+
AnotherResource.remove_instance_variable(:@adaptor) if AnotherResource.instance_variable_defined?(:@adaptor)
|
229
|
+
|
230
|
+
Muve::Model.init(SomeAdaptor)
|
231
|
+
expect(Muve::Model.send(:handler)).to be(SomeAdaptor)
|
232
|
+
expect(Resource.new.send(:adaptor)).to be(SomeAdaptor)
|
233
|
+
expect(AnotherResource.new.send(:adaptor)).to be(SomeAdaptor)
|
234
|
+
|
235
|
+
Muve::Model.init(AnotherAdaptor)
|
236
|
+
expect(Muve::Model.send(:handler)).to be(AnotherAdaptor)
|
237
|
+
expect(Resource.new.send(:adaptor)).to be(AnotherAdaptor)
|
238
|
+
expect(AnotherResource.new.send(:adaptor)).to be(AnotherAdaptor)
|
239
|
+
end
|
87
240
|
|
88
|
-
|
89
|
-
|
241
|
+
it 'allows different adaptors for different entities' do
|
242
|
+
Resource.remove_instance_variable(:@adaptor) if Resource.instance_variable_defined?(:@adaptor)
|
243
|
+
AnotherResource.remove_instance_variable(:@adaptor) if AnotherResource.instance_variable_defined?(:@adaptor)
|
244
|
+
|
245
|
+
a = SomeAdaptor
|
246
|
+
b = AnotherAdaptor
|
247
|
+
|
248
|
+
Muve::Model.init(a)
|
249
|
+
Resource.adaptor = b
|
250
|
+
expect(Resource.new.send(:adaptor)).to be(b)
|
251
|
+
expect(AnotherResource.new.send(:adaptor)).to be(a)
|
90
252
|
end
|
91
253
|
|
92
254
|
describe 'equiped with an adaptor' do
|
@@ -105,13 +267,42 @@ describe 'Model' do
|
|
105
267
|
|
106
268
|
class GenericAdaptor
|
107
269
|
extend Muve::Store
|
270
|
+
|
271
|
+
def self.index_hash(index_value)
|
272
|
+
hash = { :_id => index_value }
|
273
|
+
{ :_id => index_value }
|
274
|
+
end
|
108
275
|
end
|
109
276
|
|
110
277
|
Resource.adaptor = GenericAdaptor
|
278
|
+
AnotherResource.adaptor = GenericAdaptor # FIX: allow setting system-wide adaptor?
|
111
279
|
|
112
280
|
@res = Resource.new
|
113
281
|
end
|
114
282
|
|
283
|
+
shared_examples "an ActiveRecord-like class" do
|
284
|
+
it { is_expected.to respond_to(:create) }
|
285
|
+
it { is_expected.to respond_to(:destroy_all) }
|
286
|
+
it { is_expected.to respond_to(:find) }
|
287
|
+
end
|
288
|
+
|
289
|
+
shared_examples "an ActiveRecord-like resource" do
|
290
|
+
it { is_expected.to respond_to(:save) }
|
291
|
+
it { is_expected.to respond_to(:destroy) }
|
292
|
+
it { is_expected.to respond_to(:new_record?) }
|
293
|
+
it { is_expected.to respond_to(:destroyed?) }
|
294
|
+
end
|
295
|
+
|
296
|
+
context "the class" do
|
297
|
+
subject { Resource }
|
298
|
+
it_behaves_like "an ActiveRecord-like class"
|
299
|
+
end
|
300
|
+
|
301
|
+
context "a instance" do
|
302
|
+
subject { @res }
|
303
|
+
it_behaves_like "an ActiveRecord-like resource"
|
304
|
+
end
|
305
|
+
|
115
306
|
it 'calls the store create handler upon save' do
|
116
307
|
expect(GenericAdaptor).to receive(:create).once
|
117
308
|
@res.save
|
@@ -167,6 +358,28 @@ describe 'Model' do
|
|
167
358
|
end
|
168
359
|
end
|
169
360
|
|
361
|
+
describe '::create' do
|
362
|
+
before(:each) do
|
363
|
+
allow(GenericAdaptor).to receive(:create).and_return(@id = SecureRandom.hex)
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'creates a new instance' do
|
367
|
+
attributes = { name: 'Bonobo' }
|
368
|
+
expect(Resource).to receive(:new).with(attributes).once
|
369
|
+
Resource.create(attributes)
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'calls the save handler' do
|
373
|
+
expect_any_instance_of(Resource).to receive(:save).once
|
374
|
+
Resource.create(name: 'Nice')
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'has the set attributes' do
|
378
|
+
resource = Resource.create(name: 'Monaco')
|
379
|
+
expect(resource.name).to eq('Monaco')
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
170
383
|
describe '#save' do
|
171
384
|
before(:each) do
|
172
385
|
@res.name = 'first'
|
@@ -232,7 +445,7 @@ describe 'Model' do
|
|
232
445
|
end
|
233
446
|
|
234
447
|
it 'calls the delete handler with the proper details' do
|
235
|
-
expect(GenericAdaptor).to receive(:delete).with(
|
448
|
+
expect(GenericAdaptor).to receive(:delete).with(AnotherResource, @id).once
|
236
449
|
@res.destroy
|
237
450
|
end
|
238
451
|
end
|
@@ -255,5 +468,53 @@ describe 'Model' do
|
|
255
468
|
expect(GenericAdaptor).to receive(:fetch).with(Resource, id, anything)
|
256
469
|
Resource.find(id)
|
257
470
|
end
|
471
|
+
|
472
|
+
it 'converts the attributes to saveable objects' do
|
473
|
+
expect(Resource.new(
|
474
|
+
name: 'Peter Griffin',
|
475
|
+
version: 'dumbass',
|
476
|
+
another: AnotherResource.new(
|
477
|
+
name: 'Brian Griffin',
|
478
|
+
version: 'the pretentious one',
|
479
|
+
description: 'Canine, liberal, writer',
|
480
|
+
age: 8
|
481
|
+
)
|
482
|
+
).send(:to_hash)).to eq(
|
483
|
+
name: 'Peter Griffin',
|
484
|
+
version: 'dumbass',
|
485
|
+
another: {
|
486
|
+
name: 'Brian Griffin',
|
487
|
+
version: 'the pretentious one',
|
488
|
+
description: 'Canine, liberal, writer',
|
489
|
+
age: 8
|
490
|
+
}
|
491
|
+
)
|
492
|
+
end
|
493
|
+
|
494
|
+
it 'converts the attributes to saveable objects' do
|
495
|
+
another = AnotherResource.new
|
496
|
+
another.send :populate, {
|
497
|
+
id: SecureRandom.hex,
|
498
|
+
name: 'Brian Griffin',
|
499
|
+
version: 'the pretentious one',
|
500
|
+
description: 'Canine, liberal, writer',
|
501
|
+
age: 8
|
502
|
+
}
|
503
|
+
expect(Resource.new(
|
504
|
+
name: 'Peter Griffin',
|
505
|
+
version: 'dumbass',
|
506
|
+
another: another
|
507
|
+
).send(:to_hash)).to eq(
|
508
|
+
name: 'Peter Griffin',
|
509
|
+
version: 'dumbass',
|
510
|
+
another: {
|
511
|
+
_id: another.id,
|
512
|
+
name: 'Brian Griffin',
|
513
|
+
version: 'the pretentious one',
|
514
|
+
description: 'Canine, liberal, writer',
|
515
|
+
age: 8
|
516
|
+
}
|
517
|
+
)
|
518
|
+
end
|
258
519
|
end
|
259
520
|
end
|
data/spec/movement_spec.rb
CHANGED
data/spec/store_spec.rb
CHANGED
data/spec/traveller_spec.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Muve::Traveller do
|
4
|
-
subject { Muve::Traveller
|
4
|
+
subject { build Muve::Traveller }
|
5
5
|
|
6
6
|
it { is_expected.to respond_to(:id) }
|
7
|
-
it { is_expected.to
|
7
|
+
it { is_expected.to be_valid }
|
8
8
|
|
9
9
|
context "linked to an existing traveller" do
|
10
|
-
subject {
|
10
|
+
subject {
|
11
|
+
res = build Muve::Traveller
|
12
|
+
res.send(:populate, ({ id: SecureRandom.uuid }))
|
13
|
+
res
|
14
|
+
}
|
15
|
+
|
11
16
|
it { is_expected.to be_valid }
|
12
17
|
end
|
18
|
+
|
19
|
+
it 'is invalid if the traveller is nameless' do
|
20
|
+
expect(build Muve::Traveller, name: nil).to be_invalid
|
21
|
+
end
|
13
22
|
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.0
|
4
|
+
version: 1.1.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-08-
|
11
|
+
date: 2014-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Basic helpers to be used with Muvement
|
14
14
|
email:
|