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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/{config/rubocop.yml → .hound.yml} +1 -12
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +2 -0
  6. data/.yardopts +1 -0
  7. data/CONTRIBUTING.md +3 -3
  8. data/Gemfile.devtools +24 -12
  9. data/Guardfile +1 -1
  10. data/README.md +347 -50
  11. data/Rakefile +10 -0
  12. data/config/reek.yml +18 -5
  13. data/guacamole.gemspec +5 -2
  14. data/lib/guacamole.rb +1 -0
  15. data/lib/guacamole/collection.rb +79 -7
  16. data/lib/guacamole/configuration.rb +56 -2
  17. data/lib/guacamole/document_model_mapper.rb +87 -7
  18. data/lib/guacamole/identity_map.rb +124 -0
  19. data/lib/guacamole/proxies/proxy.rb +42 -0
  20. data/lib/guacamole/proxies/referenced_by.rb +15 -0
  21. data/lib/guacamole/proxies/references.rb +15 -0
  22. data/lib/guacamole/query.rb +11 -0
  23. data/lib/guacamole/railtie.rb +6 -1
  24. data/lib/guacamole/railtie/database.rake +57 -3
  25. data/lib/guacamole/tasks/database.rake +23 -0
  26. data/lib/guacamole/version.rb +1 -1
  27. data/lib/rails/generators/guacamole/collection/collection_generator.rb +19 -0
  28. data/lib/rails/generators/guacamole/collection/templates/collection.rb.tt +5 -0
  29. data/lib/rails/generators/guacamole/config/config_generator.rb +25 -0
  30. data/lib/rails/generators/guacamole/config/templates/guacamole.yml +15 -0
  31. data/lib/rails/generators/guacamole/model/model_generator.rb +25 -0
  32. data/lib/rails/generators/guacamole/model/templates/model.rb.tt +11 -0
  33. data/lib/rails/generators/guacamole_generator.rb +28 -0
  34. data/lib/rails/generators/rails/collection/collection_generator.rb +13 -0
  35. data/lib/rails/generators/rspec/collection/collection_generator.rb +13 -0
  36. data/lib/rails/generators/rspec/collection/templates/collection_spec.rb.tt +7 -0
  37. data/spec/acceptance/association_spec.rb +40 -0
  38. data/spec/acceptance/basic_spec.rb +19 -2
  39. data/spec/acceptance/spec_helper.rb +5 -2
  40. data/spec/fabricators/author.rb +11 -0
  41. data/spec/fabricators/author_fabricator.rb +7 -0
  42. data/spec/fabricators/book.rb +11 -0
  43. data/spec/fabricators/book_fabricator.rb +5 -0
  44. data/spec/unit/collection_spec.rb +265 -18
  45. data/spec/unit/configuration_spec.rb +11 -1
  46. data/spec/unit/document_model_mapper_spec.rb +127 -5
  47. data/spec/unit/identiy_map_spec.rb +140 -0
  48. data/spec/unit/query_spec.rb +37 -16
  49. data/tasks/adjustments.rake +0 -1
  50. 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 logger' do
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(:hash).and_return(document_attributes)
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
@@ -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
- it 'should accept a skip' do
69
- expect(connection).to receive(:all)
70
- .with(hash_including skip: skip)
68
+ it 'should accept a skip' do
69
+ expect(connection).to receive(:all)
70
+ .with(hash_including skip: skip)
71
71
 
72
- subject.skip(skip).each { }
73
- end
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
- it 'should accept a skip' do
114
- expect(connection).to receive(:by_example)
115
- .with(example, hash_including(skip: skip))
113
+ it 'should accept a skip' do
114
+ expect(connection).to receive(:by_example)
115
+ .with(example, hash_including(skip: skip))
116
116
 
117
- subject.skip(skip).each { }
118
- end
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
@@ -27,7 +27,6 @@ task ci: %w[
27
27
  spec
28
28
  metrics:coverage
29
29
  metrics:reek
30
- metrics:rubocop
31
30
  metrics:yardstick:verify
32
31
  ]
33
32