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