kvom 6.8.3.174.51542603 → 6.9.0.3.197272233

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  require 'kvom/model'
2
+
2
3
  require 'active_support/core_ext/hash/keys'
3
4
  require 'active_support/core_ext/object/blank'
4
5
  require 'active_support/core_ext/class/attribute'
@@ -17,8 +18,15 @@ class Base
17
18
 
18
19
  class << self
19
20
 
20
- def has_all_ids
21
- include Feature::AllIds
21
+ def key_prefix
22
+ to_s
23
+ end
24
+
25
+ # options:
26
+ # - hotspot: true # does not save an additional index document, but creates a hotspot
27
+ def has_all_ids(options = {})
28
+ self.kvom_uses_key_prefix_as_hash_value = options[:hotspot]
29
+ self.kvom_uses_all_index_document = !kvom_uses_key_prefix_as_hash_value?
22
30
  end
23
31
 
24
32
  def create(attributes = {})
@@ -47,27 +55,19 @@ class Base
47
55
  end
48
56
  end
49
57
 
50
- # default implementation for the class_attribute
51
- def key_prefix
52
- to_s
53
- end
54
-
55
58
  def adapter
56
59
  raise "must be overwritten in subclasses"
57
60
  end
58
61
 
59
- def id_key_for(id)
60
- key_for("id/#{id}", nil)
61
- end
62
-
63
- private
64
-
65
- def find_document(id)
66
- adapter.get(id_key_for(id))
67
- end
68
-
69
- def key_for(hash, range)
70
- ["#{key_prefix}/#{hash}", range]
62
+ def all_ids
63
+ case
64
+ when kvom_uses_key_prefix_as_hash_value?
65
+ adapter.range_values_of_hash_value(key_prefix)
66
+ when kvom_uses_all_index_document?
67
+ adapter.range_values_of_hash_value("#{key_prefix}/all")
68
+ else
69
+ raise "#{name}.all_ids needs to be enabled via has_all_ids"
70
+ end
71
71
  end
72
72
 
73
73
  end
@@ -84,8 +84,7 @@ class Base
84
84
  else
85
85
  @new = true
86
86
  attrs = doc_or_attrs.stringify_keys
87
- key = self.class.id_key_for(attrs["id"] ||= SecureRandom.hex(8))
88
- self.class.adapter.new_document(key, attrs)
87
+ self.class.new_document(attrs)
89
88
  end
90
89
  end
91
90
 
@@ -94,8 +93,7 @@ class Base
94
93
  end
95
94
 
96
95
  def save
97
- self.class.adapter.save(@document)
98
- self.class.adapter.save_index(all_key) if @new && is_a?(Feature::AllIds)
96
+ self.class.save_document(@document, @new)
99
97
  @new = false
100
98
  end
101
99
 
@@ -108,8 +106,7 @@ class Base
108
106
  end
109
107
 
110
108
  def destroy
111
- self.class.adapter.destroy_index(all_key) if is_a?(Feature::AllIds)
112
- self.class.adapter.destroy(@document)
109
+ self.class.destroy_document(@document)
113
110
  end
114
111
 
115
112
  private
@@ -122,6 +119,57 @@ class Base
122
119
  @document[name] = value
123
120
  end
124
121
 
122
+ class_attribute :kvom_uses_key_prefix_as_hash_value
123
+ class_attribute :kvom_uses_all_index_document
124
+
125
+ class << self
126
+
127
+ # :nodoc:
128
+ def new_document(attrs)
129
+ id = attrs["id"] ||= SecureRandom.hex(8)
130
+ adapter.new_document(id_key_for(id), attrs)
131
+ end
132
+
133
+ # :nodoc:
134
+ def save_document(document, is_new)
135
+ adapter.save(document)
136
+ if is_new
137
+ if kvom_uses_all_index_document?
138
+ adapter.save_index(all_key_for(document))
139
+ end
140
+ end
141
+ true
142
+ end
143
+
144
+ # :nodoc:
145
+ def destroy_document(document)
146
+ if kvom_uses_all_index_document?
147
+ adapter.destroy_index(all_key_for(document))
148
+ end
149
+ adapter.destroy(document)
150
+ end
151
+
152
+ private
153
+
154
+ def find_document(id)
155
+ adapter.get(id_key_for(id))
156
+ end
157
+
158
+ def id_key_for(id)
159
+ case
160
+ when kvom_uses_key_prefix_as_hash_value?
161
+ [key_prefix, id]
162
+ else
163
+ ["#{key_prefix}/id/#{id}", nil]
164
+ end
165
+ end
166
+
167
+ def all_key_for(document)
168
+ ["#{key_prefix}/all", document["id"]]
169
+ end
170
+
171
+ end
172
+
125
173
  end
126
174
 
127
175
  end; end # module Kvom::Model
@@ -4,7 +4,7 @@ describe Kvom::Adapter::DynamodbAdapter, "endpoint configuration", :adapter => "
4
4
 
5
5
  context "when no endpoint is specified" do
6
6
 
7
- let(:adapter) {TestModel.adapter}
7
+ let(:adapter) {AdapterForSpec.adapter}
8
8
 
9
9
  it "is configured to use Ireland" do
10
10
  adapter.__send__(:table).config.dynamo_db_endpoint.should == "dynamodb.eu-west-1.amazonaws.com"
@@ -1,76 +1,91 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Kvom::Model::Base, "with all-index enabled" do
4
- describe ".all_ids" do
5
- before(:all) do
6
- TestModelWithIndex.create(:id => volatile_id("0815-list"))
7
- TestModelWithIndex.create(:id => volatile_id("007"))
8
- OtherModelWithIndex.create(:id => volatile_id("other"))
9
- end
4
+ shared_examples_for "an all_ids supporting model" do |model_class, other_class|
5
+ describe ".all_ids" do
6
+ before(:all) do
7
+ model_class.create(:id => volatile_id("0815-list"))
8
+ model_class.create(:id => volatile_id("007"))
9
+ other_class.create(:id => volatile_id("other"))
10
+ end
10
11
 
11
- it "returns the ids of all persisted models" do
12
- all_ids = TestModelWithIndex.all_ids
13
- expected_ids = [volatile_id("0815-list"), volatile_id("007")]
14
- (expected_ids & all_ids).should eq(expected_ids)
15
- end
12
+ let(:all_ids) {model_class.all_ids}
13
+ let(:other_ids) {other_class.all_ids}
16
14
 
17
- it "returns only ids of the model" do
18
- all_ids = TestModelWithIndex.all_ids
19
- all_ids.should_not include(volatile_id("other"))
20
- end
15
+ it "returns the ids of all persisted models" do
16
+ expected_ids = [volatile_id("0815-list"), volatile_id("007")]
17
+ (expected_ids & all_ids).should eq(expected_ids)
18
+ end
21
19
 
22
- it "returns ids for which a model can be fetched" do
23
- all_ids = TestModelWithIndex.all_ids
24
- erroneous_ids = {}
25
- all_ids.each do |model_id|
26
- TestModelWithIndex.find(model_id).tap do |model|
27
- model.should be
28
- model.should be_kind_of(TestModelWithIndex)
20
+ it "returns only ids of the model" do
21
+ other_ids.should_not be_empty
22
+ (all_ids & other_ids).should be_empty
23
+ end
24
+
25
+ it "returns ids for which a model can be fetched" do
26
+ all_ids.each do |model_id|
27
+ model_class.find(model_id).tap do |model|
28
+ model.should be
29
+ model.should be_instance_of(model_class)
30
+ end
29
31
  end
30
32
  end
31
33
  end
32
- end
33
34
 
34
- describe ".create" do
35
- it "adds the instance's id to the ids returned by .all_ids" do
36
- new_id = volatile_id("neu")
37
- expect {
38
- TestModelWithIndex.create(:id => new_id)
39
- }.to change {
40
- TestModelWithIndex.all_ids.include?(new_id)
41
- }.to(true)
35
+ describe ".create" do
36
+ it "adds the instance's id to the ids returned by .all_ids" do
37
+ new_id = volatile_id("neu")
38
+ expect {
39
+ model_class.create(:id => new_id)
40
+ }.to change {
41
+ model_class.all_ids.include?(new_id)
42
+ }.to(true)
43
+ end
42
44
  end
43
- end
44
45
 
45
- describe "#destroy" do
46
- let(:model) {TestModelWithIndex.create(:id => volatile_id("weg"))}
46
+ describe "#destroy" do
47
+ let(:model_id) {volatile_id("weg")}
48
+ let(:model) {model_class.create(:id => model_id)}
47
49
 
48
- before do
49
- model
50
- end
50
+ before do
51
+ model
52
+ end
51
53
 
52
- it "removes the instance's id from the ids returned by .all_ids" do
53
- expect {
54
- model.destroy
55
- }.to change {
56
- TestModelWithIndex.all_ids.include?(volatile_id("weg"))
57
- }.to(false)
54
+ it "removes the instance's id from the ids returned by .all_ids" do
55
+ expect {
56
+ model.destroy
57
+ }.to change {
58
+ model_class.all_ids.include?(model_id)
59
+ }.to(false)
60
+ end
58
61
  end
59
62
  end
60
63
 
61
- describe ".save" do
62
- it "does not write the index doc for an existing model" do
63
- new_model = TestModelWithIndex.new(:id => volatile_id("counting"))
64
- counter_before = TestModelWithIndex.adapter.request_counter
65
- new_model.save
66
- counter_new_saved = TestModelWithIndex.adapter.request_counter
67
- requests_for_new_model = counter_new_saved - counter_before
68
- new_model.something = "changed"
69
- new_model.save
70
- counter_existing_saved = TestModelWithIndex.adapter.request_counter
71
- requests_for_existing_model = counter_existing_saved - counter_new_saved
64
+ context "for a hotspot kvom model" do
65
+ it_should_behave_like "an all_ids supporting model",
66
+ ExampleModelWithHotspotIndexForAllIds, ExampleModelWithHotspotIndex
67
+ end
68
+
69
+ context "for a standard kvom model with all_ids enabled" do
70
+ it_should_behave_like "an all_ids supporting model", ExampleModelWithIndex, OtherModelWithIndex
72
71
 
73
- requests_for_existing_model.should < requests_for_new_model
72
+ describe ".save" do
73
+ let(:adapter) {ExampleModelWithIndex.adapter}
74
+
75
+ def count_requests
76
+ counter_before = adapter.request_counter
77
+ yield
78
+ adapter.request_counter - counter_before
79
+ end
80
+
81
+ it "does not write the index doc for an existing model" do
82
+ model = ExampleModelWithIndex.new(:id => volatile_id("counting"))
83
+ request_count_for_model_create = count_requests {model.save}
84
+ request_count_for_model_update = count_requests {model.something = "changed"; model.save}
85
+
86
+ request_count_for_model_update.should be < request_count_for_model_create
87
+ request_count_for_model_update.should eq(request_count_for_model_create - 1)
88
+ end
74
89
  end
75
90
  end
76
91
  end
@@ -235,13 +235,36 @@ describe Kvom::Model::Base do
235
235
  ExampleModel.find(second_id)
236
236
  end
237
237
 
238
- describe "model's id" do
239
- it "should use a custom prefix when given" do
240
- CustomKeyPrefixModel.new.__send__(:document).key.first.should =~ /^spam_prefix\/id\/.+/
241
- end
238
+ describe "model's database key" do
239
+ let(:model_class) {ExampleModel}
240
+ let(:instance) {model_class.new}
241
+ let(:database_key) {instance.document.key}
242
242
 
243
243
  it "should use a default prefix" do
244
- ExampleModel.new.__send__(:document).key.first.should =~ /^ExampleModel\/id\/.+/
244
+ database_key.should == ["ExampleModel/id/#{instance.id}", nil]
245
+ end
246
+
247
+ context "when a custom prefix is provided" do
248
+ let(:model_class) {CustomKeyPrefixModel}
249
+
250
+ it "should use the configured prefix" do
251
+ database_key.should == ["spam_prefix/id/#{instance.id}", nil]
252
+ end
253
+ end
254
+
255
+ context "when the model class is configured as hotspot model" do
256
+ let(:model_class) {ExampleModelWithHotspotIndex}
257
+
258
+ it "should use a default prefix" do
259
+ database_key.should == ["ExampleModelWithHotspotIndex", instance.id]
260
+ end
261
+
262
+ context "when a custom prefix is provided" do
263
+ let(:model_class) {CustomKeyPrefixWithHotspotIndex}
264
+
265
+ it "should use a default prefix" do
266
+ database_key.should == ["hotspot", instance.id]
267
+ end
245
268
  end
246
269
  end
247
270
 
@@ -250,17 +273,44 @@ describe Kvom::Model::Base do
250
273
  expect { ExampleModel.find("") }.to raise_error /no id given/
251
274
  end
252
275
 
253
- it "should find model instances" do
254
- model = ExampleModel.create(:spam => "foo")
255
- ExampleModel.find(model.id).id.should == model.id
256
- ExampleModel.find(model.id).spam.should == "foo"
276
+ shared_examples_for "a kvom model find" do
277
+ context "when the instance exists in database" do
278
+ let(:instance) {model_class.create(:spam => "foo")}
279
+ let(:instance_id) {instance.id}
280
+
281
+ before do
282
+ instance
283
+ end
284
+
285
+ it "should find existing model instances" do
286
+ model_class.find(instance_id).should be_instance_of(model_class)
287
+ model_class.find(instance_id).id.should == instance_id
288
+ model_class.find(instance_id).spam.should == "foo"
289
+ end
290
+ end
291
+
292
+ context "when the instance does not exist in the database"
293
+ let(:bad_id) {random_id}
294
+
295
+ it "should raise #{Kvom::NotFound}" do
296
+ expect {
297
+ ExampleModel.find(bad_id)
298
+ }.to raise_error(Kvom::NotFound, %r(document.* key "#{key_for_bad_id}"))
299
+ end
300
+ end
301
+
257
302
  end
258
303
 
259
- it "should raise exception when instances cannot be found" do
260
- model_id = random_id
261
- expect {
262
- ExampleModel.find(model_id)
263
- }.to raise_error(Kvom::NotFound, %r(document.* key "ExampleModel/id/#{model_id}\|"))
304
+ let(:model_class) {ExampleModel}
305
+ let(:key_for_bad_id) {"ExampleModel/id/#{bad_id}\|"}
306
+
307
+ it_should_behave_like "a kvom model find"
308
+
309
+ context "when configured as hotspot model" do
310
+ let(:model_class) {ExampleModelWithHotspotIndex}
311
+ let(:key_for_bad_id) {"ExampleModel|#{bad_id}"}
312
+
313
+ it_should_behave_like "a kvom model find"
264
314
  end
265
315
  end
266
316
 
@@ -300,23 +350,27 @@ describe Kvom::Model::Base do
300
350
  end
301
351
 
302
352
  def create_document(hash, range)
353
+ adapter = AdapterForSpec.adapter
303
354
  document = adapter.new_document([volatile_id(hash), range], {})
304
355
  adapter.save(document)
305
356
  document
306
357
  end
307
358
 
308
- let(:adapter) {TestModel.adapter}
309
-
310
- let(:one_001) {create_document("one", "001")}
311
- let(:one_002) {create_document("one", "002")}
312
- let(:one_0021) {create_document("one", "0021")}
313
- let(:one_0022) {create_document("one", "0022")}
314
- let(:one_3) {create_document("one", "3")}
315
- let(:two_two) {create_document("two", "two")}
316
- let(:three_empty) {create_document("three", nil)}
359
+ let(:adapter) {AdapterForSpec.adapter}
317
360
 
318
361
  before(:all) do
319
- [one_001, one_002, one_0021, one_0022, one_3, two_two, three_empty]
362
+ @documents = {}
363
+ @documents[:one_001] = create_document("one", "001")
364
+ @documents[:one_002] = create_document("one", "002")
365
+ @documents[:one_0021] = create_document("one", "0021")
366
+ @documents[:one_0022] = create_document("one", "0022")
367
+ @documents[:one_3] = create_document("one", "3")
368
+ @documents[:two_two] = create_document("two", "two")
369
+ @documents[:three_empty] = create_document("three", nil)
370
+ end
371
+
372
+ [:one_001, :one_002, :one_0021, :one_0022, :one_3, :two_two, :three_empty].each do |key|
373
+ let(key) {@documents[key]}
320
374
  end
321
375
 
322
376
  it 'should be able to limit results' do
@@ -447,36 +501,40 @@ describe Kvom::Model::Base do
447
501
 
448
502
  let(:model_from_attributes) {ExampleModel.new({})}
449
503
 
450
- it "is new" do
451
- model_from_attributes.should be_new
504
+ context "without an id" do
505
+ it "is new" do
506
+ model_from_attributes.should be_new
507
+ end
452
508
  end
453
509
 
454
- context "and saved" do
510
+
511
+ context "including id" do
512
+
513
+ let(:model_id) {"not_a_default_#{random_id}"}
514
+ let(:model_from_attributes_including_id) {ExampleModel.new({"id" => model_id})}
455
515
 
456
516
  before do
457
- model_from_attributes.save
517
+ model_from_attributes_including_id.id.should == model_id
458
518
  end
459
519
 
460
- it "is not new" do
461
- model_from_attributes.should_not be_new
520
+ it "is new" do
521
+ model_from_attributes_including_id.should be_new
462
522
  end
463
523
 
464
524
  end
465
525
 
466
- context "including id" do
467
-
468
- let(:model_id) {"not_a_default_#{random_id}"}
469
- let(:model_from_attributes_including_id) {ExampleModel.new({"id" => model_id})}
526
+ context "and saved" do
470
527
 
471
528
  before do
472
- model_from_attributes_including_id.id.should == model_id
529
+ model_from_attributes.save
473
530
  end
474
531
 
475
- it "is new" do
476
- model_from_attributes.should be_new
532
+ it "is not new" do
533
+ model_from_attributes.should_not be_new
477
534
  end
478
535
 
479
536
  end
537
+
480
538
  end
481
539
 
482
540
  context "when instantiated from database" do
@@ -4,7 +4,7 @@ require File.expand_path("../../lib/kvom", __FILE__)
4
4
  Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f[0..-4]}
5
5
 
6
6
  RSpec.configure do |config|
7
- adapter = TestModel.adapter.class.to_s.demodulize.sub("Adapter", "").sub("db", "").downcase
7
+ adapter = AdapterForSpec.adapter.class.to_s.demodulize.sub("Adapter", "").sub("db", "").downcase
8
8
  adapter_specified_and_different = lambda {|required_adapter|
9
9
  required_adapter && required_adapter.to_s != adapter
10
10
  }
@@ -1,6 +1,8 @@
1
+ require File.join(File.dirname(__FILE__), 'test_ids')
2
+
1
3
  class TestModel < Kvom::Model::Base
2
4
  def self.adapter
3
- @adapter ||= AdapterForSpec.adapter
5
+ AdapterForSpec.adapter
4
6
  end
5
7
  end
6
8
 
@@ -8,26 +10,36 @@ class ExampleModel < TestModel
8
10
  property :spam
9
11
  end
10
12
 
11
- class CustomKeyPrefixModel < TestModel
13
+ class CustomKeyPrefixModel < ExampleModel
12
14
  self.key_prefix = "spam_prefix"
13
15
  end
14
16
 
15
17
  class ModelWithoutAdapter < Kvom::Model::Base
16
18
  end
17
19
 
18
- require 'securerandom'
19
- class TestModelWithIndex < TestModel
20
+ class ExampleModelWithIndex < TestModel
20
21
  has_all_ids
21
-
22
22
  property :something
23
23
 
24
24
  # not required to be set, but keeps cleanup and test separated
25
- $test_model_with_index_key_prefix ||= "#{key_prefix}-#{SecureRandom.hex(8)}"
26
- self.key_prefix = $test_model_with_index_key_prefix
25
+ self.key_prefix = "#{key_prefix}-#{TestIds.discriminator}"
27
26
  end
28
27
 
29
- class OtherModelWithIndex < TestModel
28
+ class OtherModelWithIndex < ExampleModel
30
29
  has_all_ids
31
30
 
32
31
  property :something
33
32
  end
33
+
34
+ class ExampleModelWithHotspotIndex < ExampleModel
35
+ # property is inherited
36
+ has_all_ids(hotspot: true)
37
+ end
38
+
39
+ class CustomKeyPrefixWithHotspotIndex < ExampleModelWithHotspotIndex
40
+ self.key_prefix = "hotspot"
41
+ end
42
+
43
+ class ExampleModelWithHotspotIndexForAllIds < ExampleModelWithHotspotIndex
44
+ self.key_prefix = "#{key_prefix}-#{TestIds.discriminator}"
45
+ end
@@ -1,9 +1,16 @@
1
+ require 'securerandom'
2
+
1
3
  module TestIds
2
4
  # - returns the same id for the whole test run
3
5
  # - returns a different id when running the specs again
4
6
  # => no id collision when running the tests on a reused and not emptied database
7
+
8
+ def self.discriminator
9
+ @discriminator ||= Time.now.strftime("%Y%m%d%H%M%S-") + SecureRandom.hex(8)
10
+ end
11
+
5
12
  def self.volatile_id(id)
6
- "#{@discriminator ||= Time.now.strftime("%Y%m%d%H%M%S-") + SecureRandom.hex(8)}#{id}"
13
+ "#{discriminator}#{id}"
7
14
  end
8
15
 
9
16
  def volatile_id(id)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kvom
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.8.3.174.51542603
4
+ version: 6.9.0.3.197272233
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-05 00:00:00.000000000 Z
12
+ date: 2013-04-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -148,8 +148,6 @@ files:
148
148
  - lib/kvom/lib/json_value.rb
149
149
  - lib/kvom/model.rb
150
150
  - lib/kvom/model/base.rb
151
- - lib/kvom/model/feature.rb
152
- - lib/kvom/model/feature/all_ids.rb
153
151
  - lib/kvom/model_identity.rb
154
152
  - lib/kvom/storage.rb
155
153
  - lib/kvom/storage/base.rb
@@ -187,7 +185,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
187
185
  version: '0'
188
186
  segments:
189
187
  - 0
190
- hash: -968281363
188
+ hash: -799392035
191
189
  required_rubygems_version: !ruby/object:Gem::Requirement
192
190
  none: false
193
191
  requirements:
@@ -1,5 +0,0 @@
1
- require 'kvom/model'
2
-
3
- module Kvom::Model::Feature
4
- Kvom.setup_autoload(self, __FILE__)
5
- end
@@ -1,30 +0,0 @@
1
- require 'kvom/model/feature'
2
-
3
- module Kvom; module Model; module Feature
4
-
5
- module AllIds
6
-
7
- def self.included(base)
8
- base.extend(ClassMethods)
9
- end
10
-
11
- module ClassMethods
12
- def all_ids
13
- adapter.range_values_of_hash_value("#{key_prefix}/all")
14
- end
15
-
16
- def all_key_for(id)
17
- key_for("all", id)
18
- end
19
-
20
- end
21
-
22
- private
23
-
24
- def all_key
25
- self.class.all_key_for(id)
26
- end
27
-
28
- end
29
-
30
- end; end; end