strokedb 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 (59) hide show
  1. data/CONTRIBUTORS +7 -0
  2. data/CREDITS +13 -0
  3. data/README +44 -0
  4. data/bin/sdbc +2 -0
  5. data/lib/config/config.rb +161 -0
  6. data/lib/data_structures/inverted_list.rb +297 -0
  7. data/lib/data_structures/point_query.rb +24 -0
  8. data/lib/data_structures/skiplist.rb +302 -0
  9. data/lib/document/associations.rb +107 -0
  10. data/lib/document/callback.rb +11 -0
  11. data/lib/document/coercions.rb +57 -0
  12. data/lib/document/delete.rb +28 -0
  13. data/lib/document/document.rb +684 -0
  14. data/lib/document/meta.rb +261 -0
  15. data/lib/document/slot.rb +199 -0
  16. data/lib/document/util.rb +27 -0
  17. data/lib/document/validations.rb +704 -0
  18. data/lib/document/versions.rb +106 -0
  19. data/lib/document/virtualize.rb +82 -0
  20. data/lib/init.rb +57 -0
  21. data/lib/stores/chainable_storage.rb +57 -0
  22. data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
  23. data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
  24. data/lib/stores/remote_store.rb +172 -0
  25. data/lib/stores/skiplist_store/chunk.rb +119 -0
  26. data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
  27. data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
  28. data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
  29. data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
  30. data/lib/stores/store.rb +5 -0
  31. data/lib/sync/chain_sync.rb +38 -0
  32. data/lib/sync/diff.rb +126 -0
  33. data/lib/sync/lamport_timestamp.rb +81 -0
  34. data/lib/sync/store_sync.rb +79 -0
  35. data/lib/sync/stroke_diff/array.rb +102 -0
  36. data/lib/sync/stroke_diff/default.rb +21 -0
  37. data/lib/sync/stroke_diff/hash.rb +186 -0
  38. data/lib/sync/stroke_diff/string.rb +116 -0
  39. data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
  40. data/lib/util/blankslate.rb +42 -0
  41. data/lib/util/ext/blank.rb +50 -0
  42. data/lib/util/ext/enumerable.rb +36 -0
  43. data/lib/util/ext/fixnum.rb +16 -0
  44. data/lib/util/ext/hash.rb +22 -0
  45. data/lib/util/ext/object.rb +8 -0
  46. data/lib/util/ext/string.rb +35 -0
  47. data/lib/util/inflect.rb +217 -0
  48. data/lib/util/java_util.rb +9 -0
  49. data/lib/util/lazy_array.rb +54 -0
  50. data/lib/util/lazy_mapping_array.rb +64 -0
  51. data/lib/util/lazy_mapping_hash.rb +46 -0
  52. data/lib/util/serialization.rb +29 -0
  53. data/lib/util/trigger_partition.rb +136 -0
  54. data/lib/util/util.rb +38 -0
  55. data/lib/util/xml.rb +6 -0
  56. data/lib/view/view.rb +55 -0
  57. data/script/console +70 -0
  58. data/strokedb.rb +75 -0
  59. metadata +148 -0
@@ -0,0 +1,217 @@
1
+ # from the English gem (http://english.rubyforge.org/)
2
+ # http://english.rubyforge.org/rdoc/classes/English/Inflect.html
3
+ #
4
+ # last updated from http://english.rubyforge.org/svn/trunk/lib/english/inflect.rb on 3-31-2008
5
+
6
+ module English
7
+
8
+ # = English Nouns Number Inflection.
9
+ #
10
+ # This module provides english singular <-> plural noun inflections.
11
+ module Inflect
12
+
13
+ @singular_of = {}
14
+ @plural_of = {}
15
+
16
+ @singular_rules = []
17
+ @plural_rules = []
18
+
19
+ class << self
20
+ # Define a general exception.
21
+ def word(singular, plural=nil)
22
+ plural = singular unless plural
23
+ singular_word(singular, plural)
24
+ plural_word(singular, plural)
25
+ end
26
+
27
+ # Define a singularization exception.
28
+ def singular_word(singular, plural)
29
+ @singular_of[plural] = singular
30
+ end
31
+
32
+ # Define a pluralization exception.
33
+ def plural_word(singular, plural)
34
+ @plural_of[singular] = plural
35
+ end
36
+
37
+ # Define a general rule.
38
+ def rule(singular, plural)
39
+ singular_rule(singular, plural)
40
+ plural_rule(singular, plural)
41
+ end
42
+
43
+ # Define a singularization rule.
44
+ def singular_rule(singular, plural)
45
+ @singular_rules << [singular, plural]
46
+ end
47
+
48
+ # Define a plurualization rule.
49
+ def plural_rule(singular, plural)
50
+ @plural_rules << [singular, plural]
51
+ end
52
+
53
+ # Read prepared singularization rules.
54
+ def singularization_rules
55
+ return @singularization_rules if @singularization_rules
56
+ sorted = @singular_rules.sort_by{ |s, p| "#{p}".size }.reverse
57
+ @singularization_rules = sorted.collect do |s, p|
58
+ [ /#{p}$/, "#{s}" ]
59
+ end
60
+ end
61
+
62
+ # Read prepared pluralization rules.
63
+ def pluralization_rules
64
+ return @pluralization_rules if @pluralization_rules
65
+ sorted = @plural_rules.sort_by{ |s, p| "#{s}".size }.reverse
66
+ @pluralization_rules = sorted.collect do |s, p|
67
+ [ /#{s}$/, "#{p}" ]
68
+ end
69
+ end
70
+
71
+ #
72
+ def plural_of
73
+ @plural_of
74
+ end
75
+
76
+ #
77
+ def singular_of
78
+ @singular_of
79
+ end
80
+
81
+ # Convert an English word from plurel to singular.
82
+ #
83
+ # "boys".singular #=> boy
84
+ # "tomatoes".singular #=> tomato
85
+ #
86
+ def singular(word)
87
+ if result = singular_of[word]
88
+ return result.dup
89
+ end
90
+ result = word.dup
91
+ singularization_rules.each do |(match, replacement)|
92
+ break if result.gsub!(match, replacement)
93
+ end
94
+ return result
95
+ end
96
+
97
+ # Alias for #singular (a Railism).
98
+ #
99
+ alias_method(:singularize, :singular)
100
+
101
+ # Convert an English word from singular to plurel.
102
+ #
103
+ # "boy".plural #=> boys
104
+ # "tomato".plural #=> tomatoes
105
+ #
106
+ def plural(word)
107
+ if result = plural_of[word]
108
+ return result.dup
109
+ end
110
+ #return self.dup if /s$/ =~ self # ???
111
+ result = word.dup
112
+ pluralization_rules.each do |(match, replacement)|
113
+ break if result.gsub!(match, replacement)
114
+ end
115
+ return result
116
+ end
117
+
118
+ # Alias for #plural (a Railism).
119
+ alias_method(:pluralize, :plural)
120
+ end
121
+
122
+ # One argument means singular and plural are the same.
123
+
124
+ word 'equipment'
125
+ word 'information'
126
+ word 'money'
127
+ word 'species'
128
+ word 'series'
129
+ word 'fish'
130
+ word 'sheep'
131
+ word 'moose'
132
+ word 'hovercraft'
133
+
134
+ # Two arguments defines a singular and plural exception.
135
+
136
+ word 'Swiss' , 'Swiss'
137
+ word 'life' , 'lives'
138
+ word 'wife' , 'wives'
139
+ word 'virus' , 'viri'
140
+ word 'octopus' , 'octopi'
141
+ word 'goose' , 'geese'
142
+ word 'criterion' , 'criteria'
143
+ word 'alias' , 'aliases'
144
+ word 'status' , 'statuses'
145
+ word 'axis' , 'axes'
146
+ word 'crisis' , 'crises'
147
+ word 'testis' , 'testes'
148
+ word 'child' , 'children'
149
+ word 'person' , 'people'
150
+ word 'potato' , 'potatoes'
151
+ word 'tomato' , 'tomatoes'
152
+ word 'buffalo' , 'buffaloes'
153
+ word 'torpedo' , 'torpedoes'
154
+ word 'quiz' , 'quizes'
155
+ word 'matrix' , 'matrices'
156
+ word 'vertex' , 'vetices'
157
+ word 'index' , 'indices'
158
+ word 'ox' , 'oxen'
159
+ word 'mouse' , 'mice'
160
+ word 'louse' , 'lice'
161
+ word 'thesis' , 'theses'
162
+ word 'thief' , 'thieves'
163
+ word 'analysis' , 'analyses'
164
+
165
+ # One-way singularization exception (convert plural to singular).
166
+
167
+ singular_word 'cactus', 'cacti'
168
+
169
+ # General rules.
170
+
171
+ rule 'hive' , 'hives'
172
+ rule 'rf' , 'rves'
173
+ rule 'af' , 'aves'
174
+ rule 'ero' , 'eroes'
175
+ rule 'man' , 'men'
176
+ rule 'ch' , 'ches'
177
+ rule 'sh' , 'shes'
178
+ rule 'ss' , 'sses'
179
+ rule 'ta' , 'tum'
180
+ rule 'ia' , 'ium'
181
+ rule 'ra' , 'rum'
182
+ rule 'ay' , 'ays'
183
+ rule 'ey' , 'eys'
184
+ rule 'oy' , 'oys'
185
+ rule 'uy' , 'uys'
186
+ rule 'y' , 'ies'
187
+ rule 'x' , 'xes'
188
+ rule 'lf' , 'lves'
189
+ rule 'us' , 'uses'
190
+ rule '' , 's'
191
+
192
+ # One-way singular rules.
193
+
194
+ singular_rule 'of' , 'ofs' # proof
195
+ singular_rule 'o' , 'oes' # hero, heroes
196
+ singular_rule 'f' , 'ves'
197
+
198
+ # One-way plural rules.
199
+
200
+ plural_rule 'fe' , 'ves' # safe, wife
201
+ plural_rule 's' , 'ses'
202
+
203
+ end
204
+ end
205
+
206
+
207
+ class String
208
+ def singular
209
+ English::Inflect.singular(self)
210
+ end
211
+ alias_method(:singularize, :singular)
212
+
213
+ def plural
214
+ English::Inflect.plural(self)
215
+ end
216
+ alias_method(:pluralize, :plural)
217
+ end
@@ -0,0 +1,9 @@
1
+ require 'java'
2
+ # Some java overrides
3
+ module StrokeDB
4
+ module Util
5
+ def self.random_uuid
6
+ java.util.UUID.random_uuid.to_s
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,54 @@
1
+ module StrokeDB
2
+ # Lazy loads items from array.
3
+ #
4
+ # Lazy arrays are backed by Proc returning regular array
5
+ # on first call retrieving it from @load_with_proc.
6
+ #
7
+ # Example:
8
+ #
9
+ # ary = LazyArray.new.load_with { Time.now.to_s.split(/\s/) }
10
+ #
11
+ # On first attempt to access <tt>ary</tt> (including <tt>inspect</tt>) it will
12
+ # evaluate load_with's Proc and update own content with its result:
13
+ #
14
+ # ary
15
+ # # ==> ["Mon", "Mar", "17", "10:35:52", "+0200", "2008"]
16
+ #
17
+ class LazyArray < BlankSlate
18
+ def initialize(*args)
19
+ @load_with_proc = proc {|v| v}
20
+ @array = Array.new(*args)
21
+ end
22
+
23
+ # Proc to execute lazy loading
24
+ def load_with(&block)
25
+ @load_with_proc = block
26
+ self
27
+ end
28
+
29
+ # Make it look like array for outer world
30
+ def class
31
+ Array
32
+ end
33
+
34
+ def method_missing sym, *args, &blk
35
+ if @array.respond_to? sym
36
+ load!
37
+ @array.__send__ sym, *args, &blk
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def load!
46
+ if @load_with_proc
47
+ @array.clear
48
+ @array.concat @load_with_proc.call(@array)
49
+ @load_with_proc = nil
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,64 @@
1
+ module StrokeDB
2
+ # Lazy loads items from array applying procs on each read and write.
3
+ #
4
+ # Example:
5
+ #
6
+ # @ary[i] = 10
7
+ #
8
+ # applies "unmap proc" to new item value.
9
+ #
10
+ # @ary.at(i)
11
+ #
12
+ # applies "map proc" to value just have been read.
13
+ #
14
+ # StrokeDB uses this class to "follow" links to other documents
15
+ # found in slots in a lazy manner.
16
+ #
17
+ # player:
18
+ # model: [@#8b195509-f9c4-4fea-90c9-425b38bdda3e.ea5eda78-d410-44be-8b14-f4e33f6fa047]
19
+ # generation: 4
20
+ #
21
+ # when model collection item is fetched, reference followed and turned into document
22
+ # instance with mapping proc of lazy mapping array.
23
+ class LazyMappingArray < BlankSlate(Array)
24
+ def initialize(*args)
25
+ @map_proc = proc {|v| v}
26
+ @unmap_proc = proc {|v| v}
27
+ super(*args)
28
+ end
29
+
30
+ def map_with(&block)
31
+ @map_proc = block
32
+ self
33
+ end
34
+
35
+ def unmap_with(&block)
36
+ @unmap_proc = block
37
+ self
38
+ end
39
+
40
+ def class
41
+ Array
42
+ end
43
+
44
+ def method_missing sym, *args, &blk
45
+ super if sym.to_s =~ /^__/
46
+ mname = "__#{::BlankSlate::MethodMapping[sym.to_s] || sym}"
47
+
48
+ case sym
49
+ when :push, :unshift, :<<, :[]=, :index, :-
50
+ last = args.pop
51
+ last = last.is_a?(Array) ? last.map{|v| @unmap_proc.call(v) } : @unmap_proc.call(last)
52
+ args.push last
53
+
54
+ __send__(mname, *args, &blk)
55
+
56
+ when :[], :slice, :at, :map, :shift, :pop, :include?, :last, :first, :zip, :each, :inject, :each_with_index
57
+ __map{|v| @map_proc.call(v) }.__send__(sym, *args, &blk)
58
+
59
+ else
60
+ __send__(mname, *args, &blk)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,46 @@
1
+ module StrokeDB
2
+ class LazyMappingHash < BlankSlate(Hash)
3
+ def initialize(original = {}, decoder = nil, encoder = nil)
4
+ @decoder = decoder || proc {|v| v}
5
+ @encoder = encoder || proc {|v| v}
6
+ super(default)
7
+ original.each {|k,v| self.__squarebracket_set(k,v) }
8
+ end
9
+
10
+ def map_with(&block)
11
+ @encoder = block
12
+ self
13
+ end
14
+
15
+ def unmap_with(&block)
16
+ @decoder = block
17
+ self
18
+ end
19
+
20
+ def class
21
+ Hash
22
+ end
23
+
24
+ def method_missing sym, *args, &blk
25
+ super if sym.to_s =~ /^__/
26
+ mname = "__#{::BlankSlate::MethodMapping[sym.to_s] || sym}"
27
+
28
+ case sym
29
+ when :keys, :values
30
+ __send__(mname, *args, &blk).map{|v| @encoder.call(v) }
31
+
32
+ when :each
33
+ self.__each do |k,v|
34
+ yield @encoder.call(k), @encoder.call(v)
35
+ end
36
+
37
+ when :[], :[]=
38
+ args.map!{|v| @decoder.call(v) }
39
+ @encoder.call __send__(mname, *args, &blk)
40
+
41
+ else
42
+ __send__(mname, *args, &blk)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ module StrokeDB
2
+ module JsonSerializationMethod
3
+ def serialize(x)
4
+ x.to_json
5
+ end
6
+ def deserialize(x)
7
+ JSON.parse(x)
8
+ end
9
+ end
10
+
11
+ module MarshalSerializationMethod
12
+ def serialize(x)
13
+ x = x.to_raw if x.respond_to?(:to_raw)
14
+ Marshal.dump(x)
15
+ end
16
+ def deserialize(x)
17
+ Marshal.load(x)
18
+ end
19
+ end
20
+
21
+
22
+ def self.serialization_method=(method_name)
23
+ StrokeDB.extend StrokeDB.const_get("#{method_name.to_s.camelize}SerializationMethod")
24
+ end
25
+
26
+ self.serialization_method = :marshal
27
+
28
+
29
+ end
@@ -0,0 +1,136 @@
1
+ module Enumerable
2
+ class TriggerPartitionContext
3
+ def initialize(enum, &block)
4
+ @enum = enum
5
+ @cont = block
6
+ end
7
+ def fill(&block)
8
+ @fill = block
9
+ self
10
+ end
11
+ def emit
12
+ partitions = []
13
+ cont = @cont
14
+ fill = @fill
15
+ p = @enum.inject(nil) do |part, elem|
16
+ if part && cont.call(part, elem)
17
+ fill.call(part, elem)
18
+ part
19
+ else
20
+ partitions << part if part
21
+ yield(elem)
22
+ end
23
+ end
24
+ partitions << p if p
25
+ partitions
26
+ end
27
+ end
28
+ def trigger_partition(&block)
29
+ TriggerPartitionContext.new(self, &block)
30
+ end
31
+
32
+ class TriggerPartitions
33
+ def self.partition(list)
34
+ partitions = []
35
+ p = list.inject(nil) do |part, elem|
36
+ if part && continue?(part, elem)
37
+ fill(part, elem)
38
+ part
39
+ else
40
+ partitions << part if part
41
+ emit(elem)
42
+ end
43
+ end
44
+ partitions << p if p
45
+ partitions
46
+ end
47
+ def self.continue?(p, e)
48
+ true
49
+ end
50
+ def self.emit(e)
51
+ [e]
52
+ end
53
+ def self.fill(p, e)
54
+ p << e
55
+ end
56
+ end
57
+ end
58
+
59
+ if __FILE__ == $0
60
+ arr = [1,2,3,4,5, -1, -4, -3, 5, 6, 7, 8, -6, -7]
61
+ parr = arr.trigger_partition do |partition, element|
62
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
63
+ end.fill do |p, e|
64
+ p << e
65
+ end.emit do |e|
66
+ [e]
67
+ end
68
+
69
+ p arr
70
+ p parr
71
+
72
+ # Class might be faster
73
+ class SignPartitions < Enumerable::TriggerPartitions
74
+ def self.continue?(partition, element)
75
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
76
+ end
77
+ end
78
+
79
+ p Enumerable::TriggerPartitions.partition(arr)
80
+ p SignPartitions.partition(arr)
81
+
82
+ require 'benchmark'
83
+ include Benchmark
84
+ n = 1000
85
+ bm(32) do |x|
86
+ x.report("#{n} times:" ) do
87
+ n.times do
88
+ arr.trigger_partition do |partition, element|
89
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
90
+ end.fill do |p, e|
91
+ p << e
92
+ end.emit do |e|
93
+ [e]
94
+ end
95
+ end
96
+ end
97
+ arrL = arr*28
98
+ x.report("#{n} times (x28 larger data):" ) do
99
+ n.times do
100
+ arrL.trigger_partition do |partition, element|
101
+ partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0
102
+ end.fill do |p, e|
103
+ p << e
104
+ end.emit do |e|
105
+ [e]
106
+ end
107
+ end
108
+ end
109
+ # 35% faster
110
+ x.report("#{n} times (SignPartitions):" ) do
111
+ (n/5).times do
112
+ SignPartitions.partition(arrL)
113
+ SignPartitions.partition(arrL)
114
+ SignPartitions.partition(arrL)
115
+ SignPartitions.partition(arrL)
116
+ SignPartitions.partition(arrL)
117
+ end
118
+ end
119
+ # + 17% faster (relative to SignPartitions)
120
+ x.report("#{n} times (raw code):" ) do
121
+ n.times do
122
+ parts = []
123
+ p = arrL.inject(nil) do |partition, element|
124
+ if partition && (partition[0] > 0 && element > 0 || partition[0] < 0 && element < 0)
125
+ partition << element
126
+ partition
127
+ else
128
+ parts << partition if partition
129
+ [element]
130
+ end
131
+ end
132
+ parts << p if p
133
+ end
134
+ end
135
+ end
136
+ end
data/lib/util/util.rb ADDED
@@ -0,0 +1,38 @@
1
+ module StrokeDB
2
+ module Util
3
+
4
+ class ::Object
5
+ # Uses references to documents (compared to to_raw using hashes instead)
6
+ def to_optimized_raw
7
+ case self
8
+ when Array
9
+ map{|v| v.to_optimized_raw }
10
+ when Hash
11
+ new_hash = {}
12
+ each_pair{|k,v| new_hash[k.to_optimized_raw] = v.to_optimized_raw}
13
+ new_hash
14
+ else
15
+ self
16
+ end
17
+ end
18
+ end
19
+
20
+ unless RUBY_PLATFORM =~ /java/
21
+ require 'uuidtools'
22
+ def self.random_uuid
23
+ ::UUID.random_create.to_s
24
+ end
25
+ end
26
+
27
+ class CircularReferenceCondition < Exception ; end
28
+ class << self
29
+ def catch_circular_reference(value,name = 'StrokeDB.reference_stack')
30
+ stack = Thread.current[name] ||= []
31
+ raise CircularReferenceCondition if stack.find{|v| value == v}
32
+ stack << value
33
+ yield
34
+ stack.pop
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/util/xml.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Object #:nodoc:
2
+ def to_xml(options = {})
3
+ xml = options[:builder]
4
+ xml.tag!(options[:root], options[:skip_types] ? {} : {:type => self.class.to_s.underscore},to_s)
5
+ end
6
+ end
data/lib/view/view.rb ADDED
@@ -0,0 +1,55 @@
1
+ module StrokeDB
2
+ View = Meta.new(:uuid => VIEW_UUID) do
3
+ attr_accessor :map_with_proc
4
+ attr_reader :reduce_with_proc
5
+
6
+ on_initialization do |view|
7
+ view.map_with_proc = proc {|doc, *args| doc }
8
+ end
9
+
10
+ def reduce_with(&block)
11
+ @reduce_with_proc = block
12
+ self
13
+ end
14
+
15
+ def map_with(&block)
16
+ @map_with_proc = block
17
+ self
18
+ end
19
+
20
+ def emit(*args)
21
+ ViewCut.new(store, :view => self, :args => args, :timestamp_state => LTS.zero.counter).emit
22
+ end
23
+
24
+ end
25
+ ViewCut = Meta.new(:uuid => VIEWCUT_UUID) do
26
+
27
+ on_new_document do |cut|
28
+ cut.instance_eval do
29
+ if view.is_a?(View)
30
+ @map_with_proc = view.map_with_proc
31
+ @reduce_with_proc = view.reduce_with_proc
32
+ end
33
+ end
34
+ end
35
+
36
+ before_save do |cut|
37
+ view = cut.view
38
+ view.last_cut = cut if view[:last_cut].nil? or (cut[:previous] && view.last_cut == cut.previous)
39
+ view.save!
40
+ end
41
+
42
+
43
+ def emit
44
+ mapped = []
45
+ store.each(:after_timestamp => timestamp_state, :include_versions => view[:include_versions]) do |doc|
46
+ mapped << @map_with_proc.call(doc,*args)
47
+ end
48
+ documents = (@reduce_with_proc ? mapped.select {|doc| @reduce_with_proc.call(doc,*args) } : mapped).map{|d| d.is_a?(Document) ? d.extend(VersionedDocument) : d}
49
+ ViewCut.new(store, :documents => documents, :view => view, :args => args, :timestamp_state => store.timestamp.counter, :previous => timestamp_state == 0 ? nil : self)
50
+ end
51
+ def to_a
52
+ documents
53
+ end
54
+ end
55
+ end