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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2c799eb30443546d3054c9dfb9b04069fa8ed21
4
- data.tar.gz: e47754d72235cafaaaf41e5075319e0b7aab1c84
3
+ metadata.gz: 86078fd9dbc468dc0837688bd28c9ffbad043681
4
+ data.tar.gz: 3de1f46815f7ec2cdb1511b8c24445277a06df7a
5
5
  SHA512:
6
- metadata.gz: 58c9bd11811f3b4181e8ff738963f275fffe8b36244d73a3013e0d5fdaaeef7bcdc8e1ca94d9d9f018995274e79680b6471388f0dd3d7dd556fb1f7b07eb7d55
7
- data.tar.gz: abd202a42a8d6da4ebac3409657f9239f4313656028ab64cac55657e57cfd6bf2a632dc02e3453426985232a33e6d811177c3c10948c2c6c077055c4183155ad
6
+ metadata.gz: b83a8681897f5ef214d6aaa3ed729972c38e263bd91e62fa12cc555775fa2cb6e413bad80911d704eb027c553d06c08e2e3ef9b9628fb1e2a5669d11a9a475db
7
+ data.tar.gz: b0fb6001de7b85bb2b60e9fea97f32466e5365d058bcfba4aa65548b1456fa4b14ec9202270c4c04bc4260c633ed2bc33b9a8d38475b1251ac261841d1251c57
data/lib/muve/errors.rb CHANGED
@@ -1,17 +1,19 @@
1
- module MuveError
2
- class MuveStandardError < StandardError; end
3
-
4
- class MuveIncompleteImplementation < MuveStandardError; end
5
-
6
- class MuveInvalidAttributes < MuveStandardError; end
7
-
8
- class MuveInvalidQuery < MuveStandardError; end
9
-
10
- class MuveNotConfigured < MuveStandardError; end
11
-
12
- class MuveNotFound < MuveStandardError; end
13
-
14
- class MuveValidationError < MuveStandardError; end
15
-
16
- class MuveSaveError < MuveStandardError; end
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
@@ -19,6 +19,14 @@ module Muve
19
19
  true
20
20
  end
21
21
 
22
+ def latitude=(value)
23
+ @latitude = value.to_f
24
+ end
25
+
26
+ def longitude=(value)
27
+ @longitude = value.to_f
28
+ end
29
+
22
30
  def random(center, range)
23
31
  end
24
32
  end
data/lib/muve/model.rb CHANGED
@@ -13,7 +13,7 @@ module Muve
13
13
  # implementation
14
14
  # ++
15
15
  module Model
16
- include MuveError
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 MuveAssocError, "#Associated #{v.class} for #{k} must respond to #to_hash or be a Hash") unless v.kind_of? Hash
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 MuveSaveError on failure
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
- e.backtrace.each { |err| p err }
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
- raise MuveValidationError, "validation failed" unless valid?
77
- create_or_update
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 serialized_attributes
138
- to_hash
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(serialized_attributes) : update(serialized_attributes)
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 MuveError
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 MuveNotConfigured, "the adaptor has not been set" unless (@adaptor || Model.handler)
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 MuveError::MuveNotConfigured, "container not defined for #{self}"
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(resource.class.adaptor.index_hash id)
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(attributes)
264
- resource = self.new(attributes)
265
- resource.save if resource
266
- resource
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
@@ -0,0 +1,12 @@
1
+ module Muve
2
+ class Place
3
+ include Model
4
+
5
+ with_fields :name, :location
6
+
7
+ def valid?
8
+ return false unless location.kind_of?(Muve::Location) && location.valid?
9
+ true
10
+ end
11
+ end
12
+ end
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 MuveError
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 MuveInvalidQuery unless id || details
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 MuveIncompleteImplementation, "implement a create handler for #{self}"
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 MuveIncompleteImplementation, "implement a delete handler for #{self}"
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 MuveIncompleteImplementation, "implement a update handler for #{self}"
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 MuveIncompleteImplementation, "implement a fetch handler for #{self}"
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 MuveIncompleteImplementation, "implement a find handler for #{self}"
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 MuveIncompleteImplementation, "implement a count handler for #{self}"
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
@@ -1,3 +1,3 @@
1
1
  module Muve
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
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 MuveNotConfigured, "the connection has not been defined"
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 MuveNotConfigured, "the database has not been defined"
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
@@ -13,4 +13,9 @@ FactoryGirl.define do
13
13
  location { build Muve::Location }
14
14
  time { Time.now - rand(500000) }
15
15
  end
16
+
17
+ factory Muve::Place do
18
+ name { Faker::Venue.name }
19
+ location { build Muve::Location }
20
+ end
16
21
  end
@@ -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(:serialized_attributes)).to eq(
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(:serialized_attributes)).to eq(
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 = MuveError::MuveNotConfigured
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 = MuveError::MuveNotConfigured
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 'converts the attributes to saveable objects' do
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 'converts the attributes to saveable objects' do
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
- _id: another.id,
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
@@ -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(MuveError::MuveIncompleteImplementation)
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.1.0
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-08-26 00:00:00.000000000 Z
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