hypostasis 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f4adf685f294fec93972358ba0f44f733ca1711
4
- data.tar.gz: 0430fec44b635d781d0d6a576cf4013afb7f0824
3
+ metadata.gz: 1c80aa68763652e26b5530ce3a5edae29146fe5b
4
+ data.tar.gz: 67ea0fe2e5b19379894a8ac743e9d8e3d5d4bb3c
5
5
  SHA512:
6
- metadata.gz: c44ccc9c76c56b257e3f86782995efbe96e01ad400fb1f098861c1bb31482cb774539f9a35edeb6759c30bc7e60637205c918750a0294a29d3fb793020b8349c
7
- data.tar.gz: 4651931a11a248d52845b6c57c8f697211983c28e6bb72fe2f1ae5b2319e8bfad43b778d20c7a74e4c8698e49985251e78a2df4264352082aa997b7c746cd8ce
6
+ metadata.gz: 5032721bdb5e7077d73150fab9c93c8a81a4c2b31ebe5d94ad3551b0628d5c2e814e24dff04dab9f6fcd378ff76a783614b2b7f0af0b8797b628f595a393d8f1
7
+ data.tar.gz: 81b5fdc2b7ad080af654c889be5956ad48e7e5dbcb2a833e29bdb797439b00643de14582d1fbd8e5d145204b32a780a58098b077eaaa3e311ab4b6630c5af0fb
data/hypostasis.gemspec CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = %w{lib}
20
20
 
21
- spec.add_dependency 'fdb', '= 1.0.1'
21
+ spec.add_dependency 'fdb', '~> 1.0.1'
22
+ spec.add_dependency 'activesupport', '>= 3.2.0'
22
23
 
23
24
  spec.add_development_dependency 'bundler', '~> 1.3'
24
25
  spec.add_development_dependency 'rake'
@@ -14,4 +14,16 @@ module Hypostasis::DataModels::Document
14
14
  def for_field(document, field, type)
15
15
  for_document(document) + '\\' + Hypostasis::Tuple.new(field.to_s, type.to_s).to_s
16
16
  end
17
+
18
+ def for_index(document, field_name, value)
19
+ class_name = document.is_a?(Class) ? document.to_s : document.class.to_s
20
+ index_path = Hypostasis::Tuple.new('indexes', class_name).to_s
21
+ value = value.to_s unless value.is_a?(Fixnum) || value.is_a?(Bignum)
22
+ if document.is_a?(Class)
23
+ field_path = Hypostasis::Tuple.new(field_name.to_s, value).to_s
24
+ else
25
+ field_path = Hypostasis::Tuple.new(field_name.to_s, value, document.id.to_s).to_s
26
+ end
27
+ name.to_s + '\\' + index_path + '\\' + field_path
28
+ end
17
29
  end
@@ -1,11 +1,19 @@
1
+ require 'active_support/concern'
2
+
3
+ require 'hypostasis/document/fields'
4
+ require 'hypostasis/document/indexes'
5
+ require 'hypostasis/document/persistence'
6
+ require 'hypostasis/document/findable'
7
+
1
8
  module Hypostasis::Document
2
- def self.included(base)
3
- base.extend ClassMethods
4
- base.class_eval do
5
- attr_reader :id
6
- # Register somewhere?
7
- end
8
- end
9
+ extend ActiveSupport::Concern
10
+
11
+ include Hypostasis::Document::Fields
12
+ include Hypostasis::Document::Indexes
13
+ include Hypostasis::Document::Persistence
14
+ include Hypostasis::Document::Findable
15
+
16
+ attr_reader :id
9
17
 
10
18
  def initialize(*attributes)
11
19
  self.class.namespace.open
@@ -15,24 +23,6 @@ module Hypostasis::Document
15
23
  attributes.each {|hsh| hsh.each {|name, value| @fields[name.to_sym] = value}}
16
24
  end
17
25
 
18
- def save
19
- generate_id
20
- self.class.namespace.transact do |tr|
21
- tr.set(self.class.namespace.for_document(self), true.to_s)
22
-
23
- @fields.each do |field_name, value|
24
- tr.set(self.class.namespace.for_field(self, field_name, value.class.to_s), value.to_s)
25
- end
26
- end
27
- self
28
- end
29
-
30
- def destroy
31
- self.class.namespace.transact do |tr|
32
- tr.clear_range_start_with(self.class.namespace.for_document(self))
33
- end
34
- end
35
-
36
26
  def generate_id
37
27
  @id ||= SecureRandom.uuid
38
28
  end
@@ -55,50 +45,5 @@ module Hypostasis::Document
55
45
  def supported_field_types
56
46
  @@supported_field_types ||= %w{Fixnum Bignum String Integer Float Date DateTime Time Boolean}
57
47
  end
58
-
59
- def field(name, options = {})
60
- register_field(name.to_sym)
61
- create_accessors(name.to_s, options)
62
- end
63
-
64
- def fields
65
- @@fields
66
- end
67
-
68
- def register_field(name)
69
- @@fields = [] unless defined?(@@fields)
70
- @@fields << name.to_sym
71
- end
72
-
73
- def create_accessors(name, options)
74
- self.class_eval do
75
- define_method(name) { @fields[name.to_sym] || nil }
76
- define_method("#{name}=") {|value| @fields[name.to_sym] = value}
77
- end
78
- end
79
-
80
- def create(*attributes)
81
- self.new(*attributes).save
82
- end
83
-
84
- def find(id)
85
- document_keys = []
86
- namespace.transact do |tr|
87
- document_keys = tr.get_range_start_with(namespace.for_document(self, id))
88
- end
89
- #raise Hypostasis::Errors::DocumentNotFound if document_keys.empty?
90
- attributes = {}
91
- id = Hypostasis::Tuple.unpack(document_keys.first.key.split('\\')[1]).to_a[1]
92
- document_keys.each do |key|
93
- attribute_tuple = key.key.split('\\')[2]
94
- next if attribute_tuple.nil?
95
- unpacked_key = Hypostasis::Tuple.unpack(attribute_tuple)
96
- raw_value = key.value
97
- attributes[unpacked_key.to_a[0].to_sym] = reconstitute_value(unpacked_key, raw_value)
98
- end
99
- document = self.new(attributes)
100
- document.set_id(id)
101
- document
102
- end
103
48
  end
104
49
  end
@@ -0,0 +1,28 @@
1
+ module Hypostasis::Document
2
+ module Fields
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def field(name, options = {})
7
+ register_field(name.to_sym)
8
+ create_accessors(name.to_s, options)
9
+ end
10
+
11
+ def fields
12
+ @@fields
13
+ end
14
+
15
+ def register_field(name)
16
+ @@fields = [] unless defined?(@@fields)
17
+ @@fields << name.to_sym
18
+ end
19
+
20
+ def create_accessors(name, options)
21
+ self.class_eval do
22
+ define_method(name) { @fields[name.to_sym] || nil }
23
+ define_method("#{name}=") {|value| @fields[name.to_sym] = value}
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ module Hypostasis::Document
2
+ module Findable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def find(id)
7
+ document_keys = []
8
+ namespace.transact do |tr|
9
+ document_keys = tr.get_range_start_with(namespace.for_document(self, id))
10
+ end
11
+ #raise Hypostasis::Errors::DocumentNotFound if document_keys.empty?
12
+ attributes = {}
13
+ id = Hypostasis::Tuple.unpack(document_keys.first.key.split('\\')[1]).to_a[1]
14
+ document_keys.each do |key|
15
+ attribute_tuple = key.key.split('\\')[2]
16
+ next if attribute_tuple.nil?
17
+ unpacked_key = Hypostasis::Tuple.unpack(attribute_tuple)
18
+ raw_value = key.value
19
+ attributes[unpacked_key.to_a[0].to_sym] = reconstitute_value(unpacked_key, raw_value)
20
+ end
21
+ document = self.new(attributes)
22
+ document.set_id(id)
23
+ document
24
+ end
25
+
26
+ def find_where(field_value_pairs)
27
+ results = []
28
+ namespace.transact do |tr|
29
+ field_value_pairs.each do |field, value|
30
+ results << tr.get_range_start_with(namespace.for_index(self, field, value)).to_a
31
+ end
32
+ end
33
+ results.flatten!
34
+ results.collect! {|result| Hypostasis::Tuple.unpack(result.key.split('\\').last).to_a.last }.compact!
35
+ results.select! {|e| results.count(e) == field_value_pairs.size}
36
+ results.uniq!
37
+ results.collect! {|result| find(result) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ module Hypostasis::Document
2
+ module Indexes
3
+ extend ActiveSupport::Concern
4
+
5
+ def indexed_fields_to_commit
6
+ self.class.indexed_fields.collect do |field_name|
7
+ self.class.namespace.for_index(self, field_name, @fields[field_name])
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def index(field_name, options = {})
13
+ @@indexed_fields = [] unless defined? @@indexed_fields
14
+ @@indexed_fields << field_name.to_sym
15
+ end
16
+
17
+ def indexed_fields
18
+ @@indexed_fields
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ module Hypostasis::Document
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ def save
6
+ generate_id
7
+ self.class.namespace.transact do |tr|
8
+ tr.set(self.class.namespace.for_document(self), true.to_s)
9
+
10
+ @fields.each do |field_name, value|
11
+ tr.set(self.class.namespace.for_field(self, field_name, value.class.to_s), value.to_s)
12
+ end
13
+
14
+ indexed_fields_to_commit.each do |key|
15
+ tr.set(key, 'true')
16
+ end
17
+ end
18
+ self
19
+ end
20
+
21
+ def destroy
22
+ self.class.namespace.transact do |tr|
23
+ tr.clear_range_start_with(self.class.namespace.for_document(self))
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def create(*attributes)
29
+ self.new(*attributes).save
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,6 +3,7 @@ module Hypostasis::Errors
3
3
  class NonExistentNamespace < StandardError; end
4
4
  class CanNotReadNamespaceConfig < StandardError; end
5
5
  class UnknownNamespaceDataModel < StandardError; end
6
+ class NamespaceDataModelMismatch < StandardError; end
6
7
  class InvalidKeyPath < StandardError; end
7
8
  class KeyPathExhausted < StandardError; end
8
9
  class InvalidTuple < StandardError; end
@@ -19,6 +19,10 @@ class Hypostasis::Namespace
19
19
  true
20
20
  end
21
21
 
22
+ def to_s
23
+ @name
24
+ end
25
+
22
26
  def self.create(name, options = {})
23
27
  raise Hypostasis::Errors::NamespaceAlreadyCreated unless database[name].nil?
24
28
  merged_options = { data_model: :key_value }.merge(options)
@@ -1,3 +1,3 @@
1
1
  module Hypostasis
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
@@ -5,10 +5,12 @@ describe Hypostasis::Document do
5
5
 
6
6
  before do
7
7
  Hypostasis::Connection.create_namespace 'sample_docs', data_model: :document
8
+ Hypostasis::Connection.create_namespace 'indexed_docs', data_model: :document
8
9
  end
9
10
 
10
11
  after do
11
12
  Hypostasis::Connection.destroy_namespace 'sample_docs'
13
+ Hypostasis::Connection.destroy_namespace 'indexed_docs'
12
14
  end
13
15
 
14
16
  it { subject.must_respond_to :name }
@@ -27,46 +29,78 @@ describe Hypostasis::Document do
27
29
 
28
30
  describe '#create' do
29
31
  let(:subject) { SampleDocument.create(name: 'John', age: 21, dob: Date.today.prev_year(21)) }
30
- let(:document_tuple) { Hypostasis::Tuple.new(SampleDocument.to_s, subject.id.to_s).to_s }
31
-
32
- def field_path(name, type)
33
- 'sample_docs\\' + document_tuple + '\\' + Hypostasis::Tuple.new(name.to_s, type.to_s).to_s
34
- end
35
32
 
36
33
  after do
37
34
  subject.destroy
38
35
  end
39
36
 
40
37
  it { subject.id.wont_be_nil }
41
- it { database.get(field_path(:name, String)).must_equal 'John' }
42
- it { database.get(field_path(:age, Fixnum)).must_equal '21' }
43
- it { database.get(field_path(:dob, Date)).must_equal Date.today.prev_year(21).to_s }
38
+ it { database.get(document_path(subject)).must_equal 'true' }
39
+ it { database.get(field_path(subject, :name, String)).must_equal 'John' }
40
+ it { database.get(field_path(subject, :age, Fixnum)).must_equal '21' }
41
+ it { database.get(field_path(subject, :dob, Date)).must_equal Date.today.prev_year(21).to_s }
44
42
  end
45
43
 
46
44
  describe '#save' do
47
- let(:document_tuple) { Hypostasis::Tuple.new(SampleDocument.to_s, @document.id.to_s).to_s }
45
+ let(:subject) { SampleDocument.new(name: 'John', age: 21, dob: Date.today.prev_year(21)) }
48
46
 
49
- def field_path(name, type)
50
- 'sample_docs\\' + document_tuple + '\\' + Hypostasis::Tuple.new(name.to_s, type.to_s).to_s
47
+ before do
48
+ subject.save
51
49
  end
52
50
 
53
- before do
54
- @document = subject.save
51
+ after do
52
+ subject.destroy
55
53
  end
56
54
 
57
- it { database.get(field_path(:name, String)).must_equal 'John' }
58
- it { database.get(field_path(:age, Fixnum)).must_equal '21' }
59
- it { database.get(field_path(:dob, Date)).must_equal Date.today.prev_year(21).to_s }
55
+ it { subject.id.wont_be_nil }
56
+ it { database.get(document_path(subject)).must_equal 'true' }
57
+ it { database.get(field_path(subject, :name, String)).must_equal 'John' }
58
+ it { database.get(field_path(subject, :age, Fixnum)).must_equal '21' }
59
+ it { database.get(field_path(subject, :dob, Date)).must_equal Date.today.prev_year(21).to_s }
60
60
  end
61
61
 
62
62
  describe '.find' do
63
63
  let(:document_id) { subject.save.id }
64
64
 
65
+ after do
66
+ subject.destroy
67
+ end
68
+
65
69
  it { SampleDocument.find(document_id).is_a?(SampleDocument).must_equal true }
66
70
  it { SampleDocument.find(document_id).id.must_equal document_id }
67
71
  end
68
72
 
69
73
  describe 'indexing' do
74
+ before do
75
+ IndexedDocument.create(name: 'John', age: 21, dob: Date.today.prev_year(21))
76
+ IndexedDocument.create(name: 'Jane', age: 21, dob: Date.today.prev_year(21))
77
+ IndexedDocument.create(name: 'John', age: 23, dob: Date.today.prev_year(23))
78
+ IndexedDocument.create(name: 'Tom', age: 20, dob: Date.today.prev_year(20))
79
+ end
80
+
81
+ it { database.get_range_start_with(index_path(IndexedDocument, :name)).size.must_equal 4 }
82
+ it { database.get_range_start_with(index_path(IndexedDocument, :age)).size.must_equal 4 }
83
+
84
+ it { database.get_range_start_with(index_path(IndexedDocument, :name, 'John')).size.must_equal 2 }
85
+ it { database.get_range_start_with(index_path(IndexedDocument, :name, 'Jane')).size.must_equal 1 }
86
+
87
+ it { database.get_range_start_with(index_path(IndexedDocument, :age, 21)).size.must_equal 2 }
88
+ end
89
+
90
+ describe '.find_where' do
91
+ before do
92
+ IndexedDocument.create(name: 'John', age: 21, dob: Date.today.prev_year(21))
93
+ IndexedDocument.create(name: 'Jane', age: 21, dob: Date.today.prev_year(21))
94
+ IndexedDocument.create(name: 'John', age: 23, dob: Date.today.prev_year(23))
95
+ IndexedDocument.create(name: 'Tom', age: 20, dob: Date.today.prev_year(20))
96
+ end
97
+
98
+ it { IndexedDocument.find_where(name: 'John').size.must_equal 2 }
99
+ it { IndexedDocument.find_where(age: 21).size.must_equal 2 }
100
+ it { IndexedDocument.find_where(name: 'Tom').size.must_equal 1 }
101
+ it { IndexedDocument.find_where(name: 'Tom').first.is_a?(IndexedDocument).must_equal true }
70
102
 
103
+ it { IndexedDocument.find_where(name: 'John', age: 23).size.must_equal 1 }
104
+ it { IndexedDocument.find_where(name: 'John', age: 23).first.is_a?(IndexedDocument).must_equal true }
71
105
  end
72
106
  end
@@ -14,6 +14,7 @@ require 'hypostasis'
14
14
  require 'minitest/autorun'
15
15
 
16
16
  require 'support/sample_document'
17
+ require 'support/indexed_document'
17
18
 
18
19
  class Minitest::Spec
19
20
 
@@ -21,4 +22,29 @@ class Minitest::Spec
21
22
  @database ||= FDB.open
22
23
  end
23
24
 
25
+ def document_path(document)
26
+ document_namespace = document.class.namespace.to_s
27
+ document_tuple = Hypostasis::Tuple.new(document.class.to_s, document.id.to_s).to_s
28
+ document_namespace + '\\' + document_tuple
29
+ end
30
+
31
+ def field_path(document, name, type)
32
+ document_namespace = document.class.namespace.to_s
33
+ document_tuple = Hypostasis::Tuple.new(document.class.to_s, document.id.to_s).to_s
34
+ field_tuple = Hypostasis::Tuple.new(name.to_s, type.to_s).to_s
35
+ document_namespace + '\\' + document_tuple + '\\' + field_tuple
36
+ end
37
+
38
+ def index_path(klass, field_name, value = nil)
39
+ namespace = klass.namespace.to_s
40
+ indexes_tuple = Hypostasis::Tuple.new('indexes', klass.to_s).to_s
41
+ if value.nil?
42
+ indexed_value_tuple = Hypostasis::Tuple.new(field_name.to_s).to_s
43
+ else
44
+ value = value.to_s unless value.is_a?(Fixnum) || value.is_a?(Bignum)
45
+ indexed_value_tuple = Hypostasis::Tuple.new(field_name.to_s, value).to_s
46
+ end
47
+ namespace + '\\' + indexes_tuple + '\\' + indexed_value_tuple
48
+ end
49
+
24
50
  end
@@ -0,0 +1,12 @@
1
+ class IndexedDocument
2
+ include Hypostasis::Document
3
+
4
+ use_namespace 'indexed_docs'
5
+
6
+ field :name
7
+ field :age
8
+ field :dob
9
+
10
+ index :name
11
+ index :age
12
+ end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hypostasis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Thompson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-22 00:00:00.000000000 Z
11
+ date: 2014-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fdb
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '='
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '='
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -88,6 +102,10 @@ files:
88
102
  - lib/hypostasis/data_models/key_value.rb
89
103
  - lib/hypostasis/data_models/utilities.rb
90
104
  - lib/hypostasis/document.rb
105
+ - lib/hypostasis/document/fields.rb
106
+ - lib/hypostasis/document/findable.rb
107
+ - lib/hypostasis/document/indexes.rb
108
+ - lib/hypostasis/document/persistence.rb
91
109
  - lib/hypostasis/errors.rb
92
110
  - lib/hypostasis/key_path.rb
93
111
  - lib/hypostasis/namespace.rb
@@ -98,6 +116,7 @@ files:
98
116
  - test/key_path_spec.rb
99
117
  - test/minitest_helper.rb
100
118
  - test/namespace_spec.rb
119
+ - test/support/indexed_document.rb
101
120
  - test/support/sample_document.rb
102
121
  - test/tuple_spec.rb
103
122
  homepage: ''
@@ -120,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
139
  version: '0'
121
140
  requirements: []
122
141
  rubyforge_project:
123
- rubygems_version: 2.2.1
142
+ rubygems_version: 2.1.11
124
143
  signing_key:
125
144
  specification_version: 4
126
145
  summary: A layer for FoundationDB providing multiple data models for Ruby.
@@ -130,5 +149,6 @@ test_files:
130
149
  - test/key_path_spec.rb
131
150
  - test/minitest_helper.rb
132
151
  - test/namespace_spec.rb
152
+ - test/support/indexed_document.rb
133
153
  - test/support/sample_document.rb
134
154
  - test/tuple_spec.rb