guacamole 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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