schron 0.0.3 → 0.0.4
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/Rakefile +4 -3
- data/lib/schron/archive.rb +17 -0
- data/lib/schron/datastore/mongo.rb +50 -22
- data/lib/schron/db.rb +36 -0
- data/lib/schron/error.rb +9 -1
- data/lib/schron/identity_map.rb +9 -1
- data/lib/schron/identity_map_repository.rb +168 -0
- data/lib/schron/logged_repository.rb +58 -0
- data/lib/schron/query.rb +14 -4
- data/lib/schron/test/entity.rb +3 -2
- data/lib/schron/test/repository_examples.rb +1 -1
- data/schron.gemspec +1 -1
- data/spec/lib/schron/datastore/mongo_spec.rb +18 -1
- data/spec/lib/schron/db_spec.rb +35 -0
- data/spec/lib/schron/identity_map_repository_spec.rb +181 -0
- data/spec/lib/schron/query_spec.rb +22 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f4bf73bf955f3f82baa2bab5bc3acc1b812cd61
|
4
|
+
data.tar.gz: 334dc42717de4a920f6c698a031829d6113615c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 978dfca8ba3c7ea74efd6ad4825fcc34f6cf9641c2b8417185a9242c15e9a68122442c3736711288ccbb5b2fb48ce73364aaf602598ab517d5391d8be5b4dac1
|
7
|
+
data.tar.gz: 4385936c35b1df5c92c15d00fef37d12641f613766305d78baf319f1a3317618205f7f1d736734f03ab6027bf0f6ff0abba6dd3dd10be757f0b8d10d6d94d0b0
|
data/Rakefile
CHANGED
@@ -12,8 +12,9 @@ task :bundle do
|
|
12
12
|
system "cd #{root} && bundle install"
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
require 'rspec/core/rake_task'
|
16
|
+
RSpec::Core::RakeTask.new do |t|
|
17
|
+
t.rspec_opts = '--color'
|
17
18
|
end
|
18
19
|
|
19
|
-
task :default => [:bundle, :spec]
|
20
|
+
task :default => [:bundle, :spec]
|
data/lib/schron/archive.rb
CHANGED
@@ -27,11 +27,28 @@ module Schron
|
|
27
27
|
def query(&block)
|
28
28
|
Query.new(self, &block)
|
29
29
|
end
|
30
|
+
|
31
|
+
def exec_query(query)
|
32
|
+
raw_results = datastore.exec_query(query)
|
33
|
+
load_all(raw_results)
|
34
|
+
end
|
35
|
+
|
36
|
+
def exec_count(query)
|
37
|
+
datastore.exec_count(query)
|
38
|
+
end
|
30
39
|
|
31
40
|
def all
|
32
41
|
query.all
|
33
42
|
end
|
34
43
|
|
44
|
+
def batches(size, &block)
|
45
|
+
query.batches(size, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def batched_each(size, &block)
|
49
|
+
query.batched_each(size, &block)
|
50
|
+
end
|
51
|
+
|
35
52
|
def first
|
36
53
|
query.first
|
37
54
|
end
|
@@ -30,43 +30,55 @@ module Schron
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def insert(kind, hash)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
with_write_errors! do
|
34
|
+
hash[:id] ||= Schron::Id.generate
|
35
|
+
serialized = serialize(kind, hash)
|
36
|
+
coll(kind).insert(serialized)
|
37
|
+
hash
|
38
|
+
end
|
37
39
|
end
|
38
40
|
|
39
41
|
def multi_insert(kind, hashes)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
with_write_errors! do
|
43
|
+
hashes.each { |h| h[:id] ||= Schron::Id.generate }
|
44
|
+
docs = hashes.map { |h| serialize(kind, h) }
|
45
|
+
coll(kind).insert(docs)
|
46
|
+
hashes
|
47
|
+
end
|
44
48
|
end
|
45
49
|
|
46
50
|
def update(kind, hash)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
with_write_errors! do
|
52
|
+
Schron::Id.require!(hash)
|
53
|
+
doc = serialize(kind, hash)
|
54
|
+
coll(kind).update(id_selector(hash[:id]), doc)
|
55
|
+
hash
|
56
|
+
end
|
51
57
|
end
|
52
58
|
|
53
59
|
def multi_update(kind, hashes)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
with_write_errors! do
|
61
|
+
Schron::Id.require_all!(hashes)
|
62
|
+
hashes.each do |hash|
|
63
|
+
doc = serialize(kind, hash)
|
64
|
+
coll(kind).update(id_selector(hash[:id]), doc)
|
65
|
+
end
|
66
|
+
hashes
|
58
67
|
end
|
59
|
-
hashes
|
60
68
|
end
|
61
69
|
|
62
70
|
def remove(kind, id)
|
63
|
-
|
64
|
-
|
71
|
+
with_write_errors! do
|
72
|
+
coll(kind).remove(id_selector(id))
|
73
|
+
nil
|
74
|
+
end
|
65
75
|
end
|
66
76
|
|
67
77
|
def multi_remove(kind, ids)
|
68
|
-
|
69
|
-
|
78
|
+
with_write_errors! do
|
79
|
+
coll(kind).remove(multiple_id_selector(ids))
|
80
|
+
nil
|
81
|
+
end
|
70
82
|
end
|
71
83
|
|
72
84
|
def exec_query(query)
|
@@ -138,6 +150,22 @@ module Schron
|
|
138
150
|
{_id: {"$in" => serialized }}
|
139
151
|
end
|
140
152
|
|
153
|
+
|
154
|
+
def with_write_errors!(&block)
|
155
|
+
block.call
|
156
|
+
rescue ::Mongo::OperationFailure => detail
|
157
|
+
if detail.result &&
|
158
|
+
detail.result['writeErrors'] &&
|
159
|
+
!detail.result['writeErrors'].empty?
|
160
|
+
case detail.result['writeErrors'].first['code']
|
161
|
+
when 11000
|
162
|
+
raise Schron::DuplicateKeyError.new(nil, detail.result)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
raise detail
|
167
|
+
end
|
168
|
+
|
141
169
|
def selector_for(query)
|
142
170
|
selector = {}
|
143
171
|
query.filters.each do |(field, op, filter_value)|
|
@@ -186,4 +214,4 @@ module Schron
|
|
186
214
|
|
187
215
|
end
|
188
216
|
end
|
189
|
-
end
|
217
|
+
end
|
data/lib/schron/db.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Schron
|
2
|
+
class DB
|
3
|
+
|
4
|
+
def initialize(repos={})
|
5
|
+
@repos = repos.each_with_object({}) do |(k,v), hash|
|
6
|
+
hash[k.to_sym] = v
|
7
|
+
end
|
8
|
+
@repos.each do |name, repo|
|
9
|
+
define_singleton_method(name) { self[name] }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](name)
|
14
|
+
@repos[name.to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
def session_begin
|
18
|
+
@repos.each do |name, repo|
|
19
|
+
repo.session_begin if repo.respond_to?(:session_begin)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def session_end
|
24
|
+
@repos.each do |name, repo|
|
25
|
+
repo.session_end if repo.respond_to?(:session_end)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def session(&block)
|
30
|
+
session_begin
|
31
|
+
block.call
|
32
|
+
ensure
|
33
|
+
session_end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/schron/error.rb
CHANGED
data/lib/schron/identity_map.rb
CHANGED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'schron/identity_map'
|
2
|
+
require 'schron/repository/interface'
|
3
|
+
require 'schron/repository'
|
4
|
+
require 'schron/archive/interface'
|
5
|
+
|
6
|
+
module Schron
|
7
|
+
module IdentityMapRepository
|
8
|
+
include Schron::Archive::Interface
|
9
|
+
include Schron::Repository::Interface
|
10
|
+
|
11
|
+
include Schron::Repository
|
12
|
+
|
13
|
+
def self.included(archive_class)
|
14
|
+
archive_class.extend Schron::DSL
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
# def initialize(*args)
|
19
|
+
# super(*args)
|
20
|
+
# @id_map = nil
|
21
|
+
# end
|
22
|
+
|
23
|
+
# def method_missing(method_name, *args, &block)
|
24
|
+
# @repo.method(method_name).unbind.bind(self).call(*args, &block)
|
25
|
+
# # @repo.__send__(method_name, *args, &block)
|
26
|
+
# end
|
27
|
+
|
28
|
+
# def respond_to_missing?(method_name, include_private = false)
|
29
|
+
# @repo.respond_to?(method_name, include_private)
|
30
|
+
# end
|
31
|
+
|
32
|
+
def session_begin
|
33
|
+
@id_map ||= IdentityMap.new
|
34
|
+
@sessions ||= 0
|
35
|
+
@sessions += 1
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def session_end
|
40
|
+
raise 'no session is open' unless @id_map
|
41
|
+
@sessions -= 1
|
42
|
+
@id_map = nil if @sessions == 0
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def session(&block)
|
47
|
+
session_begin
|
48
|
+
block.call
|
49
|
+
ensure
|
50
|
+
session_end
|
51
|
+
end
|
52
|
+
|
53
|
+
# [:datastore, :kind, :entity_class, :indexed_fields,
|
54
|
+
# :identity].each do |repo_method|
|
55
|
+
# define_method(repo_method) do |*args, &block|
|
56
|
+
# @repo.__send__(repo_method, *args, &block)
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
|
60
|
+
# def query(&block)
|
61
|
+
# Query.new(self, &block)
|
62
|
+
# end
|
63
|
+
|
64
|
+
def exec_query(query)
|
65
|
+
results = super
|
66
|
+
results.map do |object|
|
67
|
+
if identity_map.has?(object.id)
|
68
|
+
id_map_object = identity_map.get(object.id)
|
69
|
+
assign_attributes(from: object, to: id_map_object)
|
70
|
+
id_map_object
|
71
|
+
else
|
72
|
+
identity_map.put(object.id, object)
|
73
|
+
object
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def get(id)
|
79
|
+
identity_map.fetch(id) do
|
80
|
+
super
|
81
|
+
# @repo.get(id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def multi_get(ids)
|
86
|
+
ids = ids.to_a unless ids.kind_of?(Array)
|
87
|
+
result = Array.new(ids.size)
|
88
|
+
needed_ids = []
|
89
|
+
|
90
|
+
ids.each_with_index do |id, index|
|
91
|
+
if identity_map.has?(id)
|
92
|
+
result[index] = identity_map.get(id)
|
93
|
+
else
|
94
|
+
needed_ids << id
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
needed_objects = super(needed_ids)#@repo.multi_get(needed_ids)
|
99
|
+
needed_objects.each do |object|
|
100
|
+
if object
|
101
|
+
identity_map.put(object.id, object)
|
102
|
+
result[ids.index(object.id)] = object
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
result.compact
|
107
|
+
end
|
108
|
+
|
109
|
+
def insert(object)
|
110
|
+
result = super
|
111
|
+
# result = @repo.insert(object)
|
112
|
+
assign_attributes(from: result, to: object)
|
113
|
+
identity_map.put(object.id, object)
|
114
|
+
object
|
115
|
+
end
|
116
|
+
|
117
|
+
def multi_insert(objects)
|
118
|
+
# results = @repo.multi_insert(objects)
|
119
|
+
results = super
|
120
|
+
objects.each_with_index do |o, i|
|
121
|
+
assign_attributes(from: results[i], to: o)
|
122
|
+
identity_map.put(o.id, o)
|
123
|
+
end
|
124
|
+
objects
|
125
|
+
end
|
126
|
+
|
127
|
+
def update(object)
|
128
|
+
super
|
129
|
+
# @repo.update(object)
|
130
|
+
object
|
131
|
+
end
|
132
|
+
|
133
|
+
def multi_update(objects)
|
134
|
+
super
|
135
|
+
# @repo.multi_update(objects)
|
136
|
+
objects
|
137
|
+
end
|
138
|
+
|
139
|
+
def remove(id)
|
140
|
+
super
|
141
|
+
object = identity_map.delete(id)
|
142
|
+
object.freeze if object
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def multi_remove(ids)
|
147
|
+
super
|
148
|
+
ids.each do |id|
|
149
|
+
object = identity_map.delete(id)
|
150
|
+
object.freeze if object
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def identity_map
|
156
|
+
@id_map || raise('Must start a session before accessing data from an IdentityMapRepository')
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def assign_attributes(from: from, to: to)
|
162
|
+
if to.respond_to?(:attributes=)
|
163
|
+
to.attributes = from.attributes
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'logger'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
module Schron
|
6
|
+
class LoggedRepository
|
7
|
+
|
8
|
+
def initialize(repo,
|
9
|
+
logger: nil,
|
10
|
+
log_level: :info,
|
11
|
+
exclude_regexp: nil,
|
12
|
+
include_regexp: nil)
|
13
|
+
@repo = repo
|
14
|
+
@logger = logger || Logger.new(STDOUT)
|
15
|
+
@log_level = log_level
|
16
|
+
@exclude = exclude_regexp
|
17
|
+
@include = include_regexp
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_name, *args, &block)
|
21
|
+
if @repo.respond_to?(method_name)
|
22
|
+
log(method_name, *args) do
|
23
|
+
@repo.__send__(method_name, *args, &block)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(method_name, include_private=false)
|
31
|
+
@repo.respond_to?(method_name, include_private)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def log(method_name, *args, &block)
|
37
|
+
if should_log_method?(method_name)
|
38
|
+
label = "#{@repo.class.name}##{method_name}(#{args.inspect})"
|
39
|
+
return_val = nil
|
40
|
+
time = Benchmark.realtime { return_val = block.call }
|
41
|
+
@logger.__send__(@log_level, ":repo: finished #{label} (#{time}s)")
|
42
|
+
return_val
|
43
|
+
else
|
44
|
+
block.call
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def should_log_method?(method_name)
|
49
|
+
if @exclude && method_name =~ @exclude
|
50
|
+
false
|
51
|
+
elsif @include && method_name !~ @include
|
52
|
+
false
|
53
|
+
else
|
54
|
+
true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/schron/query.rb
CHANGED
@@ -71,9 +71,7 @@ module Schron
|
|
71
71
|
## Query Terminators
|
72
72
|
|
73
73
|
def all
|
74
|
-
|
75
|
-
loaded_results = archive.load_all(raw_results)
|
76
|
-
with_pagination(loaded_results)
|
74
|
+
with_pagination(archive.exec_query(self))
|
77
75
|
end
|
78
76
|
|
79
77
|
def first
|
@@ -81,13 +79,25 @@ module Schron
|
|
81
79
|
end
|
82
80
|
|
83
81
|
def count
|
84
|
-
|
82
|
+
archive.exec_count(self)
|
85
83
|
end
|
86
84
|
|
87
85
|
def exists?
|
88
86
|
count > 0
|
89
87
|
end
|
90
88
|
|
89
|
+
def batched_each(size, &block)
|
90
|
+
batches(size) { |group| group.each(&block) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def batches(size, &block)
|
94
|
+
current_page = 1
|
95
|
+
begin
|
96
|
+
results = page(current_page, per_page: size).all
|
97
|
+
block.call(results)
|
98
|
+
current_page = results.paging.next_page
|
99
|
+
end while current_page
|
100
|
+
end
|
91
101
|
|
92
102
|
protected
|
93
103
|
|
data/lib/schron/test/entity.rb
CHANGED
data/schron.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "schron"
|
7
|
-
spec.version = "0.0.
|
7
|
+
spec.version = "0.0.4"
|
8
8
|
spec.authors = ["David Faber"]
|
9
9
|
spec.email = ["david@1bios.co"]
|
10
10
|
spec.summary = %q{Repository implementation for entity persistence}
|
@@ -20,4 +20,21 @@ describe Schron::Datastore::Mongo do
|
|
20
20
|
|
21
21
|
include_examples 'schron datastore'
|
22
22
|
|
23
|
-
|
23
|
+
describe 'error handling' do
|
24
|
+
context 'duplicate key error' do
|
25
|
+
let(:coll_name) { 'books' }
|
26
|
+
|
27
|
+
before(:each) do
|
28
|
+
db[coll_name].create_index(:title, unique: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'raises a Schron::DuplicateKeyError' do
|
32
|
+
ds.insert('books', title: 'Hyperion')
|
33
|
+
expect do
|
34
|
+
ds.insert('books', title: 'Hyperion')
|
35
|
+
end.to raise_error(Schron::DuplicateKeyError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/db'
|
3
|
+
|
4
|
+
describe Schron::DB do
|
5
|
+
subject(:db) do
|
6
|
+
described_class.new(
|
7
|
+
users: id_map_repo,
|
8
|
+
foos: non_id_map_repo)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:id_map_repo) { double }
|
12
|
+
|
13
|
+
let(:non_id_map_repo) { double }
|
14
|
+
|
15
|
+
it 'defines accessors for each repository' do
|
16
|
+
expect(db.users).to be(id_map_repo)
|
17
|
+
expect(db.foos).to be(non_id_map_repo)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'access repos by name as a symbol with []' do
|
21
|
+
expect(db[:users]).to eq(id_map_repo)
|
22
|
+
expect(db[:foos]).to eq(non_id_map_repo)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'access repos by name as string with []' do
|
26
|
+
expect(db['users']).to eq(id_map_repo)
|
27
|
+
expect(db['foos']).to eq(non_id_map_repo)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'opens sessions on all repos at once' do
|
31
|
+
expect(id_map_repo).to receive(:session_begin)
|
32
|
+
expect(id_map_repo).to receive(:session_end)
|
33
|
+
db.session {}
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'schron/repository'
|
4
|
+
require 'schron/datastore/memory'
|
5
|
+
require 'schron/identity_map_repository'
|
6
|
+
|
7
|
+
describe Schron::IdentityMapRepository do
|
8
|
+
include Schron::Test
|
9
|
+
|
10
|
+
subject(:repo) { repo_class.new(ds) }
|
11
|
+
|
12
|
+
let(:archive) { repo }
|
13
|
+
|
14
|
+
let(:repo_class) do
|
15
|
+
Class.new do
|
16
|
+
include Schron::IdentityMapRepository
|
17
|
+
entity_class Schron::Test::Entity
|
18
|
+
kind 'test'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
# let(:underlying_repo) do
|
22
|
+
# underlying_repo_class.new(ds,
|
23
|
+
# entity_class: Schron::Test::Entity,
|
24
|
+
# kind: 'test')
|
25
|
+
# end
|
26
|
+
|
27
|
+
let(:ds) { Schron::Datastore::Memory.new }
|
28
|
+
|
29
|
+
describe 'session' do
|
30
|
+
it 'creates an identity map' do
|
31
|
+
expect { repo.identity_map }.to raise_error
|
32
|
+
repo.session do
|
33
|
+
expect(repo.identity_map).not_to be_nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'creates a new one for each session' do
|
38
|
+
id_map = nil
|
39
|
+
repo.session { id_map = repo.identity_map }
|
40
|
+
repo.session do
|
41
|
+
expect(id_map).not_to be(repo.identity_map)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'uses the same identity map if a session has already started' do
|
46
|
+
repo.session_begin
|
47
|
+
id_map = repo.identity_map
|
48
|
+
repo.session_begin
|
49
|
+
expect(id_map).to be(repo.identity_map)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'closes sessions after an equal number of begin and end calls have been made' do
|
53
|
+
repo.session_begin
|
54
|
+
repo.session_begin
|
55
|
+
repo.session_end
|
56
|
+
expect(repo.identity_map).not_to be_nil
|
57
|
+
repo.session_end
|
58
|
+
expect{ repo.identity_map }.to raise_error
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'shared examples' do
|
63
|
+
around(:each) do |example|
|
64
|
+
repo.session { example.run }
|
65
|
+
end
|
66
|
+
|
67
|
+
include_examples 'schron archive'
|
68
|
+
include_examples 'schron repository'
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'within a session' do
|
72
|
+
around(:each) do |example|
|
73
|
+
repo.session { example.run }
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:object) { repo.insert(repo.entity_class.new) }
|
77
|
+
let(:object2) { repo.insert(repo.entity_class.new) }
|
78
|
+
|
79
|
+
it 'returns the same object after insertion' do
|
80
|
+
obj = repo.entity_class.new
|
81
|
+
inserted = repo.insert(obj)
|
82
|
+
expect(inserted).to be(obj)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'returns the same objects after multi insertion' do
|
86
|
+
obj = repo.entity_class.new
|
87
|
+
inserted = repo.multi_insert([obj]).first
|
88
|
+
expect(obj).to be(inserted)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'returns one object ref when retreived with get' do
|
92
|
+
loaded = repo.get(object.id)
|
93
|
+
expect(object).to be(loaded)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns the same object ref when retrieved by multi get' do
|
97
|
+
loaded = repo.multi_get([object.id]).first
|
98
|
+
expect(object).to be(loaded)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'can multi_get a set of ids' do
|
102
|
+
object
|
103
|
+
object2
|
104
|
+
repo.identity_map.clear
|
105
|
+
result = repo.multi_get(Set.new([object.id, object2.id]))
|
106
|
+
expect(result.map(&:id)).to include(object.id, object2.id)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'syncs updates' do
|
110
|
+
loaded = repo.get(object.id)
|
111
|
+
loaded.attributes[:thing] = 'thing'
|
112
|
+
updated = repo.update(loaded)
|
113
|
+
expect(updated).to be(object)
|
114
|
+
expect(object.attributes[:thing]).to eq('thing')
|
115
|
+
reloaded = repo.get(object.id)
|
116
|
+
expect(reloaded.attributes[:thing]).to eq('thing')
|
117
|
+
expect(reloaded).to be(object)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'syncs multi updates' do
|
121
|
+
l1 = repo.get(object.id)
|
122
|
+
l2 = repo.get(object2.id)
|
123
|
+
l1.attributes[:a] = 'b'
|
124
|
+
l2.attributes[:b] = 'c'
|
125
|
+
|
126
|
+
results = repo.multi_update([l1, l2])
|
127
|
+
expect(results[0]).to be(object)
|
128
|
+
expect(results[1]).to be(object2)
|
129
|
+
|
130
|
+
expect(object.attributes[:a]).to eq('b')
|
131
|
+
expect(object2.attributes[:b]).to eq('c')
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'freezes removed objects' do
|
135
|
+
loaded = repo.get(object.id)
|
136
|
+
repo.remove(loaded.id)
|
137
|
+
expect(object).to be_frozen
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'freezes multi-removed objects' do
|
141
|
+
loaded = repo.get(object.id)
|
142
|
+
repo.multi_remove([loaded.id])
|
143
|
+
expect(object).to be_frozen
|
144
|
+
end
|
145
|
+
|
146
|
+
# it 'calls custom repo methods on the original repo' do
|
147
|
+
# repo.define_singleton_method(:my_query) do |a1, &block|
|
148
|
+
# block.call(a1)
|
149
|
+
# end
|
150
|
+
# results = []
|
151
|
+
# repo.my_query('testing') do |val|
|
152
|
+
# results << val
|
153
|
+
# end
|
154
|
+
# expect(results).to eq(['testing'])
|
155
|
+
# end
|
156
|
+
|
157
|
+
# it 'responds_to? methods on the original repo' do
|
158
|
+
# underlying_repo.define_singleton_method(:custom_thing) {}
|
159
|
+
# expect(repo).to respond_to(:custom_thing)
|
160
|
+
# end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
describe 'queries' do
|
165
|
+
it 'puts query results into the identiy map' do
|
166
|
+
repo.session { repo.insert(repo.entity_class.new) }
|
167
|
+
repo.session do
|
168
|
+
obj = repo.query.all.first
|
169
|
+
expect(obj).to be(repo.get(obj.id))
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'pulls query results from the identity map' do
|
174
|
+
repo.session do
|
175
|
+
obj = repo.insert(repo.entity_class.new)
|
176
|
+
expect(obj).to be(repo.query.first)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -62,4 +62,26 @@ describe Schron::Query do
|
|
62
62
|
expect(query.first).to eq('loaded')
|
63
63
|
end
|
64
64
|
end
|
65
|
+
|
66
|
+
describe 'batches' do
|
67
|
+
it 'iterates over all results in groups, only loading the specified amount into memory at a time' do
|
68
|
+
expect(ds).to receive(:exec_query).at_least(2).and_call_original
|
69
|
+
2.times { archive.insert(Schron::GenericEntity.new) }
|
70
|
+
query.batches(1) do |group|
|
71
|
+
expect(group.size).to eq(1)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'batched each' do
|
77
|
+
it 'iterates over each result, only loading the specified amount into memory at a time' do
|
78
|
+
expect(ds).to receive(:exec_query).at_least(2).and_call_original
|
79
|
+
2.times { archive.insert(Schron::GenericEntity.new) }
|
80
|
+
i = 0
|
81
|
+
query.batches(1) do |item|
|
82
|
+
i += 1
|
83
|
+
end
|
84
|
+
expect(i).to eq(2)
|
85
|
+
end
|
86
|
+
end
|
65
87
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Faber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -128,12 +128,15 @@ files:
|
|
128
128
|
- lib/schron/datastore/mongo/serializer.rb
|
129
129
|
- lib/schron/datastore/sequel.rb
|
130
130
|
- lib/schron/datastore/serializer.rb
|
131
|
+
- lib/schron/db.rb
|
131
132
|
- lib/schron/dsl.rb
|
132
133
|
- lib/schron/error.rb
|
133
134
|
- lib/schron/generic_archive.rb
|
134
135
|
- lib/schron/generic_entity.rb
|
135
136
|
- lib/schron/id.rb
|
136
137
|
- lib/schron/identity_map.rb
|
138
|
+
- lib/schron/identity_map_repository.rb
|
139
|
+
- lib/schron/logged_repository.rb
|
137
140
|
- lib/schron/paginated_results.rb
|
138
141
|
- lib/schron/paging.rb
|
139
142
|
- lib/schron/query.rb
|
@@ -150,7 +153,9 @@ files:
|
|
150
153
|
- spec/lib/schron/datastore/memory_spec.rb
|
151
154
|
- spec/lib/schron/datastore/mongo_spec.rb
|
152
155
|
- spec/lib/schron/datastore/sequel_spec.rb
|
156
|
+
- spec/lib/schron/db_spec.rb
|
153
157
|
- spec/lib/schron/dsl_spec.rb
|
158
|
+
- spec/lib/schron/identity_map_repository_spec.rb
|
154
159
|
- spec/lib/schron/identity_map_spec.rb
|
155
160
|
- spec/lib/schron/paginaged_results_spec.rb
|
156
161
|
- spec/lib/schron/query_spec.rb
|
@@ -185,7 +190,9 @@ test_files:
|
|
185
190
|
- spec/lib/schron/datastore/memory_spec.rb
|
186
191
|
- spec/lib/schron/datastore/mongo_spec.rb
|
187
192
|
- spec/lib/schron/datastore/sequel_spec.rb
|
193
|
+
- spec/lib/schron/db_spec.rb
|
188
194
|
- spec/lib/schron/dsl_spec.rb
|
195
|
+
- spec/lib/schron/identity_map_repository_spec.rb
|
189
196
|
- spec/lib/schron/identity_map_spec.rb
|
190
197
|
- spec/lib/schron/paginaged_results_spec.rb
|
191
198
|
- spec/lib/schron/query_spec.rb
|