muve 0.0.1 → 0.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: 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