paulcarey-relaxdb 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +8 -9
- data/Rakefile +1 -1
- data/lib/relaxdb/document.rb +100 -43
- data/lib/relaxdb/has_many_proxy.rb +7 -5
- data/lib/relaxdb/relaxdb.rb +1 -1
- data/lib/relaxdb/server.rb +6 -0
- data/spec/belongs_to_spec.rb +26 -5
- data/spec/derived_properties_spec.rb +109 -0
- data/spec/document_spec.rb +57 -8
- data/spec/spec_models.rb +1 -1
- metadata +3 -3
- data/spec/denormalisation_spec.rb +0 -49
data/README.textile
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
h3. What's New?
|
2
2
|
|
3
|
+
* Denormalisation via derived properties. Examples in spec/derived_properties_spec.rb.
|
4
|
+
|
5
|
+
* Validations may be skipped by passing the attribute symbol(s) to @save@ or @save!@.
|
6
|
+
|
7
|
+
* Semantic changes for @ has_many#<< @. The parent object is now assigned to the child object *prior* to validation. This potentially breaking change was made to allow child objects to derive properties from a parent object.
|
8
|
+
|
3
9
|
* Semantic consistency for load, load!, save and save!. The bang versions raise an exception when their more relaxed siblings would simply return nil.
|
4
10
|
|
5
11
|
* Minimal support for CouchDB validation
|
6
12
|
|
7
|
-
* Time storage changes. All Time objects are now converted to UTC and formatted as %Y/%m/%d %H:%M:%S +0000
|
13
|
+
* 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.
|
8
14
|
|
9
15
|
* Pagination! CouchDB offers great support for retrieving a subset of data, but the housekeeping is tricky. RelaxDB takes care of it.
|
10
16
|
** Note that if you invoke paginate_by on an already created view, the necessary reduce function won't be automatically created. Take a look at SortedByView and create the reduce func by hand.
|
11
17
|
* Support for multi key post
|
12
18
|
** For example, @ Numbers.all.sorted_by(:val) { |q| q.keys([1,2,3,5]) } @
|
13
|
-
* Works with CouchDB 0.9 trunk as of
|
19
|
+
* Works with CouchDB 0.9 trunk as of 2009/01/02. Note that pagination won't work correctly on trunk until issue "COUCHDB-135":http://issues.apache.org/jira/browse/COUCHDB-135 is fixed.
|
14
20
|
|
15
21
|
*Note*: 0.2.1 requires CouchDB 0.9 trunk. 0.2.0 works with CouchDB 0.8 onwards.
|
16
22
|
|
@@ -154,13 +160,6 @@ RelaxDB::GraphCreator.create
|
|
154
160
|
|
155
161
|
Requires graphviz. Useful for visualising relationships between a limited number of document e.g. test fixtures. "Description and example":http://dev.strawberrydiva.com/visually_explore_couchdb/.
|
156
162
|
|
157
|
-
h3. Experimental Features
|
158
|
-
|
159
|
-
* Declarative denormalisation
|
160
|
-
** Create a partial object graph in JSON with a single call
|
161
|
-
** May be used to require fewer GET requests
|
162
|
-
** View the denormalisation spec for examples
|
163
|
-
|
164
163
|
h2. Incomplete list of limitations
|
165
164
|
|
166
165
|
* Error handling is not robust
|
data/Rakefile
CHANGED
data/lib/relaxdb/document.rb
CHANGED
@@ -31,14 +31,17 @@ module RelaxDB
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
if opts[:validator]
|
35
|
+
create_validator(prop, opts[:validator])
|
36
|
+
end
|
35
37
|
|
36
38
|
if opts[:validation_msg]
|
37
|
-
|
38
|
-
opts[:validation_msg]
|
39
|
-
end
|
39
|
+
create_validation_msg(prop, opts[:validation_msg])
|
40
40
|
end
|
41
41
|
|
42
|
+
if opts[:derived]
|
43
|
+
add_derived_prop(prop, opts[:derived])
|
44
|
+
end
|
42
45
|
end
|
43
46
|
|
44
47
|
def self.properties
|
@@ -47,14 +50,47 @@ module RelaxDB
|
|
47
50
|
end
|
48
51
|
|
49
52
|
def self.create_validator(att, v)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
method_name = "validate_#{att}"
|
54
|
+
if v.is_a? Proc
|
55
|
+
v.arity == 1 ?
|
56
|
+
define_method(method_name) { |att_val| v.call(att_val) } :
|
57
|
+
define_method(method_name) { |att_val| v.call(att_val, self) }
|
58
|
+
elsif instance_methods.include? "validator_#{v}"
|
59
|
+
define_method(method_name) { |att_val| send("validator_#{v}", att_val, self) }
|
60
|
+
else
|
61
|
+
define_method(method_name) { |att_val| send(v, att_val) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.create_validation_msg(prop, validation_msg)
|
66
|
+
if validation_msg.is_a?(Proc)
|
67
|
+
validation_msg.arity == 1 ?
|
68
|
+
define_method("#{prop}_validation_msg") { |prop_val| validation_msg.call(prop_val) } :
|
69
|
+
define_method("#{prop}_validation_msg") { |prop_val| validation_msg.call(prop_val, self) }
|
70
|
+
else
|
71
|
+
define_method("#{prop}_validation_msg") { validation_msg }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# See derived_properties_spec.rb for usage
|
76
|
+
def self.add_derived_prop(prop, deriver)
|
77
|
+
source, writer = deriver[0], deriver[1]
|
78
|
+
@derived_prop_writers ||= {}
|
79
|
+
@derived_prop_writers[source] ||= {}
|
80
|
+
@derived_prop_writers[source][prop] = writer
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.derived_prop_writers
|
84
|
+
@derived_prop_writers ||= {}
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_derived_props(source)
|
88
|
+
writers = self.class.derived_prop_writers[source]
|
89
|
+
if writers
|
90
|
+
writers.each do |prop, writer|
|
91
|
+
current_val = send(prop)
|
92
|
+
send("#{prop}=", writer.call(current_val, self))
|
93
|
+
end
|
58
94
|
end
|
59
95
|
end
|
60
96
|
|
@@ -67,7 +103,9 @@ module RelaxDB
|
|
67
103
|
property :_id
|
68
104
|
property :_rev
|
69
105
|
|
70
|
-
def initialize(
|
106
|
+
def initialize(set_via_writers={}, hash=nil)
|
107
|
+
hash, set_via_writers = set_via_writers, true if set_via_writers.is_a?(Hash)
|
108
|
+
|
71
109
|
# The default _id will be overwritten if loaded from CouchDB
|
72
110
|
self._id = UuidGenerator.uuid
|
73
111
|
|
@@ -82,28 +120,57 @@ module RelaxDB
|
|
82
120
|
end
|
83
121
|
end
|
84
122
|
|
85
|
-
|
123
|
+
# Maybe use the presence of _rev in hash to determine this rather than
|
124
|
+
# exposing the implementation detail to clients that choose to override?
|
125
|
+
set_via_writers ? set_attributes(hash) : set_raw_attributes(hash)
|
86
126
|
end
|
87
127
|
|
88
128
|
def set_attributes(data)
|
89
129
|
data.each do |key, val|
|
90
130
|
# Only set instance variables on creation - object references are resolved on demand
|
91
131
|
|
92
|
-
# If the variable name ends in _at try to convert it to a Time
|
93
|
-
if key =~
|
132
|
+
# If the variable name ends in _at, _on or _date try to convert it to a Time
|
133
|
+
if [/_at$/, /_on$/, /_date$/].inject(nil) { |i, r| i ||= (key =~ r) }
|
94
134
|
val = Time.parse(val).utc rescue val
|
95
135
|
end
|
96
136
|
|
97
137
|
# Ignore param keys that don't have a corresponding writer
|
98
138
|
# This allows us to comfortably accept a hash containing superflous data
|
99
139
|
# such as a params hash in a controller
|
140
|
+
send("#{key}=".to_sym, val) if methods.include? "#{key}="
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Set the raw attribute values rather than setting via the associated writers
|
145
|
+
# The associated writers may invoke validation functions or set derived values
|
146
|
+
# Such behaviour is unwanted when loading from CouchDB and potentially under
|
147
|
+
# other circumstances
|
148
|
+
def set_raw_attributes(data)
|
149
|
+
data.each do |key, val|
|
150
|
+
if [/_at$/, /_on$/, /_date$/].inject(nil) { |i, r| i ||= (key =~ r) }
|
151
|
+
val = Time.parse(val).utc rescue val
|
152
|
+
end
|
153
|
+
|
100
154
|
if methods.include? "#{key}="
|
101
|
-
|
155
|
+
key = key.to_sym
|
156
|
+
if properties.include? key
|
157
|
+
instance_variable_set("@#{key}".to_sym, val)
|
158
|
+
elsif self.class.has_one_rels.include? key
|
159
|
+
create_or_get_proxy(HasOneProxy, key).target = val
|
160
|
+
else
|
161
|
+
# belongs_to
|
162
|
+
if key.to_s =~ /_id$/
|
163
|
+
instance_variable_set("@#{key}".to_sym, val)
|
164
|
+
else
|
165
|
+
create_or_get_proxy(BelongsToProxy, key).target = val
|
166
|
+
end
|
167
|
+
end
|
102
168
|
end
|
103
169
|
|
104
170
|
end
|
105
|
-
|
106
|
-
|
171
|
+
|
172
|
+
end
|
173
|
+
|
107
174
|
def inspect
|
108
175
|
s = "#<#{self.class}:#{self.object_id}"
|
109
176
|
properties.each do |prop|
|
@@ -122,9 +189,6 @@ module RelaxDB
|
|
122
189
|
self.class.belongs_to_rels.each do |relationship, opts|
|
123
190
|
id = instance_variable_get("@#{relationship}_id".to_sym)
|
124
191
|
data["#{relationship}_id"] = id if id
|
125
|
-
if opts[:denormalise]
|
126
|
-
add_denormalised_data(data, relationship, opts)
|
127
|
-
end
|
128
192
|
end
|
129
193
|
properties.each do |prop|
|
130
194
|
prop_val = instance_variable_get("@#{prop}".to_sym)
|
@@ -133,28 +197,16 @@ module RelaxDB
|
|
133
197
|
data["class"] = self.class.name
|
134
198
|
data.to_json
|
135
199
|
end
|
136
|
-
|
137
|
-
# quick n' dirty denormalisation - explicit denormalisation will probably become a
|
138
|
-
# permanent fixture of RelaxDB, but quite likely in a different form to this one
|
139
|
-
def add_denormalised_data(data, relationship, opts)
|
140
|
-
obj = send(relationship)
|
141
|
-
if obj
|
142
|
-
opts[:denormalise].each do |prop_name|
|
143
|
-
val = obj.send(prop_name)
|
144
|
-
data["#{relationship}_#{prop_name}"] = val
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
200
|
+
|
149
201
|
# Order changed as of 30/10/2008 to be consistent with ActiveRecord
|
150
202
|
# Not yet sure of final implemention for hooks - may lean more towards DM than AR
|
151
|
-
def save
|
152
|
-
return false unless validates?
|
203
|
+
def save(*validation_skip_list)
|
204
|
+
return false unless validates?(*validation_skip_list)
|
153
205
|
return false unless before_save
|
154
206
|
|
155
207
|
set_created_at if new_document?
|
156
208
|
|
157
|
-
resp = RelaxDB.db.put(
|
209
|
+
resp = RelaxDB.db.put(_id, to_json)
|
158
210
|
self._rev = JSON.parse(resp.body)["rev"]
|
159
211
|
|
160
212
|
after_save
|
@@ -162,30 +214,33 @@ module RelaxDB
|
|
162
214
|
self
|
163
215
|
end
|
164
216
|
|
165
|
-
def save!
|
166
|
-
save || raise(DocumentNotSaved)
|
217
|
+
def save!(*validation_skip_list)
|
218
|
+
save(*validation_skip_list) || raise(DocumentNotSaved.new(self.errors.to_json))
|
167
219
|
end
|
168
220
|
|
169
|
-
def validates?
|
221
|
+
def validates?(*skip_list)
|
170
222
|
total_success = true
|
171
223
|
properties.each do |prop|
|
224
|
+
next if skip_list.include? prop
|
172
225
|
if methods.include? "validate_#{prop}"
|
173
226
|
prop_val = instance_variable_get("@#{prop}")
|
174
227
|
success = send("validate_#{prop}", prop_val) rescue false
|
175
228
|
unless success
|
176
229
|
if methods.include? "#{prop}_validation_msg"
|
177
|
-
@errors[
|
230
|
+
@errors[prop] = send("#{prop}_validation_msg", prop_val) rescue "validation_msg_exception:invalid:#{prop_val}"
|
231
|
+
else
|
232
|
+
@errors[prop] = "invalid:#{prop}"
|
178
233
|
end
|
179
234
|
end
|
180
235
|
total_success &= success
|
181
236
|
end
|
182
237
|
end
|
183
238
|
|
184
|
-
# Unsure whether to pass the id or the doc itself - id is all I need right now
|
185
239
|
self.class.belongs_to_rels.each do |rel, opts|
|
186
240
|
if methods.include? "validate_#{rel}"
|
187
241
|
rel_val = instance_variable_get("@#{rel}_id")
|
188
242
|
success = send("validate_#{rel}", rel_val) rescue false
|
243
|
+
@errors[rel] = "invalid:#{rel_val}" unless success
|
189
244
|
total_success &= success
|
190
245
|
end
|
191
246
|
end
|
@@ -299,11 +354,13 @@ module RelaxDB
|
|
299
354
|
|
300
355
|
define_method("#{relationship}=") do |new_target|
|
301
356
|
create_or_get_proxy(BelongsToProxy, relationship).target = new_target
|
357
|
+
write_derived_props(relationship)
|
302
358
|
end
|
303
359
|
|
304
360
|
# Allows all writers to be invoked from the hash passed to initialize
|
305
361
|
define_method("#{relationship}_id=") do |id|
|
306
362
|
instance_variable_set("@#{relationship}_id".to_sym, id)
|
363
|
+
write_derived_props(relationship)
|
307
364
|
end
|
308
365
|
|
309
366
|
# Allows belongs_to relationships to be used by the paginator
|
@@ -16,13 +16,15 @@ module RelaxDB
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def <<(obj)
|
19
|
-
return false unless obj.validates?
|
20
19
|
return false if @children.include?(obj)
|
21
|
-
|
20
|
+
|
22
21
|
obj.send("#{@relationship_as_viewed_by_target}=".to_sym, @client)
|
23
|
-
obj.save
|
24
|
-
|
25
|
-
|
22
|
+
if obj.save
|
23
|
+
@children << obj
|
24
|
+
self
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def clear
|
data/lib/relaxdb/relaxdb.rb
CHANGED
data/lib/relaxdb/server.rb
CHANGED
@@ -63,7 +63,12 @@ module RelaxDB
|
|
63
63
|
|
64
64
|
class CouchDB
|
65
65
|
|
66
|
+
# Used for test instrumentation only i.e. to assert that
|
67
|
+
# an expected number of GET requests have been issued
|
68
|
+
attr_accessor :get_count
|
69
|
+
|
66
70
|
def initialize(config)
|
71
|
+
@get_count = 0
|
67
72
|
@server = RelaxDB::Server.new(config[:host], config[:port])
|
68
73
|
@logger = config[:logger] ? config[:logger] : Logger.new(Tempfile.new('couchdb.log'))
|
69
74
|
end
|
@@ -96,6 +101,7 @@ module RelaxDB
|
|
96
101
|
|
97
102
|
# *ignored allows methods to invoke get or post indifferently
|
98
103
|
def get(path=nil, *ignored)
|
104
|
+
@get_count += 1
|
99
105
|
@logger.info("GET /#{@db}/#{unesc(path)}")
|
100
106
|
@server.get("/#{@db}/#{path}")
|
101
107
|
end
|
data/spec/belongs_to_spec.rb
CHANGED
@@ -75,13 +75,34 @@ describe RelaxDB::BelongsToProxy do
|
|
75
75
|
r.photo.rating.should == r
|
76
76
|
end
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
describe "validator" do
|
79
|
+
|
80
|
+
it "should be passed the _id and object" do
|
81
|
+
a = Atom.new(:_id => "atom").save!
|
82
|
+
c = Class.new(RelaxDB::Document) do
|
83
|
+
belongs_to :foo, :validator => lambda { |foo_id, obj| foo_id.reverse == obj._id }
|
84
|
+
end
|
85
|
+
c.new(:_id => "mota", :foo => a).save!
|
86
|
+
end
|
87
|
+
|
88
|
+
it "may be used with a predefined validator" do
|
89
|
+
c = Class.new(RelaxDB::Document) do
|
90
|
+
belongs_to :foo, :validator => :required
|
91
|
+
end
|
92
|
+
c.new.save.should be_false
|
81
93
|
end
|
82
|
-
c.new.save.should be_false
|
83
|
-
end
|
84
94
|
|
95
|
+
it "should be provided with a default error message when validation fails" do
|
96
|
+
c = Class.new(RelaxDB::Document) do
|
97
|
+
belongs_to :foo, :validator => :required
|
98
|
+
end
|
99
|
+
x = c.new
|
100
|
+
x.save
|
101
|
+
x.errors[:foo].should_not be_blank
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
85
106
|
end
|
86
107
|
|
87
108
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
require File.dirname(__FILE__) + '/spec_models.rb'
|
3
|
+
|
4
|
+
# These tests would ideally instrument server.rb, asserting that no
|
5
|
+
# HTTP requests are made when retrieving the derived values
|
6
|
+
|
7
|
+
class DpInvite < RelaxDB::Document
|
8
|
+
property :event_name, :derived => [:event, lambda { |en, i| i.event.name }]
|
9
|
+
belongs_to :event
|
10
|
+
end
|
11
|
+
|
12
|
+
class DpEvent < RelaxDB::Document
|
13
|
+
property :name
|
14
|
+
end
|
15
|
+
|
16
|
+
describe RelaxDB::Document, "derived properties" do
|
17
|
+
|
18
|
+
before(:all) do
|
19
|
+
RelaxDB.configure(:host => "localhost", :port => 5984)
|
20
|
+
end
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
|
24
|
+
RelaxDB.use_db "relaxdb_spec_db"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have its value updated when the source is updated" do
|
28
|
+
e = DpEvent.new(:name => "shindig")
|
29
|
+
i = DpInvite.new(:event => e)
|
30
|
+
i.event_name.should == "shindig"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have its value persisted" do
|
34
|
+
e = DpEvent.new(:name => "shindig").save!
|
35
|
+
i = DpInvite.new(:event => e).save!
|
36
|
+
|
37
|
+
RelaxDB.db.get_count = 0
|
38
|
+
i = RelaxDB.load i._id
|
39
|
+
i.event_name.should == "shindig"
|
40
|
+
RelaxDB.db.get_count.should == 1
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should have its value updated when the source_id is updated for a saved event" do
|
44
|
+
e = DpEvent.new(:name => "shindig").save!
|
45
|
+
i = DpInvite.new(:event_id => e._id)
|
46
|
+
i.event_name.should == "shindig"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "will fail when the source_id is updated for a unsaved event" do
|
50
|
+
# Almost certainly not desired - merely codifying current behaviour
|
51
|
+
e = DpEvent.new(:name => "shindig")
|
52
|
+
lambda { DpInvite.new(:event_id => e._id) }.should raise_error
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should only be updated for registered properties" do
|
56
|
+
invite = Class.new(RelaxDB::Document) do
|
57
|
+
property :event_name, :derived => [:foo, lambda { |en, i| i.event.name }]
|
58
|
+
belongs_to :event
|
59
|
+
end
|
60
|
+
|
61
|
+
event = Class.new(RelaxDB::Document) do
|
62
|
+
property :name
|
63
|
+
end
|
64
|
+
|
65
|
+
e = event.new(:name => "shindig")
|
66
|
+
i = invite.new(:event => e)
|
67
|
+
i.event_name.should be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should have the existing value passed to the first lambda param" do
|
71
|
+
invite = Class.new(RelaxDB::Document) do
|
72
|
+
property :event_name, :derived => [:event, lambda { |en, i| en.nil? ? i.event.name : "bar" }]
|
73
|
+
belongs_to :event
|
74
|
+
end
|
75
|
+
|
76
|
+
event = Class.new(RelaxDB::Document) do
|
77
|
+
property :name
|
78
|
+
end
|
79
|
+
|
80
|
+
e1 = event.new(:name => "shindig")
|
81
|
+
e2 = event.new(:name => "shindig2")
|
82
|
+
i = invite.new(:event => e1)
|
83
|
+
i.event = e2
|
84
|
+
i.event_name.should == "bar"
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "multiple properties" do
|
88
|
+
|
89
|
+
it "should be derivable from the same source" do
|
90
|
+
invite = Class.new(RelaxDB::Document) do
|
91
|
+
property :name, :derived => [:event, lambda { |en, i| i.event.name }]
|
92
|
+
property :location, :derived => [:event, lambda { |en, i| i.event.location }]
|
93
|
+
belongs_to :event
|
94
|
+
end
|
95
|
+
|
96
|
+
event = Class.new(RelaxDB::Document) do
|
97
|
+
property :name
|
98
|
+
property :location
|
99
|
+
end
|
100
|
+
|
101
|
+
e = event.new(:name => "shindig", :location => "city17")
|
102
|
+
i = invite.new(:event => e)
|
103
|
+
i.name.should == "shindig"
|
104
|
+
i.location.should == "city17"
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
data/spec/document_spec.rb
CHANGED
@@ -131,10 +131,10 @@ describe RelaxDB::Document do
|
|
131
131
|
|
132
132
|
describe "user defined property writer" do
|
133
133
|
|
134
|
-
it "
|
134
|
+
it "may be used with caution" do
|
135
135
|
o = BespokeWriter.new(:val => 101).save
|
136
136
|
o = RelaxDB.load o._id
|
137
|
-
o.val.should ==
|
137
|
+
o.val.should == 91
|
138
138
|
end
|
139
139
|
|
140
140
|
end
|
@@ -376,13 +376,12 @@ describe RelaxDB::Document do
|
|
376
376
|
r.new(:thumbs_up => 3).save.should be_false
|
377
377
|
end
|
378
378
|
|
379
|
-
it "should
|
379
|
+
it "should pass the property value and object to the validator" do
|
380
380
|
r = Class.new(RelaxDB::Document) do
|
381
|
-
property :thumbs_up, :validator => lambda {
|
381
|
+
property :thumbs_up, :validator => lambda { |tu, o| tu >=0 && o.thumbs_up < 3 }
|
382
382
|
end
|
383
|
-
|
384
|
-
|
385
|
-
x.errors[:thumbs_up].should == "Too many thumbs"
|
383
|
+
r.new(:thumbs_up => 2).save.should be
|
384
|
+
r.new(:thumbs_up => 3).save.should be_false
|
386
385
|
end
|
387
386
|
|
388
387
|
it "should perform all validations" do
|
@@ -405,6 +404,15 @@ describe RelaxDB::Document do
|
|
405
404
|
x.save.should == false
|
406
405
|
end
|
407
406
|
|
407
|
+
it "should add a default error message if none is specified" do
|
408
|
+
r = Class.new(RelaxDB::Document) do
|
409
|
+
property :foo, :validator => lambda { raise }
|
410
|
+
end
|
411
|
+
x = r.new
|
412
|
+
x.save
|
413
|
+
x.errors[:foo].should_not be_blank
|
414
|
+
end
|
415
|
+
|
408
416
|
it "may be a proc" do
|
409
417
|
r = Class.new(RelaxDB::Document) do
|
410
418
|
property :thumbs_up, :validator => lambda { false }
|
@@ -422,6 +430,47 @@ describe RelaxDB::Document do
|
|
422
430
|
r.new(:thumbs_up => 1).save.should be
|
423
431
|
end
|
424
432
|
|
433
|
+
it "may be skipped by passing the property symbol to save" do
|
434
|
+
r = Class.new(RelaxDB::Document) do
|
435
|
+
property :thumbs_up, :validator => lambda { raise }
|
436
|
+
end
|
437
|
+
r.new.save!(:thumbs_up)
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
describe "validation message" do
|
443
|
+
|
444
|
+
it "should be set on failure" do
|
445
|
+
r = Class.new(RelaxDB::Document) do
|
446
|
+
property :thumbs_up, :validator => lambda { false }, :validation_msg => "Too many thumbs"
|
447
|
+
end
|
448
|
+
x = r.new
|
449
|
+
x.save
|
450
|
+
x.errors[:thumbs_up].should == "Too many thumbs"
|
451
|
+
end
|
452
|
+
|
453
|
+
it "may be a proc accepting the prop only" do
|
454
|
+
r = Class.new(RelaxDB::Document) do
|
455
|
+
property :thumbs_up, :validator => lambda { false },
|
456
|
+
:validation_msg => lambda { |tu| "#{tu}" }
|
457
|
+
end
|
458
|
+
x = r.new(:thumbs_up => 13)
|
459
|
+
x.save
|
460
|
+
x.errors[:thumbs_up].should == "13"
|
461
|
+
end
|
462
|
+
|
463
|
+
|
464
|
+
it "may be a proc accepting the prop and object" do
|
465
|
+
r = Class.new(RelaxDB::Document) do
|
466
|
+
property :thumbs_up, :validator => lambda { false },
|
467
|
+
:validation_msg => lambda { |tu, o| "#{tu} #{o.thumbs_up}" }
|
468
|
+
end
|
469
|
+
x = r.new(:thumbs_up => 13)
|
470
|
+
x.save
|
471
|
+
x.errors[:thumbs_up].should == "13 13"
|
472
|
+
end
|
473
|
+
|
425
474
|
end
|
426
475
|
|
427
476
|
describe "predefined validator" do
|
@@ -431,7 +480,7 @@ describe RelaxDB::Document do
|
|
431
480
|
property :foo, :validator => :required
|
432
481
|
def required; raise; end;
|
433
482
|
end
|
434
|
-
c.new(:foo => :bar).save
|
483
|
+
c.new(:foo => :bar).save!.should be
|
435
484
|
end
|
436
485
|
|
437
486
|
it "should prevent an object from being saved if validation fails" do
|
data/spec/spec_models.rb
CHANGED
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.
|
4
|
+
version: 0.2.5
|
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:
|
12
|
+
date: 2009-01-07 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -77,8 +77,8 @@ files:
|
|
77
77
|
- lib/relaxdb.rb
|
78
78
|
- spec/belongs_to_spec.rb
|
79
79
|
- spec/callbacks_spec.rb
|
80
|
-
- spec/denormalisation_spec.rb
|
81
80
|
- spec/design_doc_spec.rb
|
81
|
+
- spec/derived_properties_spec.rb
|
82
82
|
- spec/document_spec.rb
|
83
83
|
- spec/has_many_spec.rb
|
84
84
|
- spec/has_one_spec.rb
|
@@ -1,49 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
-
|
3
|
-
# Experimental only for now
|
4
|
-
|
5
|
-
class Tree < RelaxDB::Document
|
6
|
-
property :name
|
7
|
-
property :climate
|
8
|
-
has_one :leaf
|
9
|
-
end
|
10
|
-
|
11
|
-
class Leaf < RelaxDB::Document
|
12
|
-
belongs_to :tree, :denormalise => [:name]
|
13
|
-
end
|
14
|
-
|
15
|
-
describe RelaxDB::Document, "denormalisation" do
|
16
|
-
|
17
|
-
before(:all) do
|
18
|
-
RelaxDB.configure(:host => "localhost", :port => 5984)
|
19
|
-
end
|
20
|
-
|
21
|
-
before(:each) do
|
22
|
-
RelaxDB.delete_db "relaxdb_spec_db" rescue "ok"
|
23
|
-
RelaxDB.use_db "relaxdb_spec_db"
|
24
|
-
end
|
25
|
-
|
26
|
-
describe "belongs_to" do
|
27
|
-
|
28
|
-
it "should store denormalised options in its json representation" do
|
29
|
-
tree = Tree.new(:name => "sapling").save
|
30
|
-
leaf = Leaf.new(:tree => tree)
|
31
|
-
obj = JSON.parse(leaf.to_json)
|
32
|
-
obj["tree_name"].should == "sapling"
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should ignore denormalised options for nil properties" do
|
36
|
-
Leaf.new.to_json
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should not interfere with normal belongs_to behaviour" do
|
40
|
-
tree = Tree.new(:name => "sapling", :climate => "tropical").save
|
41
|
-
leaf = Leaf.new(:tree => tree).save
|
42
|
-
leaf = RelaxDB.load(leaf._id)
|
43
|
-
leaf.tree.name.should == "sapling"
|
44
|
-
leaf.tree.climate.should == "tropical"
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|