relaxdb 0.3.5 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README.textile +21 -23
  2. data/Rakefile +2 -7
  3. data/docs/spec_results.html +5 -5
  4. data/lib/more/grapher.rb +1 -1
  5. data/lib/relaxdb.rb +3 -5
  6. data/lib/relaxdb/all_delegator.rb +19 -13
  7. data/lib/relaxdb/document.rb +150 -218
  8. data/lib/relaxdb/extlib.rb +7 -1
  9. data/lib/relaxdb/migration.rb +11 -8
  10. data/lib/relaxdb/net_http_server.rb +19 -1
  11. data/lib/relaxdb/paginator.rb +30 -11
  12. data/lib/relaxdb/query.rb +1 -1
  13. data/lib/relaxdb/{belongs_to_proxy.rb → references_proxy.rb} +3 -3
  14. data/lib/relaxdb/relaxdb.rb +87 -7
  15. data/lib/relaxdb/server.rb +8 -2
  16. data/lib/relaxdb/taf2_curb_server.rb +2 -1
  17. data/lib/relaxdb/uuid_generator.rb +38 -2
  18. data/lib/relaxdb/view_by_delegator.rb +34 -0
  19. data/lib/relaxdb/view_object.rb +1 -1
  20. data/lib/relaxdb/view_uploader.rb +16 -2
  21. data/lib/relaxdb/views.rb +23 -55
  22. data/readme.rb +3 -3
  23. data/spec/all_delegator_spec.rb +52 -0
  24. data/spec/callbacks_spec.rb +4 -4
  25. data/spec/derived_properties_spec.rb +4 -4
  26. data/spec/design_doc_spec.rb +2 -2
  27. data/spec/doc_inheritable_spec.rb +2 -2
  28. data/spec/document_spec.rb +47 -25
  29. data/spec/migration_spec.rb +12 -10
  30. data/spec/qpaginate_spec.rb +88 -0
  31. data/spec/query_spec.rb +2 -2
  32. data/spec/references_proxy_spec.rb +94 -0
  33. data/spec/relaxdb_spec.rb +29 -21
  34. data/spec/server_spec.rb +4 -3
  35. data/spec/spec_helper.rb +1 -0
  36. data/spec/spec_models.rb +48 -57
  37. data/spec/uuid_generator_spec.rb +34 -0
  38. data/spec/view_by_spec.rb +62 -54
  39. data/spec/view_docs_by_spec.rb +85 -0
  40. metadata +38 -27
  41. data/lib/more/atomic_bulk_save_support.rb +0 -18
  42. data/lib/relaxdb/has_many_proxy.rb +0 -101
  43. data/lib/relaxdb/has_one_proxy.rb +0 -42
  44. data/lib/relaxdb/references_many_proxy.rb +0 -97
  45. data/spec/belongs_to_spec.rb +0 -124
  46. data/spec/has_many_spec.rb +0 -202
  47. data/spec/has_one_spec.rb +0 -123
  48. data/spec/references_many_spec.rb +0 -173
  49. data/spec/view_spec.rb +0 -23
@@ -7,7 +7,7 @@ module RelaxDB
7
7
  hash.each do |k, v|
8
8
 
9
9
  if k.to_s =~ /_at$/
10
- v = Time.local(*ParseDate.parsedate(v)) rescue v
10
+ v = Time.parse(v).utc rescue v
11
11
  end
12
12
 
13
13
  instance_variable_set("@#{k}", v)
@@ -7,11 +7,17 @@ module RelaxDB
7
7
  # Methods must start and finish on different lines
8
8
  # The function declaration must start at the beginning of a line
9
9
  # As '-' is used as a delimiter, the view name may not contain '-'
10
- # Exepcted function declaration form is
10
+ # Expected function declaration form is
11
11
  # function funcname-functype(doc) {
12
12
  # For example
13
13
  # function Users_followers-map(doc) {
14
14
  #
15
+ # Builtin Erlang views may be specified by listing a one-line function
16
+ # that contains only the builtin name. For example:
17
+ # function Users_followers-reduce() {
18
+ # _sum
19
+ # }
20
+ #
15
21
  def upload(filename)
16
22
  lines = File.readlines(filename)
17
23
  dd = RelaxDB::DesignDocument.get(RelaxDB.dd)
@@ -37,7 +43,15 @@ module RelaxDB
37
43
  declr =~ /(\w)+-(\w)+/
38
44
  declr.sub!($&, '')
39
45
  view_name, type = $&.split('-')
40
- func = lines[m[i]...m[i+1]].join
46
+ func = lines[m[i]...m[i+1]]
47
+
48
+ # Cater for erlang view shortcuts e.g. _sum, _count etc.
49
+ if func[1] =~ /\s*_\w+\s*$/
50
+ func = func[1].strip
51
+ else
52
+ func = func.join
53
+ end
54
+
41
55
  yield view_name, type, func
42
56
  end
43
57
  end
data/lib/relaxdb/views.rb CHANGED
@@ -8,15 +8,23 @@ module RelaxDB
8
8
  function(doc) {
9
9
  var class_match = #{kls_check kls}
10
10
  if (class_match) {
11
- emit(doc._id, doc);
11
+ emit(doc._id, 1);
12
12
  }
13
13
  }
14
14
  QUERY
15
15
 
16
- View.new "#{class_name}_all", map, sum_reduce_func
16
+ View.new "#{class_name}_all", map, "_sum"
17
+ end
18
+
19
+ def self.docs_by_att_list(kls, *atts)
20
+ create_by_att_list "doc", "_count", kls, *atts
17
21
  end
18
22
 
19
23
  def self.by_att_list(kls, *atts)
24
+ create_by_att_list 1, "_sum", kls, *atts
25
+ end
26
+
27
+ def self.create_by_att_list emit_val, reduce_func, kls, *atts
20
28
  class_name = kls[0]
21
29
  key = atts.map { |a| "doc.#{a}" }.join(", ")
22
30
  key = atts.size > 1 ? key.sub(/^/, "[").sub(/$/, "]") : key
@@ -26,61 +34,20 @@ module RelaxDB
26
34
  function(doc) {
27
35
  var class_match = #{kls_check kls}
28
36
  if (class_match && #{prop_check}) {
29
- emit(#{key}, doc);
37
+ emit(#{key}, #{emit_val});
30
38
  }
31
39
  }
32
40
  QUERY
33
41
 
34
42
  view_name = "#{class_name}_by_" << atts.join("_and_")
35
- View.new view_name, map, sum_reduce_func
43
+ View.new view_name, map, reduce_func
36
44
  end
37
-
38
-
39
- def self.has_n(client_class, relationship, target_class, relationship_to_client)
40
- map = <<-QUERY
41
- function(doc) {
42
- if (doc.relaxdb_class == "#{target_class}" && doc.#{relationship_to_client}_id)
43
- emit(doc.#{relationship_to_client}_id, doc);
44
- }
45
- QUERY
46
45
 
47
- view_name = "#{client_class}_#{relationship}"
48
- View.new view_name, map
49
- end
50
-
51
- def self.references_many(client_class, relationship, target_class, peers)
52
- map = <<-QUERY
53
- function(doc) {
54
- if (doc.relaxdb_class == "#{target_class}" && doc.#{peers}) {
55
- var i;
56
- for(i = 0; i < doc.#{peers}.length; i++) {
57
- emit(doc.#{peers}[i], doc);
58
- }
59
- }
60
- }
61
- QUERY
62
-
63
- view_name = "#{client_class}_#{relationship}"
64
- View.new view_name, map
65
- end
66
-
67
46
  def self.kls_check kls
68
47
  kls_names = kls.map{ |k| %Q("#{k}") }.join(",")
69
48
  "[#{kls_names}].indexOf(doc.relaxdb_class) >= 0;"
70
49
  end
71
50
 
72
- def self.sum_reduce_func
73
- <<-QUERY
74
- function(keys, values, rereduce) {
75
- if (rereduce) {
76
- return sum(values);
77
- } else {
78
- return values.length;
79
- }
80
- }
81
- QUERY
82
- end
83
-
84
51
  end
85
52
 
86
53
  class View
@@ -93,22 +60,23 @@ module RelaxDB
93
60
  @reduce_func = reduce_func
94
61
  end
95
62
 
96
- def design_doc
63
+ def self.design_doc
97
64
  @design_doc ||= DesignDocument.get(RelaxDB.dd)
98
65
  end
99
66
 
100
- def save
101
- dd = design_doc
67
+ #
68
+ # A convenience for tests that create their own views
69
+ #
70
+ def self.reset
71
+ @design_doc = nil
72
+ end
73
+
74
+ def add_to_design_doc
75
+ dd = View.design_doc
102
76
  dd.add_map_view(@view_name, @map_func)
103
77
  dd.add_reduce_view(@view_name, @reduce_func) if @reduce_func
104
- dd.save
105
- end
106
-
107
- def exists?
108
- dd = design_doc
109
- dd.data["views"] && dd.data["views"][@view_name]
110
78
  end
111
-
79
+
112
80
  end
113
81
 
114
82
  end
data/readme.rb CHANGED
@@ -28,9 +28,9 @@ class Invite < RelaxDB::Document
28
28
  property :sender_name,
29
29
  :derived => [:sender, lambda { |p, o| o.sender.name } ]
30
30
 
31
- view_by :sender_name
32
- view_by :sender_id
33
- view_by :recipient_id, :created_at, :descending => true
31
+ view_docs_by :sender_name
32
+ view_docs_by :sender_id
33
+ view_docs_by :recipient_id, :created_at, :descending => true
34
34
 
35
35
  def on_update_conflict
36
36
  puts "conflict!"
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/spec_models.rb'
3
+
4
+ describe RelaxDB::AllDelegator do
5
+
6
+ before(:all) do
7
+ setup_test_db
8
+ end
9
+
10
+ describe "size" do
11
+
12
+ it "should return the total count for a given class" do
13
+ docs = (1..101).map { |i| Primitives.new :num => i }
14
+ RelaxDB.bulk_save! *docs
15
+ Primitives.all.size.should == 101
16
+ end
17
+
18
+ end
19
+
20
+ describe "all" do
21
+
22
+ it "should return the ids for the given class" do
23
+ docs = (1..3).map { |i| Primitives.new :_id => "p#{i}" }
24
+ RelaxDB.bulk_save! *docs
25
+ Primitives.all.should == %w(p1 p2 p3)
26
+ end
27
+
28
+ end
29
+
30
+ describe "load" do
31
+
32
+ it "should load all docs for the given class" do
33
+ docs = (1..3).map { |i| Primitives.new :num => i }
34
+ RelaxDB.bulk_save! *docs
35
+ pms = Primitives.all.load!
36
+ pms.map { |p| p.num }.inject(&:+).should == 6
37
+ end
38
+
39
+ end
40
+
41
+ describe "destroy" do
42
+
43
+ it "should destroy all docs for the given class" do
44
+ docs = (1..3).map { |i| Primitives.new :num => i }
45
+ RelaxDB.bulk_save! *docs
46
+ Primitives.all.destroy!
47
+ Primitives.all.load!.should == []
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -26,14 +26,14 @@ describe RelaxDB::Document, "callbacks" do
26
26
 
27
27
  it "should prevent the object from being saved if it returns false" do
28
28
  c = Class.new(RelaxDB::Document) do
29
- before_save lambda { false }
29
+ before_save lambda { |o| false }
30
30
  end
31
31
  c.new.save.should == false
32
32
  end
33
33
 
34
34
  it "should add a description to errors when false is returned" do
35
35
  c = Class.new(RelaxDB::Document) do
36
- before_save lambda { false }
36
+ before_save lambda { |o| false }
37
37
  end
38
38
  x = c.new
39
39
  x.save
@@ -42,14 +42,14 @@ describe RelaxDB::Document, "callbacks" do
42
42
 
43
43
  it "should not prevent the object from being saved if it returns nil" do
44
44
  c = Class.new(RelaxDB::Document) do
45
- before_save lambda { nil }
45
+ before_save lambda { |o| nil }
46
46
  end
47
47
  c.new.save!
48
48
  end
49
49
 
50
50
  it "may be a proc" do
51
51
  c = Class.new(RelaxDB::Document) do
52
- before_save lambda { false }
52
+ before_save lambda { |o| false }
53
53
  end
54
54
  c.new.save.should == false
55
55
  end
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/spec_models.rb'
3
3
 
4
4
  class DpInvite < RelaxDB::Document
5
5
  property :event_name, :derived => [:event, lambda { |en, i| i.event.name }]
6
- belongs_to :event
6
+ references :event
7
7
  end
8
8
 
9
9
  class DpEvent < RelaxDB::Document
@@ -46,7 +46,7 @@ describe RelaxDB::Document, "derived properties" do
46
46
  it "should only be updated for registered properties" do
47
47
  invite = Class.new(RelaxDB::Document) do
48
48
  property :event_name, :derived => [:foo, lambda { |en, i| i.event.name }]
49
- belongs_to :event
49
+ references :event
50
50
  end
51
51
 
52
52
  event = Class.new(RelaxDB::Document) do
@@ -61,7 +61,7 @@ describe RelaxDB::Document, "derived properties" do
61
61
  it "should have the existing value passed to the first lambda param" do
62
62
  invite = Class.new(RelaxDB::Document) do
63
63
  property :event_name, :derived => [:event, lambda { |en, i| en.nil? ? i.event.name : "bar" }]
64
- belongs_to :event
64
+ references :event
65
65
  end
66
66
 
67
67
  event = Class.new(RelaxDB::Document) do
@@ -93,7 +93,7 @@ describe RelaxDB::Document, "derived properties" do
93
93
  invite = Class.new(RelaxDB::Document) do
94
94
  property :name, :derived => [:event, lambda { |en, i| i.event.name }]
95
95
  property :location, :derived => [:event, lambda { |en, i| i.event.location }]
96
- belongs_to :event
96
+ references :event
97
97
  end
98
98
 
99
99
  event = Class.new(RelaxDB::Document) do
@@ -15,8 +15,8 @@ describe RelaxDB::DesignDocument do
15
15
  describe "#save" do
16
16
 
17
17
  it "should create a corresponding document in CouchDB" do
18
- RelaxDB::DesignDocument.get("foo").save
19
- RelaxDB.load("_design/foo").should_not be_nil
18
+ RelaxDB::DesignDocument.get("foo").save
19
+ RelaxDB::DesignDocument.get("foo").should_not be_nil
20
20
  end
21
21
 
22
22
  end
@@ -34,8 +34,8 @@ describe "Inheritance" do
34
34
  a = Ancestor.new(:x => 0).save!
35
35
  d = Descendant.new(:x => 1).save!
36
36
 
37
- Ancestor.all.should == [a, d]
38
- Descendant.all.should == [d]
37
+ Ancestor.all.load!.should include(a, d)
38
+ Descendant.all.load!.should == [d]
39
39
  end
40
40
 
41
41
  it "should function with inheritance trees" do
@@ -25,10 +25,10 @@ describe RelaxDB::Document do
25
25
  p.viewed_at.should be_close(now, 1)
26
26
  end
27
27
 
28
- it "will silently ignore parameters that don't specify class attributes" do
29
- # Consider this a feature or bug. It allows an object containing both request params
30
- # and superflous data to be passed directly to a constructor.
31
- Post.new(:foo => "").save
28
+ it "will fail on parameters that don't specify class attributes" do
29
+ lambda {
30
+ Post.new :foo => ""
31
+ }.should raise_error
32
32
  end
33
33
 
34
34
  it "should create a document with a non conflicing state" do
@@ -60,6 +60,11 @@ describe RelaxDB::Document do
60
60
  json = RelaxDB.get(p._id)
61
61
  json["created_at"].should == "1970/01/01 00:00:00 +0000"
62
62
  end
63
+
64
+ it "should allow to be called with an options argument, to be compatible with ActiveSupport" do
65
+ s = Time.at(0)
66
+ lambda { s.to_json( :active_support => 'love' ) }.should_not raise_error(ArgumentError)
67
+ end
63
68
 
64
69
  end
65
70
 
@@ -112,12 +117,11 @@ describe RelaxDB::Document do
112
117
  p.created_at.should be_close(back_then, 1)
113
118
  end
114
119
 
115
- it "should set document conflict state on conflicting save" do
116
- a1 = Atom.new
117
- a2 = a1.dup
120
+ it "should set document conflict state on conflicting save" do
121
+ a1, a2 = Atom.new(:_id => "a1"), Atom.new(:_id => "a1")
118
122
  a1.save!
119
123
  a2.save
120
- a2.should be_update_conflict
124
+ a2.should be_update_conflict
121
125
  end
122
126
 
123
127
  end
@@ -139,13 +143,26 @@ describe RelaxDB::Document do
139
143
  end
140
144
 
141
145
  it "should raise UpdateConflict on an update conflict" do
142
- a1 = Atom.new
143
- a2 = a1.dup
146
+ a1, a2 = Atom.new(:_id => "a1"), Atom.new(:_id => "a1")
144
147
  a1.save!
145
148
  lambda { a2.save! }.should raise_error(RelaxDB::UpdateConflict)
146
149
  end
147
150
 
148
151
  end
152
+
153
+ describe "property" do
154
+
155
+ it "may contain another object" do
156
+ a = Atom.new.save!
157
+ p = Primitives.new(:context => a).save!
158
+
159
+ p = RelaxDB.reload p
160
+ RelaxDB.db.reset_req_count
161
+ p.context.to_obj.should == a
162
+ RelaxDB.db.req_count.should == 0
163
+ end
164
+
165
+ end
149
166
 
150
167
  describe "user defined property reader" do
151
168
 
@@ -158,27 +175,22 @@ describe RelaxDB::Document do
158
175
  it "should not modify internal state" do
159
176
  o = BespokeReader.new(:val => 101).save
160
177
  o = RelaxDB.load o._id
161
- o.instance_variable_get(:@val).should == 101
178
+ o.data["val"].should == 101
162
179
  end
163
180
 
164
181
  end
165
182
 
166
183
  describe "user defined property writer" do
167
184
 
168
- it "should not be used to modify state" do
185
+ # So this works now, but it hasn't in the past - this behaviour has changed
186
+ # many times. Use with caution i.e. it wouldn't be wise to rely on this
187
+ # across a large swathe of your codebase.
188
+ it "should only be used to modify state with caution" do
169
189
  o = BespokeWriter.new(:val => 101).save
170
190
  o = RelaxDB.load o._id
171
- o.val.should == 81
191
+ o.val.should == 91
172
192
  end
173
-
174
- it "may be used if effectively idempotent" do
175
- o = BespokeWriter.new(:tt => "2009/04/01").save
176
- RelaxDB.reload(o).tt.should == Time.parse("2009/04/01")
177
-
178
- o = BespokeWriter.new(:tt => Time.now).save
179
- RelaxDB.reload(o).tt.should be_close(Time.now, 2)
180
- end
181
-
193
+
182
194
  end
183
195
 
184
196
  describe "loaded objects" do
@@ -190,10 +202,9 @@ describe RelaxDB::Document do
190
202
  p.str.should == "foo"
191
203
  p.num.should == 19.30
192
204
  p.true_bool.should be_true
193
- # p.false_bool.should be_false
194
- p.false_bool.should_not be
205
+ p.false_bool.should be_false
195
206
  p.created_at.should be_close(now, 1)
196
- p.empty.should be_nil
207
+ p.context.should be_nil
197
208
  end
198
209
 
199
210
  it "should be saveable" do
@@ -542,4 +553,15 @@ describe RelaxDB::Document do
542
553
 
543
554
  end
544
555
 
556
+ describe "initialization process" do
557
+
558
+ it "should not modify internal state unexpectedly" do
559
+ a = Atom.new
560
+ c = Contrived.new :context => a
561
+ c.context_count.should == 1
562
+ c.foo.should == 10
563
+ end
564
+
565
+ end
566
+
545
567
  end