hypostasis 0.2.0 → 0.2.1

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