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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 83efb234b5e0adc0d47b9d3d6263e03068a35631
4
- data.tar.gz: 45802b059dcb15e55f077e6530df7268e21c3e08
3
+ metadata.gz: d2c799eb30443546d3054c9dfb9b04069fa8ed21
4
+ data.tar.gz: e47754d72235cafaaaf41e5075319e0b7aab1c84
5
5
  SHA512:
6
- metadata.gz: 5fbcbd69dd00b52e288a46d6c0d61db502f9e655bb6e2437aea3ac799c4863ee2cb86ef1b029a8161697d29f81147d0746aa12b542b0c2cf0a9c4d2a40aed96d
7
- data.tar.gz: b8717958986af1366ede7c2e22676c0d6aa8bd92fd94a7c89c76c99c10369775247b7bbe0474e7cfa604c714624d470e96b29ec6b0776ca526bc6fe5eabbf992
6
+ metadata.gz: 58c9bd11811f3b4181e8ff738963f275fffe8b36244d73a3013e0d5fdaaeef7bcdc8e1ca94d9d9f018995274e79680b6471388f0dd3d7dd556fb1f7b07eb7d55
7
+ data.tar.gz: abd202a42a8d6da4ebac3409657f9239f4313656028ab64cac55657e57cfd6bf2a632dc02e3453426985232a33e6d811177c3c10948c2c6c077055c4183155ad
@@ -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 # can't automatically infer the db as this may differ among adaptors
69
+ Model.connection = connection if connection
70
+ Model.database = database if database
71
71
  end
72
72
  end
@@ -1,19 +1,17 @@
1
1
  module MuveError
2
- class MuveStandardError < StandardError
3
- end
2
+ class MuveStandardError < StandardError; end
4
3
 
5
- class MuveSaveError < MuveStandardError
6
- end
4
+ class MuveIncompleteImplementation < MuveStandardError; end
7
5
 
8
- class MuveInvalidQuery < MuveStandardError
9
- end
6
+ class MuveInvalidAttributes < MuveStandardError; end
10
7
 
11
- class MuveInvalidAttributes < MuveStandardError
12
- end
8
+ class MuveInvalidQuery < MuveStandardError; end
13
9
 
14
- class MuveIncompleteImplementation < MuveStandardError
15
- end
10
+ class MuveNotConfigured < MuveStandardError; end
16
11
 
17
- class MuveNotConfigured < MuveStandardError
18
- end
12
+ class MuveNotFound < MuveStandardError; end
13
+
14
+ class MuveValidationError < MuveStandardError; end
15
+
16
+ class MuveSaveError < MuveStandardError; end
19
17
  end
@@ -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
@@ -2,29 +2,23 @@ module Muve
2
2
  class Location
3
3
  include Model
4
4
 
5
- attr_accessor :latitude, :longitude
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
@@ -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(adaptor=nil)
37
- @@adaptor = adaptor
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
- @@adaptor
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.container, id) == true
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(attributes) : update(attributes)
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
- data = {}
124
- fields.select{ |k| k != invalid_attributes }.each { |k|
125
- data[k.to_sym] = self.public_send(k)
126
- }
127
- data
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.container, attr)
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.container, id, attr)
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
- @@adaptor = adaptor
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
- @@adaptor
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
@@ -2,17 +2,13 @@ module Muve
2
2
  class Movement
3
3
  include Model
4
4
 
5
- attr_accessor :traveller, :traveller_id, :location, :time
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
- flds.each do |field|
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
@@ -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
@@ -1,15 +1,11 @@
1
1
  module Muve
2
2
  class Traveller
3
3
  include Model
4
-
5
- attr_accessor :id
6
-
7
- def initialize(id=nil)
8
- @id = id
9
- end
4
+
5
+ with_fields :name
10
6
 
11
7
  def valid?
12
- !id.nil?
8
+ !name.nil? && !name.empty?
13
9
  end
14
10
  end
15
11
  end
@@ -1,3 +1,3 @@
1
1
  module Muve
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -5,12 +5,12 @@ FactoryGirl.define do
5
5
  end
6
6
 
7
7
  factory Muve::Traveller do
8
- id { SecureRandom.uuid }
8
+ name { Faker::Name.name }
9
9
  end
10
10
 
11
11
  factory Muve::Movement do
12
- traveller { build(Muve::Traveller) }
13
- #traveller_id { SecureRandom.uuid }
14
- location { build(Muve::Location) }
12
+ traveller { build Muve::Traveller }
13
+ location { build Muve::Location }
14
+ time { Time.now - rand(500000) }
15
15
  end
16
16
  end
@@ -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(latitude, longitude) }
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
- it { expect { subject.latitude = new_latitude }.to change{subject.latitude}.to(new_latitude) }
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.new(-91, longitude)).to be_invalid
24
- expect(Muve::Location.new( 91, longitude)).to be_invalid
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.new(latitude, -181)).to be_invalid
29
- expect(Muve::Location.new(latitude, 181)).to be_invalid
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
@@ -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
- attr_accessor :name, :version
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
- attr_accessor :name, :version, :description
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
- private
26
- def fields
27
- [:name, :version, :description]
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
- skip # TODO: get rid of Singleton-like pattern?
40
- expect {
41
- Muve::Model.connection
42
- }.to raise_error(MuveError::MuveNotConfigured)
43
-
44
- expect {
45
- Resource.connection
46
- }.to raise_error(MuveError::MuveNotConfigured)
47
-
48
- expect {
49
- AnotherResource.connection
50
- }.to raise_error(MuveError::MuveNotConfigured)
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 = Object.new
60
- Resource.adaptor = adaptor
61
- expect(Resource.adaptor).to be(adaptor)
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 = Object.new
66
- Muve::Model.init(adaptor = adaptor)
67
- expect(Muve::Model::handler).to be(adaptor)
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
- generic_adaptor = Object.new
86
- Resource.adaptor = generic_adaptor
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
- expect(Resource.new.send(:adaptor)).to be(generic_adaptor)
89
- expect(AnotherResource.new.send(:adaptor)).to be(generic_adaptor)
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('other_resources', @id).once
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
@@ -28,7 +28,7 @@ describe Muve::Movement do
28
28
  end
29
29
 
30
30
  context "new movement" do
31
- subject { build(Muve::Movement) }
31
+ subject { build(Muve::Movement, time: Time.now) }
32
32
  it { expect(subject.time).to be_within(2).of(Time.now) }
33
33
  it { is_expected.to be_valid }
34
34
  end
@@ -2,6 +2,7 @@ describe Muve::Store do
2
2
  before do
3
3
  class Resource
4
4
  include Muve::Model
5
+ with_fields :name
5
6
 
6
7
  def self.container
7
8
  'resources'
@@ -1,13 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Muve::Traveller do
4
- subject { Muve::Traveller.new }
4
+ subject { build Muve::Traveller }
5
5
 
6
6
  it { is_expected.to respond_to(:id) }
7
- it { is_expected.to be_invalid }
7
+ it { is_expected.to be_valid }
8
8
 
9
9
  context "linked to an existing traveller" do
10
- subject { Muve::Traveller.new(SecureRandom.uuid) }
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.1
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-06 00:00:00.000000000 Z
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: