muve 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: