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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e64ab439f78e810ebff042a097baf5b95eaba0ac
4
- data.tar.gz: 1911f6965e437dab3fe5630b16e7594a09395738
3
+ metadata.gz: 07fd84a42ff9d48763eb97e0991f3b29c1922a5a
4
+ data.tar.gz: bae3ee756eb66eff4539fed0fcd717ca1bde469a
5
5
  SHA512:
6
- metadata.gz: f4f53bcfb178526d45860ff5bc19f1654532dddde18f67677816982031f41b03505237f1271e505bfcbac558329caf814418dc3a0a042d76efa913106a2dbeeb
7
- data.tar.gz: 9e5efb1a4511f3f008af45abdc9724474e72329a2f312436b07d2d649654688f0fa5abdec664241dd59c08bed80621e6f00cc3ec6d78867bb8a63ef3c6835874
6
+ metadata.gz: c864989ed5a970291c4a794e503553def1dcf813c92d7d8700858434a8170e51c3183cbd9ad0e0fa43651299e6f77a4887e74948bc04704a10140af0c1bbb0f4
7
+ data.tar.gz: 0bb9e70a4e32a2ae7c44d07689b433f67074353cf508201cc16f45b1c9aec7861cf785c54fa61ccd6e8fb7f6f094bea11d9e96100e0f8a3b261213c5a6247b10
data/.gitignore CHANGED
@@ -11,6 +11,7 @@ InstalledFiles
11
11
  _yardoc
12
12
  coverage
13
13
  doc/
14
+ html/
14
15
  lib/bundler/man
15
16
  pkg
16
17
  rdoc
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 MuveError < StandardError
2
+ class MuveStandardError < StandardError
3
3
  end
4
4
 
5
- class MuveSaveError < MuveError
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
@@ -0,0 +1,7 @@
1
+ module Muve
2
+ module Helper
3
+ def self.symbolize_keys(hash)
4
+ hash.map { |k, v| { k.to_sym => v } }.inject(&:merge)
5
+ end
6
+ end
7
+ end
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
- def initialize
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 || raise(MuveSaveError)
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
- def create
18
- end
19
-
20
- def update
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 ||= true
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
@@ -1,3 +1,3 @@
1
1
  module Muve
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
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
- @@conn ||= Object.new
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
- def self.init
34
- Model.connection
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
@@ -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
@@ -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
@@ -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 all instances' do
36
- Muve.init
35
+ it 'shares the connection among other models' do
36
+ connection = Object.new
37
+ Muve.init(connection)
37
38
 
38
- connection = Muve::Model.connection
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
@@ -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.1
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-15 00:00:00.000000000 Z
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