schron 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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) }