ceritium-relaxdb 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +185 -0
  3. data/Rakefile +58 -0
  4. data/docs/spec_results.html +604 -0
  5. data/lib/more/atomic_bulk_save_support.rb +18 -0
  6. data/lib/more/grapher.rb +48 -0
  7. data/lib/relaxdb.rb +40 -0
  8. data/lib/relaxdb/all_delegator.rb +68 -0
  9. data/lib/relaxdb/belongs_to_proxy.rb +29 -0
  10. data/lib/relaxdb/design_doc.rb +55 -0
  11. data/lib/relaxdb/document.rb +531 -0
  12. data/lib/relaxdb/extlib.rb +15 -0
  13. data/lib/relaxdb/has_many_proxy.rb +104 -0
  14. data/lib/relaxdb/has_one_proxy.rb +45 -0
  15. data/lib/relaxdb/paginate_params.rb +54 -0
  16. data/lib/relaxdb/paginator.rb +78 -0
  17. data/lib/relaxdb/query.rb +75 -0
  18. data/lib/relaxdb/references_many_proxy.rb +101 -0
  19. data/lib/relaxdb/relaxdb.rb +208 -0
  20. data/lib/relaxdb/server.rb +156 -0
  21. data/lib/relaxdb/sorted_by_view.rb +65 -0
  22. data/lib/relaxdb/uuid_generator.rb +21 -0
  23. data/lib/relaxdb/validators.rb +11 -0
  24. data/lib/relaxdb/view_object.rb +34 -0
  25. data/lib/relaxdb/view_result.rb +18 -0
  26. data/lib/relaxdb/view_uploader.rb +47 -0
  27. data/lib/relaxdb/views.rb +54 -0
  28. data/spec/belongs_to_spec.rb +129 -0
  29. data/spec/callbacks_spec.rb +80 -0
  30. data/spec/derived_properties_spec.rb +117 -0
  31. data/spec/design_doc_spec.rb +34 -0
  32. data/spec/document_spec.rb +556 -0
  33. data/spec/has_many_spec.rb +176 -0
  34. data/spec/has_one_spec.rb +128 -0
  35. data/spec/query_spec.rb +80 -0
  36. data/spec/references_many_spec.rb +178 -0
  37. data/spec/relaxdb_spec.rb +226 -0
  38. data/spec/spec.opts +1 -0
  39. data/spec/spec_helper.rb +10 -0
  40. data/spec/spec_models.rb +151 -0
  41. data/spec/view_object_spec.rb +47 -0
  42. metadata +123 -0
@@ -0,0 +1,11 @@
1
+ module RelaxDB
2
+
3
+ module Validators
4
+
5
+ def validator_required(att, o)
6
+ !att.blank?
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,34 @@
1
+ module RelaxDB
2
+
3
+ # An immuntable object typically used to display the results of a view
4
+ class ViewObject
5
+
6
+ def initialize(hash)
7
+ hash.each do |k, v|
8
+
9
+ if k.to_s =~ /_at$/
10
+ v = Time.local(*ParseDate.parsedate(v)) rescue v
11
+ end
12
+
13
+ instance_variable_set("@#{k}", v)
14
+ meta_class.instance_eval do
15
+ define_method(k.to_sym) do
16
+ instance_variable_get("@#{k}".to_sym)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.create(obj)
23
+ if obj.instance_of? Array
24
+ obj.inject([]) { |arr, o| arr << ViewObject.new(o) }
25
+ elsif obj.instance_of? Hash
26
+ ViewObject.new(obj)
27
+ else
28
+ obj
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,18 @@
1
+ module RelaxDB
2
+
3
+ class ViewResult < DelegateClass(Array)
4
+
5
+ attr_reader :offset, :total_rows
6
+
7
+ def initialize(result_hash)
8
+ objs = RelaxDB.create_from_hash(result_hash)
9
+
10
+ @offset = result_hash["offset"]
11
+ @total_rows = result_hash["total_rows"]
12
+
13
+ super(objs)
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,47 @@
1
+ module RelaxDB
2
+
3
+ class ViewUploader
4
+
5
+ class << self
6
+
7
+ # Methods must start and finish on different lines
8
+ # The function declaration must start at the beginning of a line
9
+ # As '-' is used as a delimiter, neither design doc nor view name may contain '-'
10
+ # Exepcted function declaration form is
11
+ # function DesignDoc-funcname-functype(doc) {
12
+ # For example
13
+ # function Users-followers-map(doc) {
14
+ #
15
+ def upload(filename)
16
+ lines = File.readlines(filename)
17
+ extract(lines) do |dd, vn, t, f|
18
+ RelaxDB::DesignDocument.get(dd).add_view(vn, t, f).save
19
+ end
20
+ end
21
+
22
+ def extract(lines)
23
+ # Index of function declaration matches
24
+ m = []
25
+
26
+ 0.upto(lines.size-1) do |p|
27
+ line = lines[p]
28
+ m << p if line =~ /^function[^\{]+\{/
29
+ end
30
+ # Add one beyond the last line number as the final terminator
31
+ m << lines.size
32
+
33
+ 0.upto(m.size-2) do |i|
34
+ declr = lines[m[i]]
35
+ declr =~ /(\w)+-(\w)+-(\w)+/
36
+ declr.sub!($&, '')
37
+ design_doc, view_name, type = $&.split('-')
38
+ func = lines[m[i]...m[i+1]].join
39
+ yield design_doc, view_name, type, func
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,54 @@
1
+ module RelaxDB
2
+
3
+ class ViewCreator
4
+
5
+ def self.all(target_class)
6
+ map = <<-QUERY
7
+ function(doc) {
8
+ if(doc.class == "${target_class}")
9
+ emit(null, doc);
10
+ }
11
+ QUERY
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]
25
+ end
26
+
27
+ def self.has_n(target_class, relationship_to_client)
28
+ template = <<-MAP_FUNC
29
+ function(doc) {
30
+ if(doc.class == "${target_class}" && doc.${relationship_to_client}_id)
31
+ emit(doc.${relationship_to_client}_id, doc);
32
+ }
33
+ MAP_FUNC
34
+ template.sub!("${target_class}", target_class)
35
+ template.gsub("${relationship_to_client}", relationship_to_client)
36
+ end
37
+
38
+ def self.has_many_through(target_class, peers)
39
+ template = <<-MAP_FUNC
40
+ function(doc) {
41
+ if(doc.class == "${target_class}" && doc.${peers}) {
42
+ var i;
43
+ for(i = 0; i < doc.${peers}.length; i++) {
44
+ emit(doc.${peers}[i], doc);
45
+ }
46
+ }
47
+ }
48
+ MAP_FUNC
49
+ template.sub!("${target_class}", target_class).gsub!("${peers}", peers)
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,129 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::BelongsToProxy do
5
+
6
+ before(:all) do
7
+ RelaxDB.configure(:host => "localhost", :port => 5984)
8
+ end
9
+
10
+ before(:each) do
11
+ RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
12
+ RelaxDB.use_db "relaxdb_spec_db"
13
+ end
14
+
15
+ describe "belongs_to" do
16
+
17
+ it "should return nil when accessed before assignment" do
18
+ r = Rating.new
19
+ r.photo.should == nil
20
+ end
21
+
22
+ it "should be establishable via constructor attribute" do
23
+ p = Photo.new
24
+ r = Rating.new :photo => p
25
+ r.photo.should == p
26
+ end
27
+
28
+ it "should be establishable via constructor id" do
29
+ p = Photo.new.save
30
+ r = Rating.new(:photo_id => p._id).save
31
+ r.photo.should == p
32
+ end
33
+
34
+ it "should establish the parent relationship when supplied a parent and saved" do
35
+ p = Photo.new.save
36
+ r = Rating.new
37
+ r.photo = p
38
+ # I'm not saying the following is correct or desired - merely codifying how things stand
39
+ p.rating.should be_nil
40
+ r.save
41
+ p.rating.should == r
42
+ end
43
+
44
+ it "should establish the parent relationship when supplied a parent id and saved" do
45
+ p = Photo.new.save
46
+ r = Rating.new(:photo_id => p._id).save
47
+ p.rating.should == r
48
+ end
49
+
50
+ it "should return the same object on repeated invocations" do
51
+ p = Photo.new.save
52
+ r = Rating.new(:photo => p).save
53
+ r = RelaxDB.load(r._id)
54
+ r.photo.object_id.should == r.photo.object_id
55
+ end
56
+
57
+ it "should be nullified when the parent is destroyed" do
58
+ r = Rating.new
59
+ p = Photo.new(:rating => r).save
60
+ p.destroy!
61
+ RelaxDB.load(r._id).photo.should be_nil
62
+ end
63
+
64
+ it "should be preserved across save / load boundary" do
65
+ r = Rating.new
66
+ p = Photo.new(:rating => r).save
67
+ r = RelaxDB.load r._id
68
+ r.photo.should == p
69
+ end
70
+
71
+ it "should be able to reference itself via its parent" do
72
+ r = Rating.new
73
+ p = Photo.new(:rating => r).save
74
+ r = RelaxDB.load r._id
75
+ r.photo.rating.should == r
76
+ end
77
+
78
+ it "may be used reciprocally" do
79
+ C1 = Class.new(RelaxDB::Document) do
80
+ belongs_to :c2
81
+ end
82
+ C2 = Class.new(RelaxDB::Document) do
83
+ belongs_to :c1
84
+ end
85
+ i1, i2 = C1.new, C2.new
86
+
87
+ i1.c2 = i2
88
+ i1.save!
89
+ i2.c1 = i1
90
+ i2.save!
91
+
92
+ i1 = RelaxDB.load i1._id
93
+ i1.c2.should == i2
94
+
95
+ i2 = RelaxDB.load i2._id
96
+ i2.c1.should == i1
97
+ end
98
+
99
+ describe "validator" do
100
+
101
+ it "should be passed the _id and object" do
102
+ a = Atom.new(:_id => "atom").save!
103
+ c = Class.new(RelaxDB::Document) do
104
+ belongs_to :foo, :validator => lambda { |foo_id, obj| foo_id.reverse == obj._id }
105
+ end
106
+ c.new(:_id => "mota", :foo => a).save!
107
+ end
108
+
109
+ it "may be used with a predefined validator" do
110
+ c = Class.new(RelaxDB::Document) do
111
+ belongs_to :foo, :validator => :required
112
+ end
113
+ c.new.save.should be_false
114
+ end
115
+
116
+ it "should be provided with a default error message when validation fails" do
117
+ c = Class.new(RelaxDB::Document) do
118
+ belongs_to :foo, :validator => :required
119
+ end
120
+ x = c.new
121
+ x.save
122
+ x.errors[:foo].should_not be_blank
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+
129
+ end
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ # A little naiive, needs quite a bit more thought and work
4
+
5
+ describe RelaxDB::Document, "callbacks" do
6
+
7
+ before(:all) do
8
+ RelaxDB.configure(:host => "localhost", :port => 5984)
9
+ end
10
+
11
+ before(:each) do
12
+ RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
13
+ RelaxDB.use_db "relaxdb_spec_db"
14
+ end
15
+
16
+ describe "before_save" do
17
+
18
+ it "should be run before the object is saved" do
19
+ c = Class.new(RelaxDB::Document) do
20
+ before_save lambda { |s| s.gem += 1 if s.unsaved? }
21
+ property :gem
22
+ end
23
+ p = c.new(:gem => 5).save
24
+ p.gem.should == 6
25
+ end
26
+
27
+ it "should prevent the object from being saved if it returns false" do
28
+ c = Class.new(RelaxDB::Document) do
29
+ before_save lambda { false }
30
+ end
31
+ c.new.save.should == false
32
+ end
33
+
34
+ it "should add a description to errors when false is returned" do
35
+ c = Class.new(RelaxDB::Document) do
36
+ before_save lambda { false }
37
+ end
38
+ x = c.new
39
+ x.save
40
+ x.errors[:before_save].should be
41
+ end
42
+
43
+ it "should not prevent the object from being saved if it returns nil" do
44
+ c = Class.new(RelaxDB::Document) do
45
+ before_save lambda { nil }
46
+ end
47
+ c.new.save!
48
+ end
49
+
50
+ it "may be a proc" do
51
+ c = Class.new(RelaxDB::Document) do
52
+ before_save lambda { false }
53
+ end
54
+ c.new.save.should == false
55
+ end
56
+
57
+ it "may be a method" do
58
+ c = Class.new(RelaxDB::Document) do
59
+ before_save :never
60
+ def never; false; end
61
+ end
62
+ c.new.save.should == false
63
+ end
64
+
65
+ end
66
+
67
+ describe "after_save" do
68
+
69
+ it "should be run after the object is saved" do
70
+ c = Class.new(RelaxDB::Document) do
71
+ after_save lambda { |s| s.gem +=1 unless s.unsaved? }
72
+ property :gem
73
+ end
74
+ p = c.new(:gem => 5).save
75
+ p.gem.should == 6
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,117 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ class DpInvite < RelaxDB::Document
5
+ property :event_name, :derived => [:event, lambda { |en, i| i.event.name }]
6
+ belongs_to :event
7
+ end
8
+
9
+ class DpEvent < RelaxDB::Document
10
+ property :name
11
+ end
12
+
13
+ describe RelaxDB::Document, "derived properties" do
14
+
15
+ before(:all) do
16
+ RelaxDB.configure(:host => "localhost", :port => 5984)
17
+ end
18
+
19
+ before(:each) do
20
+ RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
21
+ RelaxDB.use_db "relaxdb_spec_db"
22
+ end
23
+
24
+ it "should have its value updated when the source is updated" do
25
+ e = DpEvent.new(:name => "shindig")
26
+ i = DpInvite.new(:event => e)
27
+ i.event_name.should == "shindig"
28
+ end
29
+
30
+ it "should have its value persisted" do
31
+ e = DpEvent.new(:name => "shindig").save!
32
+ i = DpInvite.new(:event => e).save!
33
+
34
+ RelaxDB.db.get_count = 0
35
+ i = RelaxDB.load i._id
36
+ i.event_name.should == "shindig"
37
+ RelaxDB.db.get_count.should == 1
38
+ end
39
+
40
+ it "should have its value updated when the source_id is updated for a saved event" do
41
+ e = DpEvent.new(:name => "shindig").save!
42
+ i = DpInvite.new(:event_id => e._id)
43
+ i.event_name.should == "shindig"
44
+ end
45
+
46
+ it "will not raise an exception when the source is nil" do
47
+ # See the rationale in Document.write_derived_props
48
+ DpInvite.new(:event => nil).save!
49
+ end
50
+
51
+ it "should only be updated for registered properties" do
52
+ invite = Class.new(RelaxDB::Document) do
53
+ property :event_name, :derived => [:foo, lambda { |en, i| i.event.name }]
54
+ belongs_to :event
55
+ end
56
+
57
+ event = Class.new(RelaxDB::Document) do
58
+ property :name
59
+ end
60
+
61
+ e = event.new(:name => "shindig")
62
+ i = invite.new(:event => e)
63
+ i.event_name.should be_nil
64
+ end
65
+
66
+ it "should have the existing value passed to the first lambda param" do
67
+ invite = Class.new(RelaxDB::Document) do
68
+ property :event_name, :derived => [:event, lambda { |en, i| en.nil? ? i.event.name : "bar" }]
69
+ belongs_to :event
70
+ end
71
+
72
+ event = Class.new(RelaxDB::Document) do
73
+ property :name
74
+ end
75
+
76
+ e1 = event.new(:name => "shindig")
77
+ e2 = event.new(:name => "shindig2")
78
+ i = invite.new(:event => e1)
79
+ i.event = e2
80
+ i.event_name.should == "bar"
81
+ end
82
+
83
+ it "should contintue to be derived post load" do
84
+ e = DpEvent.new(:name => "shindig").save!
85
+ i = DpInvite.new(:event => e).save!
86
+
87
+ i = RelaxDB.load i._id
88
+ i.event_name.should == "shindig"
89
+
90
+ e = DpEvent.new(:name => "gidnihs").save!
91
+ i.event = e
92
+ i.event_name.should == "gidnihs"
93
+ end
94
+
95
+ describe "multiple properties" do
96
+
97
+ it "should be derivable from the same source" do
98
+ invite = Class.new(RelaxDB::Document) do
99
+ property :name, :derived => [:event, lambda { |en, i| i.event.name }]
100
+ property :location, :derived => [:event, lambda { |en, i| i.event.location }]
101
+ belongs_to :event
102
+ end
103
+
104
+ event = Class.new(RelaxDB::Document) do
105
+ property :name
106
+ property :location
107
+ end
108
+
109
+ e = event.new(:name => "shindig", :location => "city17")
110
+ i = invite.new(:event => e)
111
+ i.name.should == "shindig"
112
+ i.location.should == "city17"
113
+ end
114
+
115
+ end
116
+
117
+ end