paulcarey-relaxdb 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -1,5 +1,9 @@
1
1
  h3. What's New?
2
2
 
3
+ * Semantic consistency for load, load!, save and save!. The bang versions raise an exception when their more relaxed siblings would simply return nil.
4
+
5
+ * Minimal support for CouchDB validation
6
+
3
7
  * Time storage changes. All Time objects are now converted to UTC and formatted as %Y/%m/%d %H:%M:%S +0000. Storing all Times as UTC should have been happening anyway. Formatting Times as above (as opposed to ISO 8601 as was done prior to 0.2.3) allows the Time strings to be passed directly to Date.new in a JavaScript interpreter.
4
8
 
5
9
  * Pagination! CouchDB offers great support for retrieving a subset of data, but the housekeeping is tricky. RelaxDB takes care of it.
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'spec/rake/spectask'
4
4
 
5
5
  PLUGIN = "relaxdb"
6
6
  NAME = "relaxdb"
7
- GEM_VERSION = "0.2.3"
7
+ GEM_VERSION = "0.2.4"
8
8
  AUTHOR = "Paul Carey"
9
9
  EMAIL = "paul.p.carey@gmail.com"
10
10
  HOMEPAGE = "http://github.com/paulcarey/relaxdb/"
data/lib/relaxdb.rb CHANGED
@@ -13,6 +13,8 @@ require 'tempfile'
13
13
  $:.unshift(File.dirname(__FILE__)) unless
14
14
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
15
15
 
16
+ require 'relaxdb/validators'
17
+
16
18
  require 'relaxdb/all_delegator'
17
19
  require 'relaxdb/belongs_to_proxy'
18
20
  require 'relaxdb/design_doc'
@@ -6,6 +6,7 @@ module RelaxDB
6
6
  # FooDoc.all.sorted_by(:att1, :att2) - returns all docs in CouchDB of type FooDoc sorted by att1, then att2
7
7
  # FooDoc.all.sorted_by(:att1) { |q| q.key("bar") } - returns all docs of type FooDoc where att1 equals "bar"
8
8
  # FooDoc.all.destroy! - does what it says on the tin
9
+ # FooDoc.all.size - issues a query to a reduce function that returns the total number of docs for that class
9
10
  #
10
11
  class AllDelegator < Delegator
11
12
 
@@ -15,10 +16,10 @@ module RelaxDB
15
16
  end
16
17
 
17
18
  def __getobj__
18
- view_path = "_view/#{@class_name}/all"
19
- map_function = ViewCreator.all(@class_name)
19
+ view_path = "_view/#{@class_name}/all?reduce=false"
20
+ map, reduce = ViewCreator.all(@class_name)
20
21
 
21
- @all = RelaxDB.retrieve(view_path, @class_name, "all", map_function)
22
+ RelaxDB.retrieve(view_path, @class_name, "all", map, reduce)
22
23
  end
23
24
 
24
25
  def sorted_by(*atts)
@@ -42,6 +43,25 @@ module RelaxDB
42
43
  o.destroy!
43
44
  end
44
45
  end
46
+
47
+ # This is pretty ugly - this pattern is now spread over three
48
+ # places (sorted_by_view, relaxdb and here)
49
+ # Consolidation needed
50
+ def size
51
+ view_path = "_view/#{@class_name}/all"
52
+ map, reduce = ViewCreator.all(@class_name)
53
+
54
+ begin
55
+ resp = RelaxDB.db.get(view_path)
56
+ rescue => e
57
+ DesignDocument.get(@class_name).add_map_view("all", map).
58
+ add_reduce_view("all", reduce).save
59
+ resp = RelaxDB.db.get(view_path)
60
+ end
61
+
62
+ data = JSON.parse(resp.body)
63
+ data["rows"][0] ? data["rows"][0]["value"] : 0
64
+ end
45
65
 
46
66
  end
47
67
 
@@ -15,6 +15,11 @@ module RelaxDB
15
15
  add_view(view_name, "reduce", function)
16
16
  end
17
17
 
18
+ def add_validation_func(function)
19
+ @data["validate_doc_update"] = function
20
+ self
21
+ end
22
+
18
23
  def add_view(view_name, type, function)
19
24
  @data["views"] ||= {}
20
25
  @data["views"][view_name] ||= {}
@@ -2,6 +2,8 @@ module RelaxDB
2
2
 
3
3
  class Document
4
4
 
5
+ include RelaxDB::Validators
6
+
5
7
  # Used to store validation messages
6
8
  attr_accessor :errors
7
9
 
@@ -29,11 +31,7 @@ module RelaxDB
29
31
  end
30
32
  end
31
33
 
32
- if opts[:validator]
33
- define_method("validate_#{prop}") do |prop_val|
34
- opts[:validator].call(prop_val)
35
- end
36
- end
34
+ create_validator(prop, opts[:validator]) if opts[:validator]
37
35
 
38
36
  if opts[:validation_msg]
39
37
  define_method("#{prop}_validation_msg") do
@@ -47,6 +45,18 @@ module RelaxDB
47
45
  # Ensure that classes that don't define their own properties still function as CouchDB objects
48
46
  @properties ||= [:_id, :_rev]
49
47
  end
48
+
49
+ def self.create_validator(att, v)
50
+ define_method("validate_#{att}") do |att_val|
51
+ if v.is_a? Proc
52
+ v.call(att_val)
53
+ elsif respond_to? "validator_#{v}"
54
+ send("validator_#{v}", att_val)
55
+ else
56
+ send(v, att_val)
57
+ end
58
+ end
59
+ end
50
60
 
51
61
  def properties
52
62
  self.class.properties
@@ -100,7 +110,7 @@ module RelaxDB
100
110
  prop_val = instance_variable_get("@#{prop}".to_sym)
101
111
  s << ", #{prop}: #{prop_val.inspect}" if prop_val
102
112
  end
103
- self.class.belongs_to_rels.each do |relationship|
113
+ self.class.belongs_to_rels.each do |relationship, opts|
104
114
  id = instance_variable_get("@#{relationship}_id".to_sym)
105
115
  s << ", #{relationship}_id: #{id}" if id
106
116
  end
@@ -142,7 +152,7 @@ module RelaxDB
142
152
  return false unless validates?
143
153
  return false unless before_save
144
154
 
145
- set_created_at if unsaved?
155
+ set_created_at if new_document?
146
156
 
147
157
  resp = RelaxDB.db.put("#{_id}", to_json)
148
158
  self._rev = JSON.parse(resp.body)["rev"]
@@ -152,6 +162,10 @@ module RelaxDB
152
162
  self
153
163
  end
154
164
 
165
+ def save!
166
+ save || raise(DocumentNotSaved)
167
+ end
168
+
155
169
  def validates?
156
170
  total_success = true
157
171
  properties.each do |prop|
@@ -166,20 +180,24 @@ module RelaxDB
166
180
  total_success &= success
167
181
  end
168
182
  end
169
- total_success &= validate
183
+
184
+ # Unsure whether to pass the id or the doc itself - id is all I need right now
185
+ self.class.belongs_to_rels.each do |rel, opts|
186
+ if methods.include? "validate_#{rel}"
187
+ rel_val = instance_variable_get("@#{rel}_id")
188
+ success = send("validate_#{rel}", rel_val) rescue false
189
+ total_success &= success
190
+ end
191
+ end
192
+
170
193
  total_success
171
194
  end
172
-
173
- def validate
174
- true
175
- end
176
-
177
- # Hmm. Rename... never_saved? newnew?
178
- def unsaved?
195
+
196
+ def new_document?
179
197
  @_rev.nil?
180
198
  end
181
- alias_method :new_record?, :unsaved?
182
- alias_method :new_document?, :unsaved?
199
+ alias_method :new_record?, :new_document?
200
+ alias_method :unsaved?, :new_document?
183
201
 
184
202
  def to_param
185
203
  self._id
@@ -198,8 +216,8 @@ module RelaxDB
198
216
  proxy = instance_variable_get(proxy_sym)
199
217
  unless proxy
200
218
  proxy = opts ? klass.new(self, relationship, opts) : klass.new(self, relationship)
219
+ instance_variable_set(proxy_sym, proxy)
201
220
  end
202
- instance_variable_set(proxy_sym, proxy)
203
221
  proxy
204
222
  end
205
223
 
@@ -208,8 +226,8 @@ module RelaxDB
208
226
  other && _id == other._id
209
227
  end
210
228
 
211
- # Deprecated. This method was experimental and will be removed
212
- # once multi key GETs are available in CouchDB.
229
+ # If you're using this method, read the specs and make sure you understand
230
+ # how it can be used and how it shouldn't be used
213
231
  def self.references_many(relationship, opts={})
214
232
  # Treat the representation as a standard property
215
233
  properties << relationship
@@ -293,6 +311,7 @@ module RelaxDB
293
311
  instance_variable_get("@#{relationship}_id")
294
312
  end
295
313
 
314
+ create_validator(relationship, opts[:validator]) if opts[:validator]
296
315
  end
297
316
 
298
317
  def self.belongs_to_rels
@@ -15,7 +15,7 @@ module RelaxDB
15
15
 
16
16
  def total_doc_count(design_doc, view_name)
17
17
  result = RelaxDB.view(design_doc, view_name) do |q|
18
- q.group(true).group_level(0).reduce(true)
18
+ q.reduce(true)
19
19
  q.startkey(@orig_paginate_params.startkey).endkey(@orig_paginate_params.endkey).descending(@orig_paginate_params.descending)
20
20
  end
21
21
 
data/lib/relaxdb/query.rb CHANGED
@@ -42,6 +42,7 @@ module RelaxDB
42
42
  @keys
43
43
  else
44
44
  @keys = { :keys => keys }.to_json
45
+ self
45
46
  end
46
47
  end
47
48
 
@@ -78,12 +78,14 @@ module RelaxDB
78
78
  def inspect
79
79
  @client.instance_variable_get("@#{@relationship}".to_sym).inspect
80
80
  end
81
-
82
- private
83
-
81
+
84
82
  def peer_ids
85
83
  @client.instance_variable_get("@#{@relationship}".to_sym)
86
84
  end
85
+
86
+ alias to_id_a peer_ids
87
+
88
+ private
87
89
 
88
90
  # Resolves the actual ids into real objects via a single GET to CouchDB. Called internally by each
89
91
  def resolve
@@ -1,4 +1,7 @@
1
1
  module RelaxDB
2
+
3
+ class NotFound < StandardError; end
4
+ class DocumentNotSaved < StandardError; end
2
5
 
3
6
  @@db = nil
4
7
 
@@ -49,22 +52,37 @@ module RelaxDB
49
52
 
50
53
  def load(*ids)
51
54
  if ids.size == 1
52
- resp = db.get(ids[0])
53
- data = JSON.parse(resp.body)
54
- create_object(data)
55
+ begin
56
+ resp = db.get(ids[0])
57
+ data = JSON.parse(resp.body)
58
+ create_object(data)
59
+ rescue HTTP_404
60
+ nil
61
+ end
55
62
  else
56
63
  resp = db.post("_all_docs?include_docs=true", {:keys => ids}.to_json)
57
64
  data = JSON.parse(resp.body)
58
- data["rows"].map { |row| create_object(row["doc"]) }
65
+ data["rows"].map { |row| row["doc"] ? create_object(row["doc"]) : nil }
59
66
  end
60
67
  end
61
68
 
69
+ def load!(*ids)
70
+ res = load(*ids)
71
+
72
+ raise NotFound if res == nil
73
+ raise NotFound if res.respond_to?(:include?) && res.include?(nil)
74
+
75
+ res
76
+ end
77
+
62
78
  # Used internally by RelaxDB
63
- def retrieve(view_path, design_doc=nil, view_name=nil, map_function=nil)
79
+ def retrieve(view_path, design_doc=nil, view_name=nil, map_func=nil, reduce_func=nil)
64
80
  begin
65
81
  resp = db.get(view_path)
66
82
  rescue => e
67
- DesignDocument.get(design_doc).add_map_view(view_name, map_function).save
83
+ dd = DesignDocument.get(design_doc).add_map_view(view_name, map_func)
84
+ dd.add_reduce_view(view_name, reduce_func) if reduce_func
85
+ dd.save
68
86
  resp = db.get(view_path)
69
87
  end
70
88
 
@@ -1,4 +1,7 @@
1
1
  module RelaxDB
2
+
3
+ class HTTP_404 < StandardError; end
4
+ class HTTP_409 < StandardError; end
2
5
 
3
6
  class Server
4
7
 
@@ -46,7 +49,14 @@ module RelaxDB
46
49
  private
47
50
 
48
51
  def handle_error(req, res)
49
- e = RuntimeError.new("#{res.code}:#{res.message}\nMETHOD:#{req.method}\nURI:#{req.path}\n#{res.body}")
52
+ msg = "#{res.code}:#{res.message}\nMETHOD:#{req.method}\nURI:#{req.path}\n#{res.body}"
53
+ begin
54
+ klass = RelaxDB.const_get("HTTP_#{res.code}")
55
+ e = klass.new(msg)
56
+ rescue
57
+ e = RuntimeError.new(msg)
58
+ end
59
+
50
60
  raise e
51
61
  end
52
62
  end
@@ -25,7 +25,11 @@ module RelaxDB
25
25
  def reduce_function
26
26
  <<-QUERY
27
27
  function(keys, values, rereduce) {
28
- return values.length;
28
+ if (rereduce) {
29
+ return sum(values);
30
+ } else {
31
+ return values.length;
32
+ }
29
33
  }
30
34
  QUERY
31
35
  end
@@ -42,8 +46,7 @@ module RelaxDB
42
46
  # to false if it hasn't already been set.
43
47
  query.reduce(false) if query.reduce.nil?
44
48
 
45
- # I hope the query.group(true) should be temporary only (given that reduce has been set to false)
46
- method = query.keys ? (query.group(true) && :post) : :get
49
+ method = query.keys ? :post : :get
47
50
 
48
51
  begin
49
52
  resp = RelaxDB.db.send(method, query.view_path, query.keys)
data/lib/relaxdb/views.rb CHANGED
@@ -3,13 +3,25 @@ module RelaxDB
3
3
  class ViewCreator
4
4
 
5
5
  def self.all(target_class)
6
- template = <<-QUERY
6
+ map = <<-QUERY
7
7
  function(doc) {
8
8
  if(doc.class == "${target_class}")
9
9
  emit(null, doc);
10
10
  }
11
11
  QUERY
12
- template.sub!("${target_class}", target_class.to_s)
12
+ map.sub!("${target_class}", target_class.to_s)
13
+
14
+ reduce = <<-QUERY
15
+ function(keys, values, rereduce) {
16
+ if (rereduce) {
17
+ return sum(values);
18
+ } else {
19
+ return values.length;
20
+ }
21
+ }
22
+ QUERY
23
+
24
+ [map, reduce]
13
25
  end
14
26
 
15
27
  def self.has_n(target_class, relationship_to_client)
@@ -74,6 +74,13 @@ describe RelaxDB::BelongsToProxy do
74
74
  r = RelaxDB.load r._id
75
75
  r.photo.rating.should == r
76
76
  end
77
+
78
+ it "should function with the required validator" do
79
+ c = Class.new(RelaxDB::Document) do
80
+ belongs_to :foo, :validator => :required
81
+ end
82
+ c.new.save.should be_false
83
+ end
77
84
 
78
85
  end
79
86
 
@@ -26,7 +26,7 @@ describe RelaxDB::DesignDocument do
26
26
  it "should delete the corresponding document from CouchDB" do
27
27
  dd = RelaxDB::DesignDocument.get("foo").save
28
28
  dd.destroy!
29
- lambda { RelaxDB.load("_design%2Ffoo") }.should raise_error
29
+ RelaxDB.load("_design%2Ffoo").should be_nil
30
30
  end
31
31
 
32
32
  end
@@ -37,6 +37,17 @@ describe RelaxDB::Document do
37
37
  end
38
38
 
39
39
  end
40
+
41
+ describe "#initialize" do
42
+
43
+ it "may be overridden by inheriting classes" do
44
+ i = Initiative.new(:x => "y").save
45
+ i = RelaxDB.load(i._id)
46
+ i.x.should == "y"
47
+ i.foo.should == :bar
48
+ end
49
+
50
+ end
40
51
 
41
52
  describe "#to_json" do
42
53
 
@@ -84,6 +95,24 @@ describe RelaxDB::Document do
84
95
 
85
96
  end
86
97
 
98
+ describe "#save!" do
99
+
100
+ it "should save objects" do
101
+ a = Atom.new.save
102
+ RelaxDB.load(a._id).should == a
103
+ end
104
+
105
+ it "should throw an exception when an object is not saved" do
106
+ r = Class.new(RelaxDB::Document) do
107
+ property :thumbs_up, :validator => lambda { false }
108
+ end
109
+ lambda do
110
+ r.new.save!
111
+ end.should raise_error(RelaxDB::DocumentNotSaved)
112
+ end
113
+
114
+ end
115
+
87
116
  describe "user defined property reader" do
88
117
 
89
118
  it "should not effect normal operation" do
@@ -119,7 +148,8 @@ describe RelaxDB::Document do
119
148
  p.str.should == "foo"
120
149
  p.num.should == 19.30
121
150
  p.true_bool.should be_true
122
- p.false_bool.should_not be_true
151
+ # p.false_bool.should be_false
152
+ p.false_bool.should_not be
123
153
  p.created_at.should be_close(now, 1)
124
154
  p.empty.should be_nil
125
155
  end
@@ -136,7 +166,7 @@ describe RelaxDB::Document do
136
166
 
137
167
  it "should delete the corresponding document from CouchDB" do
138
168
  p = Atom.new.save.destroy!
139
- lambda { RelaxDB.load(p._id) }.should raise_error
169
+ RelaxDB.load(p._id).should be_nil
140
170
  end
141
171
 
142
172
  it "should prevent the object from being resaved" do
@@ -195,6 +225,21 @@ describe RelaxDB::Document do
195
225
 
196
226
  end
197
227
 
228
+ describe ".all.size" do
229
+
230
+ it "should return the total number of docs" do
231
+ docs = []
232
+ 100.times { docs << Atom.new }
233
+ RelaxDB.bulk_save(*docs)
234
+ Atom.all.size.should == 100
235
+ end
236
+
237
+ it "should return 0 when no docs exist" do
238
+ Atom.all.size.should == 0
239
+ end
240
+
241
+ end
242
+
198
243
  describe ".all.sorted_by" do
199
244
 
200
245
  it "should sort ascending by default" do
@@ -222,6 +267,17 @@ describe RelaxDB::Document do
222
267
  posts[1].content.should == "late"
223
268
  end
224
269
 
270
+ it "should return the count when queried with reduce=true" do
271
+ docs = []
272
+ 100.times { |i| docs << Primitives.new(:num => i) }
273
+ RelaxDB.bulk_save(*docs)
274
+ # Create the view
275
+ Primitives.all.sorted_by(:num)
276
+ res = RelaxDB.view("Primitives", "all_sorted_by_num") { |q| q.reduce(true) }
277
+ count = RelaxDB.reduce_result(res)
278
+ count.should == 100
279
+ end
280
+
225
281
  describe "results" do
226
282
 
227
283
  it "should be retrievable by exact criteria" do
@@ -307,7 +363,7 @@ describe RelaxDB::Document do
307
363
 
308
364
  it "should prevent an object from being saved if it throws an exception" do
309
365
  r = Class.new(RelaxDB::Document) do
310
- property :thumbs_up, :validator => lambda { raise "foo" }
366
+ property :thumbs_up, :validator => lambda { raise }
311
367
  end
312
368
  r.new.save.should be_false
313
369
  end
@@ -331,8 +387,8 @@ describe RelaxDB::Document do
331
387
 
332
388
  it "should perform all validations" do
333
389
  r = Class.new(RelaxDB::Document) do
334
- property :foo, :validator => lambda { false }, :validation_msg => "oof"
335
- property :bar, :validator => lambda { false }, :validation_msg => "rab"
390
+ property :foo, :validator => lambda { raise }, :validation_msg => "oof"
391
+ property :bar, :validator => lambda { raise }, :validation_msg => "rab"
336
392
  end
337
393
  x = r.new
338
394
  x.save
@@ -359,13 +415,32 @@ describe RelaxDB::Document do
359
415
  it "may be a method" do
360
416
  r = Class.new(RelaxDB::Document) do
361
417
  property :thumbs_up, :validator => :count_em
362
- def count_em
363
- false
418
+ def count_em(tu)
419
+ tu >=0 && tu < 3
364
420
  end
365
421
  end
366
- r.new.save.should be_false
422
+ r.new(:thumbs_up => 1).save.should be
367
423
  end
368
424
 
369
425
  end
370
426
 
427
+ describe "predefined validator" do
428
+
429
+ it "should be invoked when a symbol clash exists" do
430
+ c = Class.new(RelaxDB::Document) do
431
+ property :foo, :validator => :required
432
+ def required; raise; end;
433
+ end
434
+ c.new(:foo => :bar).save.should be
435
+ end
436
+
437
+ it "should prevent an object from being saved if validation fails" do
438
+ c = Class.new(RelaxDB::Document) do
439
+ property :foo, :validator => :required
440
+ end
441
+ c.new.save.should be_false
442
+ end
443
+
444
+ end
445
+
371
446
  end
@@ -13,10 +13,7 @@ describe RelaxDB::ReferencesManyProxy do
13
13
  end
14
14
 
15
15
  describe "references_many" do
16
-
17
- it "is now deprecated and will be removed in the near future" do
18
- end
19
-
16
+
20
17
  it "should preserve the relationships across the save / load boundary" do
21
18
  p = Photo.new
22
19
  t = Tag.new
data/spec/relaxdb_spec.rb CHANGED
@@ -73,6 +73,46 @@ describe RelaxDB do
73
73
  ar2.should == a2
74
74
  end
75
75
 
76
+ it "should return nil when given a id for a non existant doc" do
77
+ RelaxDB.load("nothere").should be_nil
78
+ end
79
+
80
+ it "should return an array with correctly placed nils when given a list containing non existant doc ids" do
81
+ a1, a2 = Atom.new.save, Atom.new.save
82
+ res = RelaxDB.load nil, a1._id, nil, a2._id, nil
83
+ res.should == [nil, a1, nil, a2, nil]
84
+ end
85
+
86
+ end
87
+
88
+ describe ".load!" do
89
+
90
+ it "should load a single document" do
91
+ a = Atom.new.save
92
+ ar = RelaxDB.load! a._id
93
+ ar.should == a
94
+ end
95
+
96
+ it "should load multiple documents" do
97
+ a1, a2 = Atom.new.save, Atom.new.save
98
+ ar1, ar2 = RelaxDB.load! a1._id, a2._id
99
+ ar1.should == a1
100
+ ar2.should == a2
101
+ end
102
+
103
+ it "should throw an exception if given a single id for a non-existant doc" do
104
+ lambda do
105
+ RelaxDB.load! "nothere"
106
+ end.should raise_error(RelaxDB::NotFound)
107
+ end
108
+
109
+ it "should throw an exception if any of a list of doc ids is for a non-existant doc" do
110
+ a = Atom.new.save
111
+ lambda do
112
+ RelaxDB.load! nil, a._id
113
+ end.should raise_error(RelaxDB::NotFound)
114
+ end
115
+
76
116
  end
77
117
 
78
118
  describe ".view" do
data/spec/spec_models.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  class Atom < RelaxDB::Document
2
2
  end
3
3
 
4
+ class Initiative < RelaxDB::Document
5
+ property :x
6
+ attr_reader :foo
7
+ def initialize(hash={})
8
+ super
9
+ @foo = :bar
10
+ end
11
+ end
12
+
4
13
  class Primitives < RelaxDB::Document
5
14
 
6
15
  property :str
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paulcarey-relaxdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Carey
@@ -9,7 +9,7 @@ autorequire: relaxdb
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-25 00:00:00 -07:00
12
+ date: 2008-12-26 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -68,6 +68,7 @@ files:
68
68
  - lib/relaxdb/server.rb
69
69
  - lib/relaxdb/sorted_by_view.rb
70
70
  - lib/relaxdb/uuid_generator.rb
71
+ - lib/relaxdb/validators
71
72
  - lib/relaxdb/view_object.rb
72
73
  - lib/relaxdb/view_result.rb
73
74
  - lib/relaxdb/view_uploader.rb