guacamole 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/{config/rubocop.yml → .hound.yml} +1 -12
- data/.ruby-version +1 -1
- data/.travis.yml +2 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +3 -3
- data/Gemfile.devtools +24 -12
- data/Guardfile +1 -1
- data/README.md +347 -50
- data/Rakefile +10 -0
- data/config/reek.yml +18 -5
- data/guacamole.gemspec +5 -2
- data/lib/guacamole.rb +1 -0
- data/lib/guacamole/collection.rb +79 -7
- data/lib/guacamole/configuration.rb +56 -2
- data/lib/guacamole/document_model_mapper.rb +87 -7
- data/lib/guacamole/identity_map.rb +124 -0
- data/lib/guacamole/proxies/proxy.rb +42 -0
- data/lib/guacamole/proxies/referenced_by.rb +15 -0
- data/lib/guacamole/proxies/references.rb +15 -0
- data/lib/guacamole/query.rb +11 -0
- data/lib/guacamole/railtie.rb +6 -1
- data/lib/guacamole/railtie/database.rake +57 -3
- data/lib/guacamole/tasks/database.rake +23 -0
- data/lib/guacamole/version.rb +1 -1
- data/lib/rails/generators/guacamole/collection/collection_generator.rb +19 -0
- data/lib/rails/generators/guacamole/collection/templates/collection.rb.tt +5 -0
- data/lib/rails/generators/guacamole/config/config_generator.rb +25 -0
- data/lib/rails/generators/guacamole/config/templates/guacamole.yml +15 -0
- data/lib/rails/generators/guacamole/model/model_generator.rb +25 -0
- data/lib/rails/generators/guacamole/model/templates/model.rb.tt +11 -0
- data/lib/rails/generators/guacamole_generator.rb +28 -0
- data/lib/rails/generators/rails/collection/collection_generator.rb +13 -0
- data/lib/rails/generators/rspec/collection/collection_generator.rb +13 -0
- data/lib/rails/generators/rspec/collection/templates/collection_spec.rb.tt +7 -0
- data/spec/acceptance/association_spec.rb +40 -0
- data/spec/acceptance/basic_spec.rb +19 -2
- data/spec/acceptance/spec_helper.rb +5 -2
- data/spec/fabricators/author.rb +11 -0
- data/spec/fabricators/author_fabricator.rb +7 -0
- data/spec/fabricators/book.rb +11 -0
- data/spec/fabricators/book_fabricator.rb +5 -0
- data/spec/unit/collection_spec.rb +265 -18
- data/spec/unit/configuration_spec.rb +11 -1
- data/spec/unit/document_model_mapper_spec.rb +127 -5
- data/spec/unit/identiy_map_spec.rb +140 -0
- data/spec/unit/query_spec.rb +37 -16
- data/tasks/adjustments.rake +0 -1
- metadata +78 -8
@@ -21,11 +21,21 @@ describe 'Guacamole.configuration' do
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
describe 'Guacamole.logger' do
|
25
|
+
subject { Guacamole }
|
26
|
+
|
27
|
+
it 'should just forward to Configuration#logger' do
|
28
|
+
expect(Guacamole.configuration).to receive(:logger)
|
29
|
+
|
30
|
+
subject.logger
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
24
34
|
describe Guacamole::Configuration do
|
25
35
|
subject { Guacamole::Configuration }
|
26
36
|
|
27
37
|
describe 'database' do
|
28
|
-
it 'should set the
|
38
|
+
it 'should set the database' do
|
29
39
|
database = double('Database')
|
30
40
|
subject.database = database
|
31
41
|
|
@@ -6,16 +6,24 @@ require 'guacamole/document_model_mapper'
|
|
6
6
|
class FancyModel
|
7
7
|
end
|
8
8
|
|
9
|
+
class FakeIdentityMap
|
10
|
+
class << self
|
11
|
+
def retrieve_or_store(*args, &block)
|
12
|
+
block.call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
describe Guacamole::DocumentModelMapper do
|
10
18
|
subject { Guacamole::DocumentModelMapper }
|
11
19
|
|
12
20
|
it 'should be initialized with a model class' do
|
13
|
-
mapper = subject.new FancyModel
|
21
|
+
mapper = subject.new FancyModel, FakeIdentityMap
|
14
22
|
expect(mapper.model_class).to eq FancyModel
|
15
23
|
end
|
16
24
|
|
17
25
|
describe 'document_to_model' do
|
18
|
-
subject { Guacamole::DocumentModelMapper.new FancyModel }
|
26
|
+
subject { Guacamole::DocumentModelMapper.new FancyModel, FakeIdentityMap }
|
19
27
|
|
20
28
|
let(:document) { double('Ashikawa::Core::Document') }
|
21
29
|
let(:document_attributes) { double('Hash') }
|
@@ -25,7 +33,7 @@ describe Guacamole::DocumentModelMapper do
|
|
25
33
|
|
26
34
|
before do
|
27
35
|
allow(subject.model_class).to receive(:new).and_return(model_instance)
|
28
|
-
allow(document).to receive(:
|
36
|
+
allow(document).to receive(:to_h).and_return(document_attributes)
|
29
37
|
allow(document).to receive(:key).and_return(some_key)
|
30
38
|
allow(document).to receive(:revision).and_return(some_rev)
|
31
39
|
end
|
@@ -44,6 +52,62 @@ describe Guacamole::DocumentModelMapper do
|
|
44
52
|
subject.document_to_model document
|
45
53
|
end
|
46
54
|
|
55
|
+
context 'with referenced_by models' do
|
56
|
+
let(:referenced_by_model_name) { :cupcakes }
|
57
|
+
let(:referenced_by_models) { [referenced_by_model_name] }
|
58
|
+
let(:association_proxy) { Guacamole::Proxies::ReferencedBy }
|
59
|
+
let(:association_proxy_instance) { double('AssociationProxy') }
|
60
|
+
|
61
|
+
before do
|
62
|
+
allow(subject).to receive(:referenced_by_models).and_return referenced_by_models
|
63
|
+
allow(association_proxy).to receive(:new)
|
64
|
+
.with(referenced_by_model_name, model_instance)
|
65
|
+
.and_return(association_proxy_instance)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should initialize the association proxy with referenced_by model and its name' do
|
69
|
+
expect(association_proxy).to receive(:new)
|
70
|
+
.with(referenced_by_model_name, model_instance)
|
71
|
+
.and_return(association_proxy_instance)
|
72
|
+
|
73
|
+
subject.document_to_model document
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should set an association proxy' do
|
77
|
+
expect(model_instance).to receive("#{referenced_by_model_name}=").with(association_proxy_instance)
|
78
|
+
|
79
|
+
subject.document_to_model document
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'with referenced models' do
|
84
|
+
let(:referenced_model_name) { :pony }
|
85
|
+
let(:referenced_models) { [referenced_model_name] }
|
86
|
+
let(:association_proxy) { Guacamole::Proxies::References }
|
87
|
+
let(:association_proxy_instance) { double('AssociationProxy') }
|
88
|
+
|
89
|
+
before do
|
90
|
+
allow(subject).to receive(:referenced_models).and_return referenced_models
|
91
|
+
allow(association_proxy).to receive(:new)
|
92
|
+
.with(referenced_model_name, document)
|
93
|
+
.and_return(association_proxy_instance)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should initialize the association proxy with the document and the referenced model name' do
|
97
|
+
expect(association_proxy).to receive(:new)
|
98
|
+
.with(referenced_model_name, document)
|
99
|
+
.and_return(association_proxy_instance)
|
100
|
+
|
101
|
+
subject.document_to_model document
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should set an association proxy' do
|
105
|
+
expect(model_instance).to receive("#{referenced_model_name}=").with(association_proxy_instance)
|
106
|
+
|
107
|
+
subject.document_to_model document
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
47
111
|
context 'with embedded ponies' do
|
48
112
|
# This is handled by Virtus, we just need to provide a hash
|
49
113
|
# and the coercing will be taken care of by Virtus
|
@@ -51,7 +115,7 @@ describe Guacamole::DocumentModelMapper do
|
|
51
115
|
end
|
52
116
|
|
53
117
|
describe 'model_to_document' do
|
54
|
-
subject { Guacamole::DocumentModelMapper.new FancyModel }
|
118
|
+
subject { Guacamole::DocumentModelMapper.new FancyModel, FakeIdentityMap }
|
55
119
|
|
56
120
|
let(:model) { double('Model') }
|
57
121
|
let(:model_attributes) { double('Hash').as_null_object }
|
@@ -106,10 +170,48 @@ describe Guacamole::DocumentModelMapper do
|
|
106
170
|
subject.model_to_document(model)
|
107
171
|
end
|
108
172
|
end
|
173
|
+
|
174
|
+
context 'with referenced_by models' do
|
175
|
+
let(:referenced_by_model_name) { :cupcakes }
|
176
|
+
let(:referenced_by_models) { [referenced_by_model_name] }
|
177
|
+
|
178
|
+
before do
|
179
|
+
allow(subject).to receive(:referenced_by_models).and_return referenced_by_models
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should remove the referenced_by attribute from the document' do
|
183
|
+
expect(model_attributes).to receive(:delete).with(referenced_by_model_name)
|
184
|
+
|
185
|
+
subject.model_to_document(model)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'with referenced models' do
|
190
|
+
let(:referenced_model) { double('ReferencedModel', key: 23) }
|
191
|
+
let(:referenced_model_name) { :pony }
|
192
|
+
let(:referenced_models) { [referenced_model_name] }
|
193
|
+
|
194
|
+
before do
|
195
|
+
allow(subject).to receive(:referenced_models).and_return referenced_models
|
196
|
+
allow(model).to receive(:send).with(referenced_model_name).and_return referenced_model
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should remove the referenced attribute from the document' do
|
200
|
+
expect(model_attributes).to receive(:delete).with(referenced_model_name)
|
201
|
+
|
202
|
+
subject.model_to_document(model)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should add the key of the referenced model to the document' do
|
206
|
+
expect(model_attributes).to receive(:[]=).with(:"#{referenced_model_name}_id", referenced_model.key)
|
207
|
+
|
208
|
+
subject.model_to_document(model)
|
209
|
+
end
|
210
|
+
end
|
109
211
|
end
|
110
212
|
|
111
213
|
describe 'embed' do
|
112
|
-
subject { Guacamole::DocumentModelMapper.new FancyModel }
|
214
|
+
subject { Guacamole::DocumentModelMapper.new FancyModel, FakeIdentityMap }
|
113
215
|
|
114
216
|
it 'should remember which models to embed' do
|
115
217
|
subject.embeds :ponies
|
@@ -117,4 +219,24 @@ describe Guacamole::DocumentModelMapper do
|
|
117
219
|
expect(subject.models_to_embed).to include :ponies
|
118
220
|
end
|
119
221
|
end
|
222
|
+
|
223
|
+
describe 'referenced_by' do
|
224
|
+
subject { Guacamole::DocumentModelMapper.new FancyModel, FakeIdentityMap }
|
225
|
+
|
226
|
+
it 'should remember which models holding references' do
|
227
|
+
subject.referenced_by :ponies
|
228
|
+
|
229
|
+
expect(subject.referenced_by_models).to include :ponies
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'references' do
|
234
|
+
subject { Guacamole::DocumentModelMapper.new FancyModel, FakeIdentityMap }
|
235
|
+
|
236
|
+
it 'should remember which models are referenced' do
|
237
|
+
subject.references :pony
|
238
|
+
|
239
|
+
expect(subject.referenced_models).to include :pony
|
240
|
+
end
|
241
|
+
end
|
120
242
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'guacamole/identity_map'
|
5
|
+
|
6
|
+
describe Guacamole::IdentityMap::Session do
|
7
|
+
context 'initialization' do
|
8
|
+
subject { Guacamole::IdentityMap::Session }
|
9
|
+
|
10
|
+
let(:an_app) { double('TheApp') }
|
11
|
+
|
12
|
+
it 'should require an app object' do
|
13
|
+
middleware = subject.new an_app
|
14
|
+
|
15
|
+
expect(middleware.instance_variable_get('@app')).to eq an_app
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'resetting the IdentityMap' do
|
20
|
+
let(:some_app) { double('TheApp').as_null_object }
|
21
|
+
let(:rack_env) { double('RackEnv') }
|
22
|
+
let(:logger) { double('Logger').as_null_object }
|
23
|
+
|
24
|
+
subject { Guacamole::IdentityMap::Session.new some_app }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(Guacamole).to receive(:logger).and_return(logger)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should reset the IdentityMap upon `call` and bypass to the @app' do
|
31
|
+
expect(Guacamole::IdentityMap).to receive(:reset)
|
32
|
+
|
33
|
+
subject.call rack_env
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should pass the request to the @app instance' do
|
37
|
+
expect(some_app).to receive(:call).with(rack_env)
|
38
|
+
|
39
|
+
subject.call rack_env
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should log the reset action as debug' do
|
43
|
+
expect(logger).to receive(:debug).with('[SESSION] Resetting the IdentityMap')
|
44
|
+
|
45
|
+
subject.call rack_env
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe Guacamole::IdentityMap do
|
52
|
+
subject { Guacamole::IdentityMap }
|
53
|
+
|
54
|
+
before do
|
55
|
+
subject.reset
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'Management' do
|
59
|
+
it 'should always return an identity_map_instance' do
|
60
|
+
expect(subject.identity_map_instance).not_to be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should reset the current map' do
|
64
|
+
expect(Hamster).to receive(:hash)
|
65
|
+
|
66
|
+
subject.reset
|
67
|
+
subject.identity_map_instance
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'construct the storage key' do
|
71
|
+
it 'should use the object' do
|
72
|
+
some_object = double('SomeObject', key: '1337')
|
73
|
+
|
74
|
+
expect(subject.key_for(some_object)).to eq [some_object.class, some_object.key]
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should construct the map key based on the class and the key' do
|
78
|
+
some_class = double(:SomeClass)
|
79
|
+
some_key = '1337'
|
80
|
+
|
81
|
+
expect(subject.key_for(some_class.class, some_key)).to eq [some_class.class, some_key]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'Store objects' do
|
87
|
+
let(:cupcake) { double('Cupcake', key: '42') }
|
88
|
+
|
89
|
+
before do
|
90
|
+
subject.reset
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should use the `key_for` method to construct the map key' do
|
94
|
+
expect(subject).to receive(:key_for).with(cupcake).and_return(:the_key)
|
95
|
+
|
96
|
+
subject.store cupcake
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should store the object in the map' do
|
100
|
+
subject.store cupcake
|
101
|
+
|
102
|
+
expect(subject.include?(cupcake)).to be_true
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should use an immutable storage' do
|
106
|
+
old_map = subject.identity_map_instance
|
107
|
+
subject.store cupcake
|
108
|
+
|
109
|
+
expect(old_map.key?(subject.key_for(cupcake))).to be_false
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should return the stored object' do
|
113
|
+
expect(subject.store(cupcake)).to eq cupcake
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'Retrieve objects' do
|
118
|
+
let(:pony) { double('Pony', key: '23') }
|
119
|
+
|
120
|
+
before do
|
121
|
+
subject.store pony
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should load the corresponding object from the map' do
|
125
|
+
expect(subject.retrieve(pony.class, pony.key)).to eq pony
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe 'Retrieve or store objects' do
|
130
|
+
let(:rainbow) { double('Rainbow', key: 'all-the-colors') }
|
131
|
+
|
132
|
+
it 'should store and retrieve an object in one step' do
|
133
|
+
result = subject.retrieve_or_store(rainbow.class, rainbow.key) do
|
134
|
+
rainbow
|
135
|
+
end
|
136
|
+
|
137
|
+
expect(subject.retrieve(rainbow.class, rainbow.key)).to eq result
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/spec/unit/query_spec.rb
CHANGED
@@ -41,13 +41,13 @@ describe Guacamole::Query do
|
|
41
41
|
expect(connection).to receive(:all)
|
42
42
|
.with({})
|
43
43
|
|
44
|
-
subject.each {
|
44
|
+
subject.each {}
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'should iterate over the resulting documents' do
|
48
48
|
expect(result).to receive(:each)
|
49
49
|
|
50
|
-
subject.each {
|
50
|
+
subject.each {}
|
51
51
|
end
|
52
52
|
|
53
53
|
it 'should yield the models to the caller' do
|
@@ -62,15 +62,15 @@ describe Guacamole::Query do
|
|
62
62
|
expect(connection).to receive(:all)
|
63
63
|
.with(hash_including limit: limit)
|
64
64
|
|
65
|
-
subject.limit(limit).each {
|
65
|
+
subject.limit(limit).each {}
|
66
66
|
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
it 'should accept a skip' do
|
69
|
+
expect(connection).to receive(:all)
|
70
|
+
.with(hash_including skip: skip)
|
71
71
|
|
72
|
-
|
73
|
-
|
72
|
+
subject.skip(skip).each {}
|
73
|
+
end
|
74
74
|
end
|
75
75
|
|
76
76
|
context 'an example was provided' do
|
@@ -86,13 +86,13 @@ describe Guacamole::Query do
|
|
86
86
|
expect(connection).to receive(:by_example)
|
87
87
|
.with(example, {})
|
88
88
|
|
89
|
-
subject.each {
|
89
|
+
subject.each {}
|
90
90
|
end
|
91
91
|
|
92
92
|
it 'should iterate over the resulting documents' do
|
93
93
|
expect(result).to receive(:each)
|
94
94
|
|
95
|
-
subject.each {
|
95
|
+
subject.each {}
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'should yield the models to the caller' do
|
@@ -107,15 +107,15 @@ describe Guacamole::Query do
|
|
107
107
|
expect(connection).to receive(:by_example)
|
108
108
|
.with(example, hash_including(limit: limit))
|
109
109
|
|
110
|
-
subject.limit(limit).each {
|
110
|
+
subject.limit(limit).each {}
|
111
111
|
end
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
it 'should accept a skip' do
|
114
|
+
expect(connection).to receive(:by_example)
|
115
|
+
.with(example, hash_including(skip: skip))
|
116
116
|
|
117
|
-
|
118
|
-
|
117
|
+
subject.skip(skip).each {}
|
118
|
+
end
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
@@ -137,4 +137,25 @@ describe Guacamole::Query do
|
|
137
137
|
end
|
138
138
|
end
|
139
139
|
end
|
140
|
+
|
141
|
+
describe '==' do
|
142
|
+
subject { Guacamole::Query }
|
143
|
+
|
144
|
+
let(:one_query) { subject.new(double('Connection'), double('Mapper')) }
|
145
|
+
let(:another_query) { subject.new(double('Connection'), double('Mapper')) }
|
146
|
+
|
147
|
+
it 'should be equal if the example it equal' do
|
148
|
+
one_query.example = { this: 23 }
|
149
|
+
another_query.example = { this: 23 }
|
150
|
+
|
151
|
+
expect(one_query).to eq another_query
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should not be equal if the examples differ' do
|
155
|
+
one_query.example = { this: 23 }
|
156
|
+
another_query.example = { that: 42 }
|
157
|
+
|
158
|
+
expect(one_query).not_to eq another_query
|
159
|
+
end
|
160
|
+
end
|
140
161
|
end
|