muve 0.0.1 → 0.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/.gitignore +1 -0
- data/Gemfile +11 -1
- data/Rakefile +6 -0
- data/lib/muve/errors.rb +14 -2
- data/lib/muve/helpers.rb +7 -0
- data/lib/muve/model.rb +176 -12
- data/lib/muve/store/mongo.rb +50 -0
- data/lib/muve/store.rb +81 -0
- data/lib/muve/version.rb +1 -1
- data/lib/muve.rb +39 -3
- data/muve.gemspec +1 -5
- data/spec/adaptor/mongo_spec.rb +52 -0
- data/spec/helper_spec.rb +11 -0
- data/spec/model_spec.rb +251 -0
- data/spec/movement_spec.rb +4 -4
- data/spec/muve_spec.rb +29 -0
- data/spec/store_spec.rb +62 -0
- metadata +16 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07fd84a42ff9d48763eb97e0991f3b29c1922a5a
|
4
|
+
data.tar.gz: bae3ee756eb66eff4539fed0fcd717ca1bde469a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c864989ed5a970291c4a794e503553def1dcf813c92d7d8700858434a8170e51c3183cbd9ad0e0fa43651299e6f77a4887e74948bc04704a10140af0c1bbb0f4
|
7
|
+
data.tar.gz: 0bb9e70a4e32a2ae7c44d07689b433f67074353cf508201cc16f45b1c9aec7861cf785c54fa61ccd6e8fb7f6f094bea11d9e96100e0f8a3b261213c5a6247b10
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in muve.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
gem "rake", "~> 10.3.2"
|
6
|
+
gem "bundler", "~> 1.6"
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem "mongo", "~> 1.10.2"
|
10
|
+
gem "bson_ext"
|
11
|
+
gem "ffaker", "~> 1.24.0"
|
12
|
+
gem "factory_girl", "~> 4.0.0"
|
13
|
+
gem "rspec", "~> 3.0.0"
|
14
|
+
end
|
data/Rakefile
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
require "rspec/core/rake_task"
|
2
2
|
require "rake/testtask"
|
3
3
|
require "bundler/gem_tasks"
|
4
|
+
require "rdoc/task"
|
4
5
|
|
5
6
|
RSpec::Core::RakeTask.new('spec') do |t|
|
6
7
|
t.verbose = false
|
7
8
|
end
|
8
9
|
|
10
|
+
RDoc::Task.new do |rdoc|
|
11
|
+
rdoc.main = "README.rdoc"
|
12
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
13
|
+
end
|
14
|
+
|
9
15
|
task default: :spec
|
data/lib/muve/errors.rb
CHANGED
@@ -1,7 +1,19 @@
|
|
1
1
|
module MuveError
|
2
|
-
class
|
2
|
+
class MuveStandardError < StandardError
|
3
3
|
end
|
4
4
|
|
5
|
-
class MuveSaveError <
|
5
|
+
class MuveSaveError < MuveStandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class MuveInvalidQuery < MuveStandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class MuveInvalidAttributes < MuveStandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class MuveIncompleteImplementation < MuveStandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class MuveNotConfigured < MuveStandardError
|
6
18
|
end
|
7
19
|
end
|
data/lib/muve/helpers.rb
ADDED
data/lib/muve/model.rb
CHANGED
@@ -1,40 +1,204 @@
|
|
1
1
|
module Muve
|
2
|
+
# Muve models imitate some behavior that one may commonly expect of typical
|
3
|
+
# models. There are mechanisms in place for creation, modification, retrieval
|
4
|
+
# and the removal of resources.
|
5
|
+
#
|
6
|
+
# In order to make this a flexible solution, models never take care of
|
7
|
+
# handling the persisting of the resources they manange. That part of the
|
8
|
+
# lifing is dispatched to the +Model::Store+ object which may be though of
|
9
|
+
# as an adaptor.
|
10
|
+
#
|
11
|
+
# --
|
12
|
+
# TODO: Include ActiveRecord::Model instead of using this tedious
|
13
|
+
# implementation
|
14
|
+
# ++
|
2
15
|
module Model
|
3
16
|
include MuveError
|
4
|
-
|
5
|
-
|
17
|
+
include Muve::Helper
|
18
|
+
|
19
|
+
def initialize(params={})
|
20
|
+
params = {} unless params
|
21
|
+
params.each do |attr, value|
|
22
|
+
next if invalid_attributes.include? attr.to_s
|
23
|
+
self.public_send "#{attr}=", value
|
24
|
+
end
|
25
|
+
|
6
26
|
@new_record = true
|
27
|
+
@destroyed = false
|
7
28
|
end
|
8
|
-
|
29
|
+
|
30
|
+
def self.included(base)
|
31
|
+
base.extend ClassMethods
|
32
|
+
end
|
33
|
+
|
34
|
+
# Initializes the +Muve::Model+ class. Use the +Muve::Model::init+ method
|
35
|
+
# to set a adaptor to take care of the retrieval and storage of resources.
|
36
|
+
def self.init(adaptor=nil)
|
37
|
+
@@adaptor = adaptor
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the adaptor set to handle retrieval and storage of resources
|
41
|
+
def self.handler
|
42
|
+
@@adaptor
|
43
|
+
end
|
44
|
+
|
45
|
+
# Save a resource and raises an MuveSaveError on failure
|
9
46
|
def save!
|
10
|
-
create_or_update
|
47
|
+
create_or_update
|
48
|
+
rescue => e
|
49
|
+
raise MuveSaveError, "Save failed because #{e} was raised"
|
11
50
|
end
|
12
51
|
|
52
|
+
# Save a resource
|
13
53
|
def save
|
14
54
|
create_or_update
|
55
|
+
rescue => e
|
56
|
+
false
|
15
57
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
58
|
+
|
59
|
+
# Destroy a resource
|
60
|
+
def destroy
|
61
|
+
if adaptor.delete(container, id) == true
|
62
|
+
@destroyed = true
|
63
|
+
end
|
21
64
|
end
|
22
|
-
|
65
|
+
|
66
|
+
# Returns true if the resource has recently been instantiated but not yet
|
67
|
+
# written to the data store.
|
23
68
|
def new_record?
|
24
|
-
@new_record
|
69
|
+
@new_record = true if @new_record.nil?
|
70
|
+
@new_record
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns true if the resource is not newly instantiated or recently
|
74
|
+
# destroyed.
|
75
|
+
def persisted?
|
76
|
+
!(new_record? || destroyed?)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns true if the resource in question has been destroyed
|
80
|
+
def destroyed?
|
81
|
+
@destroyed
|
25
82
|
end
|
26
83
|
|
84
|
+
# Returns a true if the resource passes all validations
|
27
85
|
def valid?
|
28
86
|
false
|
29
87
|
end
|
30
88
|
|
89
|
+
# Returns true if the resource fails any validation
|
31
90
|
def invalid?
|
32
91
|
!valid?
|
33
92
|
end
|
93
|
+
|
94
|
+
def id
|
95
|
+
@id
|
96
|
+
end
|
34
97
|
|
35
98
|
private
|
99
|
+
def invalid_attributes
|
100
|
+
%w(id adaptor)
|
101
|
+
end
|
102
|
+
|
103
|
+
def populate(details)
|
104
|
+
details = {} unless details
|
105
|
+
|
106
|
+
@id = details[:id] if details.key? :id
|
107
|
+
|
108
|
+
details.each do |attr, value|
|
109
|
+
next if invalid_attributes.include? attr.to_s
|
110
|
+
self.public_send "#{attr}=", value
|
111
|
+
end
|
112
|
+
|
113
|
+
@new_record = false if details.key? :id
|
114
|
+
end
|
115
|
+
|
36
116
|
def create_or_update
|
37
|
-
result = new_record? ? create : update
|
117
|
+
result = new_record? ? create(attributes) : update(attributes)
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
# NOTE: not sure we need this
|
122
|
+
def attributes
|
123
|
+
data = {}
|
124
|
+
fields.each { |k| data[k.to_sym] = self.public_send(k) }
|
125
|
+
data
|
126
|
+
end
|
127
|
+
|
128
|
+
# Creates the record and performs the necessary housekeeping (e.g.: setting
|
129
|
+
# the new id and un-marking the new_record?
|
130
|
+
def create(attr)
|
131
|
+
@id = adaptor.create(container, attr)
|
132
|
+
@new_record = false
|
133
|
+
end
|
134
|
+
|
135
|
+
# TODO: Update the record and return the number of modified rows
|
136
|
+
def update(attr)
|
137
|
+
adaptor.update(container, id, attr)
|
138
|
+
end
|
139
|
+
|
140
|
+
def adaptor
|
141
|
+
self.class.adaptor
|
142
|
+
end
|
143
|
+
|
144
|
+
def container
|
145
|
+
end
|
146
|
+
|
147
|
+
def details
|
148
|
+
end
|
149
|
+
|
150
|
+
# Class methods exposed to all Muve models
|
151
|
+
module ClassMethods
|
152
|
+
# Configure the adaptor to take care of handling persistence for this
|
153
|
+
# model. The adaptor should extend +Muve::Store+.
|
154
|
+
#
|
155
|
+
# Adaptors provide an abstraction layer between Muve models and the actual
|
156
|
+
# datastore. This provides some flexibility in design as one may exchange
|
157
|
+
# an adaptor for another in order to support another database technology
|
158
|
+
# (.e.g: swithing between document databases or relational databases)
|
159
|
+
def adaptor=(adaptor)
|
160
|
+
@@adaptor = adaptor
|
161
|
+
end
|
162
|
+
|
163
|
+
# The adaptor currently set to handle persistence for all Muve::Model
|
164
|
+
# classes and instances
|
165
|
+
def adaptor
|
166
|
+
@@adaptor
|
167
|
+
end
|
168
|
+
|
169
|
+
def connection
|
170
|
+
Muve::Model.connection
|
171
|
+
end
|
172
|
+
|
173
|
+
def database
|
174
|
+
Muve::Model.database
|
175
|
+
end
|
176
|
+
|
177
|
+
# The container (e.g.: collection, tablename or anything that is analogous
|
178
|
+
# to this construct) of the resource
|
179
|
+
def container
|
180
|
+
raise MuveError::MuveNotConfigured, "container not defined for #{self}"
|
181
|
+
end
|
182
|
+
|
183
|
+
# Finds a resource by id
|
184
|
+
def find(id)
|
185
|
+
result = self.new()
|
186
|
+
result.send(:populate, self.adaptor.get(self, id))
|
187
|
+
result
|
188
|
+
end
|
189
|
+
|
190
|
+
# Querries the resource repository for all resources that match the
|
191
|
+
# specified parameters.
|
192
|
+
def where(params)
|
193
|
+
Enumerator.new do |item|
|
194
|
+
(self.adaptor.get(self, nil, params) or []).each do |details|
|
195
|
+
details
|
196
|
+
result = self.new()
|
197
|
+
result.send(:populate, details)
|
198
|
+
item << result
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
38
202
|
end
|
39
203
|
end
|
40
204
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Muve
|
2
|
+
module Store
|
3
|
+
class Mongo
|
4
|
+
require 'mongo'
|
5
|
+
|
6
|
+
extend Muve::Store
|
7
|
+
|
8
|
+
def self.create(resource, details)
|
9
|
+
raise MuveInvalidAttributes, "invalid update data" unless details.kind_of? Hash
|
10
|
+
resource.database[resource.container].insert(details)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.fetch(resource, id, details={})
|
14
|
+
# TODO: discover a solution that works for situations where database
|
15
|
+
# driver returns string keys as well as symbol keys
|
16
|
+
details = {} unless details.kind_of? Hash
|
17
|
+
result = resource.database[resource.container].find_one(details.merge(_id: id))
|
18
|
+
result = Helper.symbolize_keys(result)
|
19
|
+
result[:id] = result.delete(:_id)
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.find(resource, details)
|
24
|
+
details = {} unless details.kind_of? Hash
|
25
|
+
Enumerator.new do |result|
|
26
|
+
resource.database[resource.container].find(details).each do |item|
|
27
|
+
item = Helper.symbolize_keys(item)
|
28
|
+
item[:id] = item.delete(:_id)
|
29
|
+
result << item
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.update(resource, id, details)
|
35
|
+
raise MuveInvalidAttributes, "invalid update data" unless details.kind_of? Hash
|
36
|
+
# TODO: raise error if details is not valid
|
37
|
+
resource.database[resource.container].find_and_modify(
|
38
|
+
query: { _id: id },
|
39
|
+
update: details
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.delete(resource, id, details=nil)
|
44
|
+
details = {} unless details.kind_of? Hash
|
45
|
+
details = details.merge(_id: id) if id
|
46
|
+
resource.database[resource.container].remove(details)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/muve/store.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'muve/store/mongo'
|
2
|
+
|
3
|
+
module Muve
|
4
|
+
# Muve::Store takes care of resource persistence and retrieval. Use stores
|
5
|
+
# as adaptors to connect your implementation of Muve to whichever datastore
|
6
|
+
# you please.
|
7
|
+
#
|
8
|
+
# Adaptors or Stores only take of the interaction with the datastore but
|
9
|
+
# leave the model's housekeeping up the respective model. Make sure to
|
10
|
+
# conform to the expected return formats for the different adaptor methods.
|
11
|
+
#
|
12
|
+
# Take a look at +Muve::Store::Mongo+ to find an implementation of a store
|
13
|
+
# adaptor.
|
14
|
+
module Store
|
15
|
+
include MuveError
|
16
|
+
include Muve::Helper
|
17
|
+
|
18
|
+
# gets data from the given container matching the provided details
|
19
|
+
#
|
20
|
+
# Given a +Place+ resource the following calls may be acceptable
|
21
|
+
# - +Adaptor.get(Place, 1232) # find one resource where id = 1232+
|
22
|
+
# - +Adaptor.get(Place, nil, { city: 'NYC', rating: 5 })+ # find more
|
23
|
+
def get(resource, id=nil, details=nil)
|
24
|
+
raise MuveInvalidQuery unless id || details
|
25
|
+
|
26
|
+
if details
|
27
|
+
find(resource, details)
|
28
|
+
else
|
29
|
+
fetch(resource, id, {})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# creates a resource containing the specified details in the repository.
|
34
|
+
# Returns the id of the created object on success, raises an error otherwise
|
35
|
+
def create(resource, details)
|
36
|
+
raise MuveIncompleteImplementation, "implement a create handler for #{self}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# removes a resource matching the optional +id+ and +details+
|
40
|
+
# from the repository.
|
41
|
+
# A successful removal operation should returns +true+ while any other
|
42
|
+
# value is considered an error.
|
43
|
+
def delete(resource, id, details=nil)
|
44
|
+
raise MuveIncompleteImplementation, "implement a delete handler for #{self}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# update a resource with the identified by +id+ with the given +details+
|
48
|
+
def update(resource, id, details)
|
49
|
+
raise MuveIncompleteImplementation, "implement a update handler for #{self}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# collect a single resource from the repository that matches the given id
|
53
|
+
# and details. Upon the successful retrieval of a resource the id of the
|
54
|
+
# resource is presented under the key +id+ while other attributes of the
|
55
|
+
# resource bear arbitrary names.
|
56
|
+
#
|
57
|
+
# { id: 12, name: 'Spock', organization: 'The Enterprise' }
|
58
|
+
def fetch(resource, id, details={})
|
59
|
+
raise MuveIncompleteImplementation, "implement a fetch handler for #{self}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# find resources from its repository that match the given id and details
|
63
|
+
# Returns an +Enumerator+ that returns a hash with the key +id+ containing
|
64
|
+
# the primary key for the respective resource.
|
65
|
+
#
|
66
|
+
# def find(resource, details)
|
67
|
+
# details = {} unless details.kind_of? Hash
|
68
|
+
# Enumerator.new do |item|
|
69
|
+
# fetched_result_from_datastore.each do |data|
|
70
|
+
# item << format_data(data) # format_data composes the required hash
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
def find(resource, details)
|
75
|
+
raise MuveIncompleteImplementation, "implement a find handler for #{self}"
|
76
|
+
end
|
77
|
+
|
78
|
+
alias_method :destroy, :delete
|
79
|
+
alias_method :remove, :delete
|
80
|
+
end
|
81
|
+
end
|
data/lib/muve/version.rb
CHANGED
data/lib/muve.rb
CHANGED
@@ -15,22 +15,58 @@ require "muve/version"
|
|
15
15
|
# times to improve its usability and implementation.
|
16
16
|
module Muve
|
17
17
|
require "muve/errors"
|
18
|
+
require "muve/helpers"
|
18
19
|
require "muve/model"
|
20
|
+
require "muve/store"
|
19
21
|
require "muve/location"
|
20
22
|
require "muve/traveller"
|
21
23
|
require "muve/movement"
|
22
24
|
|
23
25
|
module Model
|
26
|
+
# Connection to the datastore
|
24
27
|
def connection
|
25
28
|
Model.connection
|
26
29
|
end
|
30
|
+
|
31
|
+
# Database instance for the model
|
32
|
+
def database
|
33
|
+
Model.database
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set the connection to the datastore
|
37
|
+
def self.connection=connection
|
38
|
+
(@@conn = connection) if (connection)
|
39
|
+
end
|
27
40
|
|
41
|
+
def self.database=database
|
42
|
+
(@@db = database) if (database)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Connection to the datastore
|
28
46
|
def self.connection
|
29
|
-
|
47
|
+
begin
|
48
|
+
@@conn
|
49
|
+
rescue => e
|
50
|
+
raise MuveNotConfigured, "the connection has not been defined"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Database instance to be used by the adaptor
|
55
|
+
def self.database
|
56
|
+
begin
|
57
|
+
@@db
|
58
|
+
rescue => e
|
59
|
+
raise MuveNotConfigured, "the database has not been defined"
|
60
|
+
end
|
30
61
|
end
|
31
62
|
end
|
32
63
|
|
33
|
-
|
34
|
-
|
64
|
+
# Initialize Muve with an optional connection to the datastore.
|
65
|
+
# This could be a MongoDB or PostgreSQL connection for instance. Besides a
|
66
|
+
# connection, an adaptor will be needed to actually handle the interaction
|
67
|
+
# between the models and the datastore through the given connection.
|
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
|
35
71
|
end
|
36
72
|
end
|
data/muve.gemspec
CHANGED
@@ -18,11 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_development_dependency "bundler", "~> 1.6"
|
22
|
-
s.add_development_dependency "rake"
|
23
|
-
s.add_development_dependency "rspec", "~> 3.0.0"
|
24
|
-
s.add_development_dependency "ffaker"
|
25
|
-
s.add_development_dependency "factory_girl", "~> 4.0"
|
21
|
+
#s.add_development_dependency "bundler", "~> 1.6"
|
26
22
|
|
27
23
|
s.required_ruby_version = '>= 1.9.2'
|
28
24
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Mongo Adaptor' do
|
4
|
+
let(:connection) { Mongo::MongoClient.new }
|
5
|
+
let(:database) { connection.db('muve_test') }
|
6
|
+
before do
|
7
|
+
class Place
|
8
|
+
include Muve::Model
|
9
|
+
|
10
|
+
def self.container
|
11
|
+
'places'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Muve.init(connection, database)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'writes model data to the store' do
|
19
|
+
expect{
|
20
|
+
Muve::Store::Mongo.create(Place, {
|
21
|
+
city: Faker::Address.city,
|
22
|
+
street: Faker::Address.street_name,
|
23
|
+
building: Faker::Address.building_number
|
24
|
+
})
|
25
|
+
}.to change{database['places'].count}.by(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'writes modifications to the store' do
|
29
|
+
id = database['places'].insert(name: Faker::Venue.name)
|
30
|
+
new_name = Faker::Venue.name
|
31
|
+
|
32
|
+
expect{
|
33
|
+
Muve::Store::Mongo.update(Place, id, { name: new_name })
|
34
|
+
}.to change{database['places'].find_one(_id: id)['name']}.to(new_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'finds a resource from store' do
|
38
|
+
id = database['places'].insert(name: Faker::Venue.name)
|
39
|
+
expect(Muve::Store::Mongo.get(Place, id)[:id]).to eq(id)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'finds multiple resources from store' do
|
43
|
+
expect(Muve::Store::Mongo.find(Place, {})).to be_a(Enumerable)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'extracts a resource from every result in a multiple resource set' do
|
47
|
+
Muve::Store::Mongo.find(Place, {}).take(3).each do |result|
|
48
|
+
attributes = result.keys.map{ |i| i.to_s }
|
49
|
+
expect(attributes).to include('id', 'city', 'street', 'building')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/helper_spec.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
describe Muve::Helper do
|
2
|
+
describe '#symbolize_keys' do
|
3
|
+
it 'converts all keys to symbols' do
|
4
|
+
hash = { 'name' => 'Stewie Griffin', :skill => 'Physics' }
|
5
|
+
expect(Muve::Helper.symbolize_keys(hash)).to eq(
|
6
|
+
name: 'Stewie Griffin',
|
7
|
+
skill: 'Physics'
|
8
|
+
)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/spec/model_spec.rb
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
describe 'Model' do
|
2
|
+
before do
|
3
|
+
class Resource
|
4
|
+
include Muve::Model
|
5
|
+
attr_accessor :name, :version
|
6
|
+
|
7
|
+
def self.container
|
8
|
+
'resources'
|
9
|
+
end
|
10
|
+
|
11
|
+
def fields
|
12
|
+
[:name, :version]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class AnotherResource
|
17
|
+
include Muve::Model
|
18
|
+
attr_accessor :name, :version, :description
|
19
|
+
|
20
|
+
def self.container
|
21
|
+
'other_resources'
|
22
|
+
end
|
23
|
+
|
24
|
+
def fields
|
25
|
+
[:name, :version, :description]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'remembers its connection' do
|
31
|
+
object = Object.new
|
32
|
+
Muve::Model.connection = object
|
33
|
+
expect(Muve::Model.connection).to be(object)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'raises a not configured exception when connection is not set' do
|
37
|
+
skip # TODO: get rid of Singleton-like pattern?
|
38
|
+
expect {
|
39
|
+
Muve::Model.connection
|
40
|
+
}.to raise_error(MuveError::MuveNotConfigured)
|
41
|
+
|
42
|
+
expect {
|
43
|
+
Resource.connection
|
44
|
+
}.to raise_error(MuveError::MuveNotConfigured)
|
45
|
+
|
46
|
+
expect {
|
47
|
+
AnotherResource.connection
|
48
|
+
}.to raise_error(MuveError::MuveNotConfigured)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'knows the identifier of its repository' do
|
52
|
+
expect(Resource).to respond_to(:container)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'allows the setting of the adaptor' do
|
56
|
+
adaptor = Object.new
|
57
|
+
Resource.adaptor = adaptor
|
58
|
+
expect(Resource.adaptor).to be(adaptor)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'allows the setting of the adaptor through init' do
|
62
|
+
adaptor = Object.new
|
63
|
+
Muve::Model.init(adaptor = adaptor)
|
64
|
+
expect(Muve::Model::handler).to be(adaptor)
|
65
|
+
expect(Muve::Model.handler).to be(adaptor)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sets the attributes of the resource at initialization' do
|
69
|
+
resource = Resource.new(name: 'muve-resource', version: 0)
|
70
|
+
expect(resource.name).to eq('muve-resource')
|
71
|
+
expect(resource.version).to eq(0)
|
72
|
+
|
73
|
+
another = AnotherResource.new(name: 'muve-something', version: nil, description: 'blah')
|
74
|
+
expect(another.name).to eq('muve-something')
|
75
|
+
expect(another.version).to eq(nil)
|
76
|
+
expect(another.description).to eq('blah')
|
77
|
+
end
|
78
|
+
|
79
|
+
# TODO: Study if this is desirable perhaps one would rather prefer setting
|
80
|
+
# seperate adaptors for different models
|
81
|
+
it 'shares the adaptor amongst all its instances' do
|
82
|
+
generic_adaptor = Object.new
|
83
|
+
Resource.adaptor = generic_adaptor
|
84
|
+
|
85
|
+
expect(Resource.new.send(:adaptor)).to be(generic_adaptor)
|
86
|
+
expect(AnotherResource.new.send(:adaptor)).to be(generic_adaptor)
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'equiped with an adaptor' do
|
90
|
+
before do
|
91
|
+
class Resource
|
92
|
+
include Muve::Model
|
93
|
+
|
94
|
+
def valid?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_as_new_record(bool)
|
99
|
+
@new_record = bool
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class GenericAdaptor
|
104
|
+
extend Muve::Store
|
105
|
+
end
|
106
|
+
|
107
|
+
Resource.adaptor = GenericAdaptor
|
108
|
+
|
109
|
+
@res = Resource.new
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'calls the store create handler upon save' do
|
113
|
+
expect(GenericAdaptor).to receive(:create).once
|
114
|
+
@res.save
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#find' do
|
118
|
+
before(:each) do
|
119
|
+
@id = SecureRandom.hex
|
120
|
+
allow(GenericAdaptor).to receive(:fetch).and_return({
|
121
|
+
id: @id,
|
122
|
+
name: 'Smile',
|
123
|
+
version: '0',
|
124
|
+
description: 'Supermodel smile... at least that is what they said'
|
125
|
+
})
|
126
|
+
allow(GenericAdaptor).to receive(:find).and_return(Enumerator.new { |y|
|
127
|
+
5.times {
|
128
|
+
y << { id: 12, name: 'Something', version: 1, description: 'haha' }
|
129
|
+
}
|
130
|
+
})
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'returns an instance of the model' do
|
134
|
+
expect(AnotherResource.find(@id)).to be_a(AnotherResource)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'returns an object containing the record data' do
|
138
|
+
result = AnotherResource.find(@id)
|
139
|
+
expect(result.name).to eq('Smile')
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'returns a record that is not a new record' do
|
143
|
+
expect(AnotherResource.find(@id).new_record?).to be(false)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe '#where' do
|
148
|
+
before(:each) do
|
149
|
+
allow(GenericAdaptor).to receive(:find).and_return(Enumerator.new { |y|
|
150
|
+
5.times {
|
151
|
+
y << { id: 12, name: 'Something', version: 1, description: 'ahha' }
|
152
|
+
}
|
153
|
+
})
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'returns the complete result set' do
|
157
|
+
expect(AnotherResource.where({ name: 'all' }).count).to eq(5)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'returns a instance of the resource for each item' do
|
161
|
+
AnotherResource.where({ name: 'all' }).each do |item|
|
162
|
+
expect(item).to be_a(AnotherResource)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '#save' do
|
168
|
+
before(:each) do
|
169
|
+
@res.name = 'first'
|
170
|
+
allow(GenericAdaptor).to receive(:create).and_return(@id = SecureRandom.hex)
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'on a new record' do
|
174
|
+
it 'returns an instance of itself' do
|
175
|
+
expect(@res.save).to be_a(Resource)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'obtains an id' do
|
179
|
+
expect { @res.save }.to change{ @res.id }.to(@id)
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'is no longer a new record' do
|
183
|
+
expect{ @res.save }.to change{ @res.new_record? }.to(false)
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'is persisted' do
|
187
|
+
expect{ @res.save }.to change{ @res.persisted? }.to(true)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'on a existing record' do
|
192
|
+
before(:each) do
|
193
|
+
allow(GenericAdaptor).to receive(:update).and_return(true)
|
194
|
+
@res.save
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'returns persist the resource' do
|
198
|
+
expect(@res).to receive(:update).once
|
199
|
+
@res.name = 'second'
|
200
|
+
@res.save
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe '#destroy' do
|
206
|
+
before(:each) do
|
207
|
+
@id = SecureRandom.hex
|
208
|
+
allow(GenericAdaptor).to receive(:fetch).and_return({
|
209
|
+
id: @id,
|
210
|
+
name: 'Laugh',
|
211
|
+
version: 28,
|
212
|
+
description: 'The best laugh ever... makes me laugh'
|
213
|
+
})
|
214
|
+
allow(GenericAdaptor).to receive(:delete).and_return(true)
|
215
|
+
@res = AnotherResource.find(@id)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'is marked as removed' do
|
219
|
+
expect { @res.destroy }.to change{ @res.destroyed? }.to(true)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'is not a new record' do
|
223
|
+
expect { @res.destroy }.to change{ @res.destroyed? }.to(true)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'calls the delete handler upon remove' do
|
228
|
+
expect(GenericAdaptor).to receive(:delete).once
|
229
|
+
@res.destroy
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'calls the update handler upon save on a resource with an id' do
|
233
|
+
id = SecureRandom.uuid
|
234
|
+
expect(GenericAdaptor).to receive(:update).once
|
235
|
+
@res.set_as_new_record false
|
236
|
+
expect(@res.new_record?).to be(false)
|
237
|
+
@res.save
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'calls the find handler upon a request to find resources' do
|
241
|
+
expect(GenericAdaptor).to receive(:find).with(Resource, { name: 'bogus' })
|
242
|
+
Resource.where(name: 'bogus').take(1)
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'calls the fetcher to get a resource' do
|
246
|
+
id = SecureRandom.uuid
|
247
|
+
expect(GenericAdaptor).to receive(:fetch).with(Resource, id, anything)
|
248
|
+
Resource.find(id)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
data/spec/movement_spec.rb
CHANGED
@@ -32,11 +32,11 @@ describe Muve::Movement do
|
|
32
32
|
expect(Muve::Movement.new).to respond_to(:connection)
|
33
33
|
end
|
34
34
|
|
35
|
-
it 'shares the connection among
|
36
|
-
|
35
|
+
it 'shares the connection among other models' do
|
36
|
+
connection = Object.new
|
37
|
+
Muve.init(connection)
|
37
38
|
|
38
|
-
|
39
|
-
expect(connection).not_to eq(nil)
|
39
|
+
expect(Muve::Model.connection).to eq(connection)
|
40
40
|
|
41
41
|
expect(Muve::Movement.new.connection).to be(connection)
|
42
42
|
expect(Muve::Location.new.connection).to be(connection)
|
data/spec/muve_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Muve do
|
4
|
+
it 'includes the model module' do
|
5
|
+
expect{ Muve::Model }.not_to raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'includes the location class' do
|
9
|
+
expect{ Muve::Location }.not_to raise_error
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'includes the traveller class' do
|
13
|
+
expect{ Muve::Traveller }.not_to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'includes the movement class' do
|
17
|
+
expect{ Muve::Movement }.not_to raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'contains a model that behaves like one' do
|
21
|
+
class TestModel
|
22
|
+
include Muve::Model
|
23
|
+
end
|
24
|
+
|
25
|
+
expect(TestModel.new).to respond_to(:save)
|
26
|
+
expect(TestModel.new).to respond_to(:valid?)
|
27
|
+
expect(TestModel.new).to respond_to(:connection)
|
28
|
+
end
|
29
|
+
end
|
data/spec/store_spec.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
describe Muve::Store do
|
2
|
+
before do
|
3
|
+
class Resource
|
4
|
+
include Muve::Model
|
5
|
+
|
6
|
+
def self.container
|
7
|
+
'resources'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class GenericStore
|
12
|
+
extend Muve::Store
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'provides methods to get a resource' do
|
17
|
+
expect(GenericStore).to respond_to(:get)
|
18
|
+
expect(GenericStore).to respond_to(:fetch)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'provides methods to create a resource' do
|
22
|
+
expect(GenericStore).to respond_to(:create)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'provides methods to destroy a resource' do
|
26
|
+
expect(GenericStore).to respond_to(:delete)
|
27
|
+
expect(GenericStore).to respond_to(:destroy)
|
28
|
+
expect(GenericStore).to respond_to(:remove)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'provides methods to update a resource' do
|
32
|
+
expect(GenericStore).to respond_to(:update)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'raises incomplete implementation errors on non-implemented methods' do
|
36
|
+
%w(create delete update fetch find).each do |method|
|
37
|
+
expect{
|
38
|
+
if method == 'update'
|
39
|
+
GenericStore.send(method, 'resource', nil, {})
|
40
|
+
else
|
41
|
+
GenericStore.send(method, 'resource', nil)
|
42
|
+
end
|
43
|
+
}.to raise_error(MuveError::MuveIncompleteImplementation)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'attempts to fetch a resource if the id is given' do
|
48
|
+
expect(GenericStore).to receive(:fetch)
|
49
|
+
GenericStore.get(Resource, SecureRandom.uuid)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'attempts to find a resource if the id is not given but the details are' do
|
53
|
+
expect(GenericStore).to receive(:find)
|
54
|
+
GenericStore.get(Resource, nil, { name: 'bogus' })
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises an error if neither id nor details are given' do
|
58
|
+
expect{
|
59
|
+
GenericStore.get(Resource)
|
60
|
+
}.to raise_error
|
61
|
+
end
|
62
|
+
end
|
metadata
CHANGED
@@ -1,85 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: muve
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.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-07-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.6'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.6'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 3.0.0
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 3.0.0
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: ffaker
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: factory_girl
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '4.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '4.0'
|
11
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
83
13
|
description: Basic helpers to be used with Muvement
|
84
14
|
email:
|
85
15
|
- david@supr.nu
|
@@ -93,16 +23,24 @@ files:
|
|
93
23
|
- Rakefile
|
94
24
|
- lib/muve.rb
|
95
25
|
- lib/muve/errors.rb
|
26
|
+
- lib/muve/helpers.rb
|
96
27
|
- lib/muve/location.rb
|
97
28
|
- lib/muve/model.rb
|
98
29
|
- lib/muve/movement.rb
|
30
|
+
- lib/muve/store.rb
|
31
|
+
- lib/muve/store/mongo.rb
|
99
32
|
- lib/muve/traveller.rb
|
100
33
|
- lib/muve/version.rb
|
101
34
|
- muve.gemspec
|
35
|
+
- spec/adaptor/mongo_spec.rb
|
102
36
|
- spec/factories.rb
|
37
|
+
- spec/helper_spec.rb
|
103
38
|
- spec/location_spec.rb
|
39
|
+
- spec/model_spec.rb
|
104
40
|
- spec/movement_spec.rb
|
41
|
+
- spec/muve_spec.rb
|
105
42
|
- spec/spec_helper.rb
|
43
|
+
- spec/store_spec.rb
|
106
44
|
- spec/traveller_spec.rb
|
107
45
|
homepage: ''
|
108
46
|
licenses:
|
@@ -129,8 +67,13 @@ signing_key:
|
|
129
67
|
specification_version: 4
|
130
68
|
summary: muve gem
|
131
69
|
test_files:
|
70
|
+
- spec/adaptor/mongo_spec.rb
|
132
71
|
- spec/factories.rb
|
72
|
+
- spec/helper_spec.rb
|
133
73
|
- spec/location_spec.rb
|
74
|
+
- spec/model_spec.rb
|
134
75
|
- spec/movement_spec.rb
|
76
|
+
- spec/muve_spec.rb
|
135
77
|
- spec/spec_helper.rb
|
78
|
+
- spec/store_spec.rb
|
136
79
|
- spec/traveller_spec.rb
|