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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +19 -0
  5. data/lib/schron.rb +4 -0
  6. data/lib/schron/archive.rb +88 -0
  7. data/lib/schron/archive/interface.rb +52 -0
  8. data/lib/schron/datastore/interface.rb +60 -0
  9. data/lib/schron/datastore/memory.rb +152 -0
  10. data/lib/schron/datastore/mongo.rb +173 -0
  11. data/lib/schron/datastore/mongo/metadata.rb +49 -0
  12. data/lib/schron/datastore/mongo/serializer.rb +38 -0
  13. data/lib/schron/datastore/sequel.rb +137 -0
  14. data/lib/schron/datastore/serializer.rb +46 -0
  15. data/lib/schron/dsl.rb +28 -0
  16. data/lib/schron/error.rb +7 -0
  17. data/lib/schron/id.rb +22 -0
  18. data/lib/schron/identity_map.rb +26 -0
  19. data/lib/schron/paginated_results.rb +28 -0
  20. data/lib/schron/paging.rb +5 -0
  21. data/lib/schron/query.rb +122 -0
  22. data/lib/schron/repository.rb +35 -0
  23. data/lib/schron/repository/interface.rb +36 -0
  24. data/lib/schron/test.rb +22 -0
  25. data/lib/schron/test/archive_examples.rb +133 -0
  26. data/lib/schron/test/datastore_examples.rb +419 -0
  27. data/lib/schron/test/entity.rb +21 -0
  28. data/lib/schron/test/repository_examples.rb +68 -0
  29. data/lib/schron/util.rb +27 -0
  30. data/schron.gemspec +24 -0
  31. data/spec/lib/schron/archive_spec.rb +26 -0
  32. data/spec/lib/schron/datastore/memory_spec.rb +9 -0
  33. data/spec/lib/schron/datastore/mongo_spec.rb +23 -0
  34. data/spec/lib/schron/datastore/sequel_spec.rb +40 -0
  35. data/spec/lib/schron/dsl_spec.rb +46 -0
  36. data/spec/lib/schron/identity_map_spec.rb +36 -0
  37. data/spec/lib/schron/paginaged_results_spec.rb +27 -0
  38. data/spec/lib/schron/query_spec.rb +46 -0
  39. data/spec/lib/schron/repository_spec.rb +27 -0
  40. data/spec/spec_helper.rb +9 -0
  41. data/spec/support/test_entities.rb +15 -0
  42. 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
@@ -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,9 @@
1
+ require 'spec_helper'
2
+ require 'schron/datastore/memory'
3
+
4
+ describe Schron::Datastore::Memory do
5
+ subject(:ds) { described_class.new }
6
+
7
+ include_examples 'schron datastore'
8
+
9
+ 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
@@ -0,0 +1,9 @@
1
+ require 'logger'
2
+ ENV['LOG_LEVEL'] ||= Logger::WARN.to_s
3
+
4
+ LOGGER = Logger.new(STDOUT)
5
+ LOGGER.level = ENV['LOG_LEVEL'].to_i
6
+
7
+ require 'schron/test'
8
+
9
+ Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |f| require(f) }