schron 0.0.2
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 +7 -0
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/Rakefile +19 -0
- data/lib/schron.rb +4 -0
- data/lib/schron/archive.rb +88 -0
- data/lib/schron/archive/interface.rb +52 -0
- data/lib/schron/datastore/interface.rb +60 -0
- data/lib/schron/datastore/memory.rb +152 -0
- data/lib/schron/datastore/mongo.rb +173 -0
- data/lib/schron/datastore/mongo/metadata.rb +49 -0
- data/lib/schron/datastore/mongo/serializer.rb +38 -0
- data/lib/schron/datastore/sequel.rb +137 -0
- data/lib/schron/datastore/serializer.rb +46 -0
- data/lib/schron/dsl.rb +28 -0
- data/lib/schron/error.rb +7 -0
- data/lib/schron/id.rb +22 -0
- data/lib/schron/identity_map.rb +26 -0
- data/lib/schron/paginated_results.rb +28 -0
- data/lib/schron/paging.rb +5 -0
- data/lib/schron/query.rb +122 -0
- data/lib/schron/repository.rb +35 -0
- data/lib/schron/repository/interface.rb +36 -0
- data/lib/schron/test.rb +22 -0
- data/lib/schron/test/archive_examples.rb +133 -0
- data/lib/schron/test/datastore_examples.rb +419 -0
- data/lib/schron/test/entity.rb +21 -0
- data/lib/schron/test/repository_examples.rb +68 -0
- data/lib/schron/util.rb +27 -0
- data/schron.gemspec +24 -0
- data/spec/lib/schron/archive_spec.rb +26 -0
- data/spec/lib/schron/datastore/memory_spec.rb +9 -0
- data/spec/lib/schron/datastore/mongo_spec.rb +23 -0
- data/spec/lib/schron/datastore/sequel_spec.rb +40 -0
- data/spec/lib/schron/dsl_spec.rb +46 -0
- data/spec/lib/schron/identity_map_spec.rb +36 -0
- data/spec/lib/schron/paginaged_results_spec.rb +27 -0
- data/spec/lib/schron/query_spec.rb +46 -0
- data/spec/lib/schron/repository_spec.rb +27 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/test_entities.rb +15 -0
- metadata +192 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Schron
|
2
|
+
module Test
|
3
|
+
class Entity
|
4
|
+
|
5
|
+
attr_reader :attributes
|
6
|
+
def initialize(attrs={})
|
7
|
+
@attributes = attrs
|
8
|
+
end
|
9
|
+
|
10
|
+
def id
|
11
|
+
attributes[:id]
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(entity)
|
15
|
+
entity.is_a?(self.class) && attributes == entity.attributes
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
if defined?(RSpec)
|
2
|
+
|
3
|
+
require 'schron/query'
|
4
|
+
|
5
|
+
# Be sure to set the repo before inclusion:
|
6
|
+
# let(:repo) { ... }
|
7
|
+
shared_examples 'schron repository' do
|
8
|
+
|
9
|
+
let(:entity_class) { repo.entity_class }
|
10
|
+
|
11
|
+
describe 'update' do
|
12
|
+
it 'returns the updated object' do
|
13
|
+
object = repo.insert(repo.entity_class.new)
|
14
|
+
object.attributes[:a] = 6
|
15
|
+
|
16
|
+
result = repo.update(object)
|
17
|
+
|
18
|
+
expect_same_entity result, repo.entity_class.new(id: object.id, a: 6)
|
19
|
+
expect_same_entity repo.get(result.id), result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'multi update' do
|
24
|
+
it 'returns the updated objects' do
|
25
|
+
objects = repo.multi_insert([repo.entity_class.new, repo.entity_class.new])
|
26
|
+
objects[0].attributes[:a] = 6
|
27
|
+
objects[1].attributes[:a] = 7
|
28
|
+
|
29
|
+
results = repo.multi_update(objects)
|
30
|
+
|
31
|
+
expect(results[0].attributes).to eq(id: objects[0].id, a: 6)
|
32
|
+
expect(results[1].attributes).to eq(id: objects[1].id, a: 7)
|
33
|
+
0.upto(1) do |i|
|
34
|
+
expect_same_entity repo.get(results[i].id), results[i]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'remove' do
|
40
|
+
it 'returns nil when no object is removed' do
|
41
|
+
expect(repo.remove('1234')).to be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns nil when an object is removed' do
|
45
|
+
obj = repo.insert(repo.entity_class.new)
|
46
|
+
expect(repo.remove(obj.id)).to be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'removes the object from the repository by id' do
|
50
|
+
obj = repo.insert(repo.entity_class.new)
|
51
|
+
repo.remove(obj.id)
|
52
|
+
expect(repo.get(obj.id)).to be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'multi remove' do
|
57
|
+
it 'removes the objects from the repository by id and returns nil' do
|
58
|
+
objects = repo.multi_insert([entity_class.new, entity_class.new])
|
59
|
+
ids = objects.map(&:id)
|
60
|
+
result = repo.multi_remove(ids)
|
61
|
+
expect(result).to be_nil
|
62
|
+
expect(repo.multi_get(ids)).to be_empty
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/schron/util.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Schron
|
2
|
+
module Util
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def symbolize_keys(obj)
|
6
|
+
case obj
|
7
|
+
when Hash
|
8
|
+
obj.reduce({}) do |symbolized, (k, v)|
|
9
|
+
symbolized[k.to_sym] = symbolize_keys(v)
|
10
|
+
symbolized
|
11
|
+
end
|
12
|
+
when Array
|
13
|
+
obj.map { |v| symbolize_keys(v) }
|
14
|
+
else
|
15
|
+
obj
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def sorted_by_id_list(records, ids)
|
20
|
+
if ids.respond_to?(:index)
|
21
|
+
records.sort_by { |r| ids.index(r[:id]) }
|
22
|
+
else
|
23
|
+
records
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/schron.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "schron"
|
7
|
+
spec.version = "0.0.2"
|
8
|
+
spec.authors = ["David Faber"]
|
9
|
+
spec.email = ["david@1bios.co"]
|
10
|
+
spec.summary = %q{Repository implementation for entity persistence}
|
11
|
+
|
12
|
+
spec.files = `git ls-files -z`.split("\x0")
|
13
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
14
|
+
spec.require_paths = ["lib"]
|
15
|
+
|
16
|
+
|
17
|
+
spec.add_development_dependency 'rake', '~> 10.1.0'
|
18
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
19
|
+
spec.add_development_dependency "rspec", "~> 3.0.0.beta1"
|
20
|
+
spec.add_development_dependency 'sequel', '~> 4.9.0'
|
21
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3.9'
|
22
|
+
spec.add_development_dependency 'mongo', '~> 1.10.0'
|
23
|
+
spec.add_development_dependency 'bson_ext', '~> 1.10.0'
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/archive'
|
3
|
+
require 'schron/datastore/memory'
|
4
|
+
|
5
|
+
describe Schron::Archive do
|
6
|
+
|
7
|
+
subject(:archive) do
|
8
|
+
archive_class.new(ds,
|
9
|
+
kind: 'test',
|
10
|
+
entity_class: Schron::Test::Entity,
|
11
|
+
indexed_fields: [])
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:archive_class) do
|
15
|
+
Class.new do
|
16
|
+
include Schron::Archive
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:ds) do
|
21
|
+
Schron::Datastore::Memory.new
|
22
|
+
end
|
23
|
+
|
24
|
+
include_examples 'schron archive'
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/datastore/mongo'
|
3
|
+
|
4
|
+
describe Schron::Datastore::Mongo do
|
5
|
+
let(:ds) { described_class.new(db) }
|
6
|
+
|
7
|
+
let(:db) { client[db_name] }
|
8
|
+
|
9
|
+
let(:db_name) { 'test' }
|
10
|
+
|
11
|
+
let(:client) { Mongo::MongoClient.new }
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
db.collections.each do |coll|
|
15
|
+
unless coll.name =~ /^system\./
|
16
|
+
coll.drop
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
include_examples 'schron datastore'
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
require 'schron/datastore/sequel'
|
4
|
+
|
5
|
+
describe Schron::Datastore::Sequel do
|
6
|
+
subject(:ds) { described_class.new(db) }
|
7
|
+
|
8
|
+
let(:db) { Sequel.sqlite(':memory:', logger: LOGGER) }
|
9
|
+
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
db.create_table :entities do
|
13
|
+
String :id, primary_key: true
|
14
|
+
end
|
15
|
+
db.create_table :named do
|
16
|
+
String :id, primary_key: true
|
17
|
+
String :name
|
18
|
+
end
|
19
|
+
db.create_table :with_types do
|
20
|
+
String :id, primary_key: true
|
21
|
+
String :array
|
22
|
+
String :set
|
23
|
+
Date :date
|
24
|
+
Time :time
|
25
|
+
String :date_time
|
26
|
+
String :hash
|
27
|
+
String :children
|
28
|
+
Float :float
|
29
|
+
String :name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:each) do
|
34
|
+
db.drop_table? :entities
|
35
|
+
db.drop_table? :named
|
36
|
+
end
|
37
|
+
|
38
|
+
include_examples 'schron datastore'
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/dsl'
|
3
|
+
|
4
|
+
describe Schron::DSL do
|
5
|
+
let(:repo_class) do
|
6
|
+
Class.new do
|
7
|
+
extend Schron::DSL
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
describe 'kind' do
|
13
|
+
it 'should set and get on the class' do
|
14
|
+
repo_class.kind 'testing'
|
15
|
+
expect(repo_class.kind).to eq('testing')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should not be inherited by subclasses' do
|
19
|
+
repo_class.kind 'testing'
|
20
|
+
sub = Class.new(repo_class)
|
21
|
+
expect(sub.kind).to be_nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'entity class' do
|
26
|
+
it 'should set and get on the class' do
|
27
|
+
klass = Class.new
|
28
|
+
repo_class.entity_class klass
|
29
|
+
expect(repo_class.entity_class).to be(klass)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should not be inherited by subclasses' do
|
33
|
+
repo_class.entity_class(Object)
|
34
|
+
sub = Class.new(repo_class)
|
35
|
+
expect(sub.entity_class).to be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'indexed fields' do
|
40
|
+
it 'sets one at a time with indexed_field, and gets with indexed_fields' do
|
41
|
+
repo_class.indexed_field :a
|
42
|
+
repo_class.indexed_field :b
|
43
|
+
expect(repo_class.indexed_fields).to eq([:a, :b])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/identity_map'
|
3
|
+
|
4
|
+
describe Schron::IdentityMap do
|
5
|
+
subject(:map) { described_class.new }
|
6
|
+
|
7
|
+
let(:object) { Object.new }
|
8
|
+
|
9
|
+
describe 'put and get' do
|
10
|
+
it 'shoudl return nil if no object exists with the id' do
|
11
|
+
expect(map.get(123)).to be_nil
|
12
|
+
end
|
13
|
+
it 'should put things in and take things out by id' do
|
14
|
+
map.put(1, object)
|
15
|
+
expect(map.get(1)).to be(object)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'fetch' do
|
20
|
+
it 'should return objects already in the map without calling the block' do
|
21
|
+
spy = double
|
22
|
+
expect(spy).not_to receive(:uh_oh)
|
23
|
+
map.put(1, object)
|
24
|
+
fetched = map.fetch(1) { double.uh_oh }
|
25
|
+
expect(fetched).to be(object)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'has?' do
|
30
|
+
it 'returns true if the map has an entry for the given key, else false' do
|
31
|
+
expect(map.has?(1)).to be(false)
|
32
|
+
map.put(1, object)
|
33
|
+
expect(map.has?(1)).to be(true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/paginated_results'
|
3
|
+
|
4
|
+
describe Schron::PaginatedResults do
|
5
|
+
|
6
|
+
let(:results) do
|
7
|
+
ary = described_class.new([1,2])
|
8
|
+
ary.paging.previous_page = 1
|
9
|
+
ary.paging.current_page = 2
|
10
|
+
ary.paging.next_page = 3
|
11
|
+
ary
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'closure of enumerable operations' do
|
15
|
+
|
16
|
+
def verify_paging(p)
|
17
|
+
expect(p.previous_page).to eq(1)
|
18
|
+
expect(p.current_page).to eq(2)
|
19
|
+
expect(p.next_page).to eq(3)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'persists its paging' do
|
23
|
+
p = results.map {|i| i}.paging
|
24
|
+
verify_paging(p)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/query'
|
3
|
+
require 'schron/datastore/memory'
|
4
|
+
|
5
|
+
describe Schron::Query do
|
6
|
+
subject(:query) { described_class.new(kind, ds) }
|
7
|
+
|
8
|
+
let(:kind) { 'tests' }
|
9
|
+
let(:ds) { Schron::Datastore::Memory.new }
|
10
|
+
|
11
|
+
describe 'page' do
|
12
|
+
it 'sets the limit and offset' do
|
13
|
+
query.page(3, per_page: 7)
|
14
|
+
expect(query.limit).to eq(7)
|
15
|
+
expect(query.offset).to eq(14)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'paginated result set' do
|
19
|
+
before(:each) do
|
20
|
+
10.times { ds.insert('tests', {}) }
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has the current page' do
|
24
|
+
results = query.page(3, per_page: 1).all
|
25
|
+
expect(results.paging.current_page).to eq(3)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'indicates next and previous page when present' do
|
29
|
+
query.page(2, per_page: 3)
|
30
|
+
results = query.all
|
31
|
+
expect(results.paging.previous_page).to eq(1)
|
32
|
+
expect(results.paging.next_page).to eq(3)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'shows previous page as nil on the first page' do
|
36
|
+
results = query.page(1).all
|
37
|
+
expect(results.paging.previous_page).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'shows next page as nil on last page' do
|
41
|
+
results = query.page(1, per_page: 10).all
|
42
|
+
expect(results.paging.next_page).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'schron/repository'
|
3
|
+
require 'schron/datastore/memory'
|
4
|
+
|
5
|
+
describe Schron::Repository do
|
6
|
+
include Schron::Test
|
7
|
+
|
8
|
+
subject(:repo) do
|
9
|
+
repo_class.new(ds,
|
10
|
+
entity_class: Schron::Test::Entity,
|
11
|
+
kind: 'tests')
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:archive) { repo }
|
15
|
+
let(:ds) { Schron::Datastore::Memory.new }
|
16
|
+
|
17
|
+
let(:repo_class) do
|
18
|
+
Class.new do
|
19
|
+
include Schron::Repository
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include_examples 'schron archive'
|
24
|
+
include_examples 'schron repository'
|
25
|
+
|
26
|
+
|
27
|
+
end
|