paulcarey-relaxdb 0.2.6 → 0.2.7
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.
- data/README.textile +16 -2
- data/Rakefile +1 -1
- data/lib/relaxdb/document.rb +101 -29
- data/lib/relaxdb/has_many_proxy.rb +17 -0
- data/lib/relaxdb/relaxdb.rb +43 -20
- data/lib/relaxdb/server.rb +15 -7
- data/spec/derived_properties_spec.rb +4 -8
- data/spec/document_spec.rb +60 -4
- data/spec/has_many_spec.rb +35 -6
- data/spec/relaxdb_spec.rb +56 -7
- data/spec/spec_models.rb +101 -90
- metadata +2 -2
data/README.textile
CHANGED
@@ -1,9 +1,23 @@
|
|
1
1
|
h3. What's New?
|
2
2
|
|
3
|
-
*
|
3
|
+
* Potentially breaking change. Skipping validations is now done by adding attribute symbols to an object's list rather than passing them to @save@. For example @my_obj.validation_skip_list << :foo@. This offers per object granularity over validations when working with bulk_save.
|
4
|
+
|
5
|
+
* Potentially breaking change. @load@ now returns an array if passed an array of size one. Previously it would have returned a single object.
|
6
|
+
|
7
|
+
* Update conflict hook and property
|
8
|
+
|
9
|
+
* Semantic consistency for bulk_save and bulk_save! wrt to save and save!
|
10
|
+
|
11
|
+
* Multiple exception handling improvements
|
12
|
+
|
13
|
+
* @save_all@ that issues a bulk_save for an object and its has_one and has_many children
|
14
|
+
|
15
|
+
* assignment of @has_many@ relationships
|
4
16
|
|
5
17
|
* Validations may be skipped by passing the attribute symbol(s) to @save@ or @save!@.
|
6
18
|
|
19
|
+
* Denormalisation via derived properties. Examples in spec/derived_properties_spec.rb.
|
20
|
+
|
7
21
|
* 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
22
|
|
9
23
|
* Semantic consistency for load, load!, save and save!. The bang versions raise an exception when their more relaxed siblings would simply return nil.
|
@@ -18,7 +32,7 @@ h3. What's New?
|
|
18
32
|
** For example, @ Numbers.all.sorted_by(:val) { |q| q.keys([1,2,3,5]) } @
|
19
33
|
* 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.
|
20
34
|
|
21
|
-
*Note*:
|
35
|
+
*Note*: Current versions require CouchDB 0.9 trunk. If you're working with CouchDB 0.8 or 0.8.1, please build from commit @ a8a2d496462 @.
|
22
36
|
|
23
37
|
h2. Overview
|
24
38
|
|
data/Rakefile
CHANGED
data/lib/relaxdb/document.rb
CHANGED
@@ -7,6 +7,9 @@ module RelaxDB
|
|
7
7
|
# Used to store validation messages
|
8
8
|
attr_accessor :errors
|
9
9
|
|
10
|
+
# Attribute symbols added to this list won't be validated on save
|
11
|
+
attr_accessor :validation_skip_list
|
12
|
+
|
10
13
|
# Define properties and property methods
|
11
14
|
|
12
15
|
def self.property(prop, opts={})
|
@@ -62,13 +65,13 @@ module RelaxDB
|
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
def self.create_validation_msg(
|
68
|
+
def self.create_validation_msg(att, validation_msg)
|
66
69
|
if validation_msg.is_a?(Proc)
|
67
70
|
validation_msg.arity == 1 ?
|
68
|
-
define_method("#{
|
69
|
-
define_method("#{
|
71
|
+
define_method("#{att}_validation_msg") { |att_val| validation_msg.call(att_val) } :
|
72
|
+
define_method("#{att}_validation_msg") { |att_val| validation_msg.call(att_val, self) }
|
70
73
|
else
|
71
|
-
define_method("#{
|
74
|
+
define_method("#{att}_validation_msg") { validation_msg }
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
@@ -84,12 +87,22 @@ module RelaxDB
|
|
84
87
|
@derived_prop_writers ||= {}
|
85
88
|
end
|
86
89
|
|
90
|
+
#
|
91
|
+
# The rationale for rescuing the send below is that the lambda for a derived
|
92
|
+
# property shouldn't need to concern itself with checking the validity of
|
93
|
+
# the underlying property. Nor, IMO, should clients be exposed to the
|
94
|
+
# possibility of a writer raising an exception.
|
95
|
+
#
|
87
96
|
def write_derived_props(source)
|
88
97
|
writers = self.class.derived_prop_writers[source]
|
89
98
|
if writers
|
90
99
|
writers.each do |prop, writer|
|
91
100
|
current_val = send(prop)
|
92
|
-
|
101
|
+
begin
|
102
|
+
send("#{prop}=", writer.call(current_val, self))
|
103
|
+
rescue => e
|
104
|
+
RelaxDB.logger.warn "Deriving #{prop} from #{source} raised #{e}"
|
105
|
+
end
|
93
106
|
end
|
94
107
|
end
|
95
108
|
end
|
@@ -110,6 +123,7 @@ module RelaxDB
|
|
110
123
|
self._id = UuidGenerator.uuid
|
111
124
|
|
112
125
|
@errors = Errors.new
|
126
|
+
@validation_skip_list = []
|
113
127
|
|
114
128
|
# Set default properties if this object has not known CouchDB
|
115
129
|
unless hash["_rev"]
|
@@ -201,31 +215,76 @@ module RelaxDB
|
|
201
215
|
data.to_json
|
202
216
|
end
|
203
217
|
|
204
|
-
# Order changed as of 30/10/2008 to be consistent with ActiveRecord
|
205
218
|
# Not yet sure of final implemention for hooks - may lean more towards DM than AR
|
206
|
-
def save
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
219
|
+
def save
|
220
|
+
if pre_save && save_to_couch
|
221
|
+
after_save
|
222
|
+
self
|
223
|
+
else
|
224
|
+
false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def save_to_couch
|
229
|
+
begin
|
230
|
+
resp = RelaxDB.db.put(_id, to_json)
|
231
|
+
self._rev = JSON.parse(resp.body)["rev"]
|
232
|
+
rescue HTTP_412
|
233
|
+
on_update_conflict
|
234
|
+
@update_conflict = true
|
235
|
+
return false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def on_update_conflict
|
240
|
+
# override with any behaviour you want to happen when
|
241
|
+
# CouchDB returns DocumentConflict on an attempt to save
|
242
|
+
end
|
243
|
+
|
244
|
+
def pre_save
|
245
|
+
return false unless validates?
|
246
|
+
return false unless before_save
|
247
|
+
set_created_at if new_document?
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
251
|
+
def post_save
|
215
252
|
after_save
|
253
|
+
end
|
254
|
+
|
255
|
+
def save!
|
256
|
+
if save
|
257
|
+
self
|
258
|
+
elsif update_conflict?
|
259
|
+
raise UpdateConflict, self
|
260
|
+
else
|
261
|
+
raise ValidationFailure, self.errors.to_json
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def save_all
|
266
|
+
RelaxDB.bulk_save(self, *all_children)
|
267
|
+
end
|
216
268
|
|
217
|
-
|
218
|
-
|
269
|
+
def save_all!
|
270
|
+
RelaxDB.bulk_save!(self, *all_children)
|
271
|
+
end
|
272
|
+
|
273
|
+
def all_children
|
274
|
+
ho = self.class.has_one_rels.map { |r| send(r) }
|
275
|
+
hm = self.class.has_many_rels.inject([]) { |m,r| m += send(r).children }
|
276
|
+
ho + hm
|
277
|
+
end
|
219
278
|
|
220
|
-
def
|
221
|
-
|
279
|
+
def update_conflict?
|
280
|
+
@update_conflict
|
222
281
|
end
|
223
282
|
|
224
|
-
def validates?
|
225
|
-
props = properties -
|
283
|
+
def validates?
|
284
|
+
props = properties - validation_skip_list
|
226
285
|
prop_vals = props.map { |prop| instance_variable_get("@#{prop}") }
|
227
286
|
|
228
|
-
rels = self.class.belongs_to_rels.keys -
|
287
|
+
rels = self.class.belongs_to_rels.keys - validation_skip_list
|
229
288
|
rel_vals = rels.map { |rel| instance_variable_get("@#{rel}_id") }
|
230
289
|
|
231
290
|
att_names = props + rels
|
@@ -246,7 +305,7 @@ module RelaxDB
|
|
246
305
|
begin
|
247
306
|
success = send("validate_#{att_name}", att_val)
|
248
307
|
rescue => e
|
249
|
-
RelaxDB.logger.warn
|
308
|
+
RelaxDB.logger.warn "Validating #{att_name} with #{att_val} raised #{e}"
|
250
309
|
succes = false
|
251
310
|
end
|
252
311
|
|
@@ -255,8 +314,8 @@ module RelaxDB
|
|
255
314
|
begin
|
256
315
|
@errors[att_name] = send("#{att_name}_validation_msg", att_val)
|
257
316
|
rescue => e
|
258
|
-
RelaxDB.logger.warn
|
259
|
-
@errors[att_name] = "validation_msg_exception:invalid:#{
|
317
|
+
RelaxDB.logger.warn "Validation_msg for #{att_name} with #{att_val} raised #{e}"
|
318
|
+
@errors[att_name] = "validation_msg_exception:invalid:#{att_val}"
|
260
319
|
end
|
261
320
|
else
|
262
321
|
@errors[att_name] = "invalid:#{att_val}"
|
@@ -322,7 +381,6 @@ module RelaxDB
|
|
322
381
|
end
|
323
382
|
|
324
383
|
def self.references_many_rels
|
325
|
-
# Don't force clients to check its instantiated
|
326
384
|
@references_many_rels ||= []
|
327
385
|
end
|
328
386
|
|
@@ -334,8 +392,10 @@ module RelaxDB
|
|
334
392
|
create_or_get_proxy(HasManyProxy, relationship, opts)
|
335
393
|
end
|
336
394
|
|
337
|
-
define_method("#{relationship}=") do
|
338
|
-
|
395
|
+
define_method("#{relationship}=") do |children|
|
396
|
+
create_or_get_proxy(HasManyProxy, relationship, opts).children = children
|
397
|
+
write_derived_props(relationship)
|
398
|
+
children
|
339
399
|
end
|
340
400
|
end
|
341
401
|
|
@@ -354,6 +414,8 @@ module RelaxDB
|
|
354
414
|
|
355
415
|
define_method("#{relationship}=") do |new_target|
|
356
416
|
create_or_get_proxy(HasOneProxy, relationship).target = new_target
|
417
|
+
write_derived_props(relationship)
|
418
|
+
new_target
|
357
419
|
end
|
358
420
|
end
|
359
421
|
|
@@ -378,6 +440,7 @@ module RelaxDB
|
|
378
440
|
define_method("#{relationship}_id=") do |id|
|
379
441
|
instance_variable_set("@#{relationship}_id".to_sym, id)
|
380
442
|
write_derived_props(relationship)
|
443
|
+
id
|
381
444
|
end
|
382
445
|
|
383
446
|
# Allows belongs_to relationships to be used by the paginator
|
@@ -386,10 +449,16 @@ module RelaxDB
|
|
386
449
|
end
|
387
450
|
|
388
451
|
create_validator(relationship, opts[:validator]) if opts[:validator]
|
452
|
+
|
453
|
+
# Untested below
|
454
|
+
create_validation_msg(relationship, opts[:validation_msg]) if opts[:validation_msg]
|
455
|
+
end
|
456
|
+
|
457
|
+
class << self
|
458
|
+
alias_method :references, :belongs_to
|
389
459
|
end
|
390
460
|
|
391
461
|
def self.belongs_to_rels
|
392
|
-
# Don't force clients to check that it's instantiated
|
393
462
|
@belongs_to_rels ||= {}
|
394
463
|
end
|
395
464
|
|
@@ -404,6 +473,9 @@ module RelaxDB
|
|
404
473
|
# destroy! nullifies all relationships with peers and children before deleting
|
405
474
|
# itself in CouchDB
|
406
475
|
# The nullification and deletion are not performed in a transaction
|
476
|
+
#
|
477
|
+
# TODO: Current implemention may be inappropriate - causing CouchDB to try to JSON
|
478
|
+
# encode undefined. Ensure nil is serialized? See has_many_spec#should nullify its child relationships
|
407
479
|
def destroy!
|
408
480
|
self.class.references_many_rels.each do |rel|
|
409
481
|
send(rel).clear
|
@@ -3,6 +3,8 @@ module RelaxDB
|
|
3
3
|
class HasManyProxy
|
4
4
|
|
5
5
|
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :children
|
6
8
|
|
7
9
|
def initialize(client, relationship, opts)
|
8
10
|
@client = client
|
@@ -57,6 +59,14 @@ module RelaxDB
|
|
57
59
|
def [](*args)
|
58
60
|
@children[*args]
|
59
61
|
end
|
62
|
+
|
63
|
+
def first
|
64
|
+
@children[0]
|
65
|
+
end
|
66
|
+
|
67
|
+
def last
|
68
|
+
@children[size-1]
|
69
|
+
end
|
60
70
|
|
61
71
|
def each(&blk)
|
62
72
|
@children.each(&blk)
|
@@ -73,6 +83,13 @@ module RelaxDB
|
|
73
83
|
map_function = ViewCreator.has_n(@target_class, @relationship_as_viewed_by_target)
|
74
84
|
@children = RelaxDB.retrieve(view_path, design_doc, view_name, map_function)
|
75
85
|
end
|
86
|
+
|
87
|
+
def children=(children)
|
88
|
+
children.each do |obj|
|
89
|
+
obj.send("#{@relationship_as_viewed_by_target}=".to_sym, @client)
|
90
|
+
end
|
91
|
+
@children = children
|
92
|
+
end
|
76
93
|
|
77
94
|
def inspect
|
78
95
|
@children.inspect
|
data/lib/relaxdb/relaxdb.rb
CHANGED
@@ -2,6 +2,8 @@ module RelaxDB
|
|
2
2
|
|
3
3
|
class NotFound < StandardError; end
|
4
4
|
class DocumentNotSaved < StandardError; end
|
5
|
+
class UpdateConflict < DocumentNotSaved; end
|
6
|
+
class ValidationFailure < DocumentNotSaved; end
|
5
7
|
|
6
8
|
@@db = nil
|
7
9
|
|
@@ -21,11 +23,15 @@ module RelaxDB
|
|
21
23
|
|
22
24
|
# Creates the named database if it doesn't already exist
|
23
25
|
def use_db(name)
|
24
|
-
db.use_db
|
26
|
+
db.use_db name
|
27
|
+
end
|
28
|
+
|
29
|
+
def db_exists?(name)
|
30
|
+
db.db_exists? name
|
25
31
|
end
|
26
32
|
|
27
33
|
def delete_db(name)
|
28
|
-
db.delete_db
|
34
|
+
db.delete_db name
|
29
35
|
end
|
30
36
|
|
31
37
|
def list_dbs
|
@@ -36,41 +42,58 @@ module RelaxDB
|
|
36
42
|
db.replicate_db source, target
|
37
43
|
end
|
38
44
|
|
39
|
-
def bulk_save(*objs)
|
45
|
+
def bulk_save!(*objs)
|
46
|
+
pre_save_success = objs.inject(true) { |s, o| s &= o.pre_save }
|
47
|
+
raise ValidationFailure, objs unless pre_save_success
|
48
|
+
|
40
49
|
docs = {}
|
41
50
|
objs.each { |o| docs[o._id] = o }
|
51
|
+
|
52
|
+
begin
|
53
|
+
resp = db.post("_bulk_docs", { "docs" => objs }.to_json )
|
54
|
+
data = JSON.parse(resp.body)
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
56
|
+
data["new_revs"].each do |new_rev|
|
57
|
+
obj = docs[ new_rev["id"] ]
|
58
|
+
obj._rev = new_rev["rev"]
|
59
|
+
obj.post_save
|
60
|
+
end
|
61
|
+
rescue HTTP_412
|
62
|
+
raise UpdateConflict, objs
|
48
63
|
end
|
49
64
|
|
50
|
-
|
65
|
+
objs
|
66
|
+
end
|
67
|
+
|
68
|
+
def bulk_save(*objs)
|
69
|
+
begin
|
70
|
+
bulk_save!(*objs)
|
71
|
+
rescue ValidationFailure, UpdateConflict
|
72
|
+
false
|
73
|
+
end
|
51
74
|
end
|
52
75
|
|
53
|
-
def load(
|
54
|
-
if ids.
|
76
|
+
def load(ids)
|
77
|
+
if ids.is_a? Array
|
78
|
+
resp = db.post("_all_docs?include_docs=true", {:keys => ids}.to_json)
|
79
|
+
data = JSON.parse(resp.body)
|
80
|
+
data["rows"].map { |row| row["doc"] ? create_object(row["doc"]) : nil }
|
81
|
+
else
|
55
82
|
begin
|
56
|
-
resp = db.get(ids
|
83
|
+
resp = db.get(ids)
|
57
84
|
data = JSON.parse(resp.body)
|
58
85
|
create_object(data)
|
59
86
|
rescue HTTP_404
|
60
87
|
nil
|
61
88
|
end
|
62
|
-
else
|
63
|
-
resp = db.post("_all_docs?include_docs=true", {:keys => ids}.to_json)
|
64
|
-
data = JSON.parse(resp.body)
|
65
|
-
data["rows"].map { |row| row["doc"] ? create_object(row["doc"]) : nil }
|
66
89
|
end
|
67
90
|
end
|
68
91
|
|
69
|
-
def load!(
|
70
|
-
res = load(
|
92
|
+
def load!(ids)
|
93
|
+
res = load(ids)
|
71
94
|
|
72
|
-
raise NotFound if res == nil
|
73
|
-
raise NotFound if res.respond_to?(:include?) && res.include?(nil)
|
95
|
+
raise NotFound, ids if res == nil
|
96
|
+
raise NotFound, ids if res.respond_to?(:include?) && res.include?(nil)
|
74
97
|
|
75
98
|
res
|
76
99
|
end
|
data/lib/relaxdb/server.rb
CHANGED
@@ -2,6 +2,7 @@ module RelaxDB
|
|
2
2
|
|
3
3
|
class HTTP_404 < StandardError; end
|
4
4
|
class HTTP_409 < StandardError; end
|
5
|
+
class HTTP_412 < StandardError; end
|
5
6
|
|
6
7
|
class Server
|
7
8
|
|
@@ -62,13 +63,15 @@ module RelaxDB
|
|
62
63
|
end
|
63
64
|
|
64
65
|
class CouchDB
|
66
|
+
|
67
|
+
attr_reader :logger
|
65
68
|
|
66
69
|
# Used for test instrumentation only i.e. to assert that
|
67
|
-
# an expected number of
|
68
|
-
attr_accessor :get_count
|
70
|
+
# an expected number of requests have been issued
|
71
|
+
attr_accessor :get_count, :put_count
|
69
72
|
|
70
73
|
def initialize(config)
|
71
|
-
@get_count = 0
|
74
|
+
@get_count, @put_count = 0, 0
|
72
75
|
@server = RelaxDB::Server.new(config[:host], config[:port])
|
73
76
|
@logger = config[:logger] ? config[:logger] : Logger.new(Tempfile.new('couchdb.log'))
|
74
77
|
end
|
@@ -78,6 +81,10 @@ module RelaxDB
|
|
78
81
|
@db = name
|
79
82
|
end
|
80
83
|
|
84
|
+
def db_exists?(name)
|
85
|
+
@server.get("/#{name}") rescue false
|
86
|
+
end
|
87
|
+
|
81
88
|
def delete_db(name)
|
82
89
|
@logger.info("Deleting database #{name}")
|
83
90
|
@server.delete("/#{name}")
|
@@ -99,7 +106,7 @@ module RelaxDB
|
|
99
106
|
@server.delete("/#{@db}/#{path}")
|
100
107
|
end
|
101
108
|
|
102
|
-
# *ignored allows methods to invoke get or post indifferently
|
109
|
+
# *ignored allows methods to invoke get or post indifferently via send
|
103
110
|
def get(path=nil, *ignored)
|
104
111
|
@get_count += 1
|
105
112
|
@logger.info("GET /#{@db}/#{unesc(path)}")
|
@@ -112,6 +119,7 @@ module RelaxDB
|
|
112
119
|
end
|
113
120
|
|
114
121
|
def put(path=nil, json=nil)
|
122
|
+
@put_count += 1
|
115
123
|
@logger.info("PUT /#{@db}/#{unesc(path)} #{json}")
|
116
124
|
@server.put("/#{@db}/#{path}", json)
|
117
125
|
end
|
@@ -129,10 +137,10 @@ module RelaxDB
|
|
129
137
|
@db
|
130
138
|
end
|
131
139
|
|
132
|
-
def
|
133
|
-
@
|
140
|
+
def name=(name)
|
141
|
+
@db = name
|
134
142
|
end
|
135
|
-
|
143
|
+
|
136
144
|
private
|
137
145
|
|
138
146
|
def create_db_if_non_existant(name)
|
@@ -1,9 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
2
|
require File.dirname(__FILE__) + '/spec_models.rb'
|
3
3
|
|
4
|
-
# These tests would ideally instrument server.rb, asserting that no
|
5
|
-
# HTTP requests are made when retrieving the derived values
|
6
|
-
|
7
4
|
class DpInvite < RelaxDB::Document
|
8
5
|
property :event_name, :derived => [:event, lambda { |en, i| i.event.name }]
|
9
6
|
belongs_to :event
|
@@ -46,12 +43,11 @@ describe RelaxDB::Document, "derived properties" do
|
|
46
43
|
i.event_name.should == "shindig"
|
47
44
|
end
|
48
45
|
|
49
|
-
it "will
|
50
|
-
#
|
51
|
-
|
52
|
-
lambda { DpInvite.new(:event_id => e._id) }.should raise_error
|
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!
|
53
49
|
end
|
54
|
-
|
50
|
+
|
55
51
|
it "should only be updated for registered properties" do
|
56
52
|
invite = Class.new(RelaxDB::Document) do
|
57
53
|
property :event_name, :derived => [:foo, lambda { |en, i| i.event.name }]
|
data/spec/document_spec.rb
CHANGED
@@ -36,6 +36,10 @@ describe RelaxDB::Document do
|
|
36
36
|
Post.new(:foo => "").save
|
37
37
|
end
|
38
38
|
|
39
|
+
it "should create a document with a non conflicing state" do
|
40
|
+
Atom.new.should_not be_update_conflict
|
41
|
+
end
|
42
|
+
|
39
43
|
end
|
40
44
|
|
41
45
|
describe "#initialize" do
|
@@ -92,6 +96,14 @@ describe RelaxDB::Document do
|
|
92
96
|
p = Post.new(:created_at => back_then).save
|
93
97
|
p.created_at.should be_close(back_then, 1)
|
94
98
|
end
|
99
|
+
|
100
|
+
it "should set document conflict state on conflicting save" do
|
101
|
+
a1 = Atom.new
|
102
|
+
a2 = a1.dup
|
103
|
+
a1.save!
|
104
|
+
a2.save
|
105
|
+
a2.should be_update_conflict
|
106
|
+
end
|
95
107
|
|
96
108
|
end
|
97
109
|
|
@@ -102,15 +114,57 @@ describe RelaxDB::Document do
|
|
102
114
|
RelaxDB.load(a._id).should == a
|
103
115
|
end
|
104
116
|
|
105
|
-
it "should
|
117
|
+
it "should raise ValidationFailure on validation failure" do
|
106
118
|
r = Class.new(RelaxDB::Document) do
|
107
119
|
property :thumbs_up, :validator => lambda { false }
|
108
120
|
end
|
109
121
|
lambda do
|
110
122
|
r.new.save!
|
111
|
-
end.should raise_error(RelaxDB::
|
123
|
+
end.should raise_error(RelaxDB::ValidationFailure)
|
112
124
|
end
|
113
125
|
|
126
|
+
it "should raise UpdateConflict on an update conflict" do
|
127
|
+
a1 = Atom.new
|
128
|
+
a2 = a1.dup
|
129
|
+
a1.save!
|
130
|
+
lambda { a2.save! }.should raise_error(RelaxDB::UpdateConflict)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#save_all" do
|
136
|
+
|
137
|
+
before(:each) do
|
138
|
+
# Create the underlying views
|
139
|
+
User.new(:items => [], :invites_received => [], :invites_sent => [])
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should issue only a single PUT request" do
|
143
|
+
RelaxDB.db.put_count = 0
|
144
|
+
RelaxDB.db.get_count = 0
|
145
|
+
|
146
|
+
i1, i2 = Item.new(:name => "i1"), Item.new(:name => "i2")
|
147
|
+
u = User.new(:items => [i1, i2])
|
148
|
+
u.save_all!
|
149
|
+
|
150
|
+
RelaxDB.db.put_count.should == 0
|
151
|
+
RelaxDB.db.get_count.should == 3
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "#all_children" do
|
157
|
+
|
158
|
+
it "should return an array containing all children" do
|
159
|
+
r = Rating.new
|
160
|
+
p = Photo.new(:rating => r)
|
161
|
+
t = Tag.new
|
162
|
+
t1, t2 = Tagging.new(:photo => p, :tag => t), Tagging.new(:photo => p, :tag => t)
|
163
|
+
p.taggings = [t1, t2]
|
164
|
+
p.all_children.size.should == 3
|
165
|
+
[r, t1, t2].each { |c| p.all_children.should include(c) }
|
166
|
+
end
|
167
|
+
|
114
168
|
end
|
115
169
|
|
116
170
|
describe "user defined property reader" do
|
@@ -171,7 +225,7 @@ describe RelaxDB::Document do
|
|
171
225
|
|
172
226
|
it "should prevent the object from being resaved" do
|
173
227
|
p = Atom.new.save.destroy!
|
174
|
-
lambda { p.save }.should raise_error
|
228
|
+
lambda { p.save! }.should raise_error
|
175
229
|
end
|
176
230
|
|
177
231
|
it "will result in undefined behaviour when invoked on unsaved objects" do
|
@@ -434,7 +488,9 @@ describe RelaxDB::Document do
|
|
434
488
|
r = Class.new(RelaxDB::Document) do
|
435
489
|
property :thumbs_up, :validator => lambda { raise }
|
436
490
|
end
|
437
|
-
r.new
|
491
|
+
x = r.new
|
492
|
+
x.validation_skip_list << :thumbs_up
|
493
|
+
x.save!
|
438
494
|
end
|
439
495
|
|
440
496
|
end
|
data/spec/has_many_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe RelaxDB::HasManyProxy do
|
|
16
16
|
|
17
17
|
it "should be considered enumerable" do
|
18
18
|
u = User.new.save
|
19
|
-
u.items.should be_a_kind_of(
|
19
|
+
u.items.should be_a_kind_of(Enumerable)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should actually be enumerable" do
|
@@ -41,7 +41,7 @@ describe RelaxDB::HasManyProxy do
|
|
41
41
|
m = RelaxDB.load m._id
|
42
42
|
m.multi_word_children[0].should == c
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
describe "#<<" do
|
46
46
|
|
47
47
|
it "should link the added item to the parent" do
|
@@ -74,10 +74,39 @@ describe RelaxDB::HasManyProxy do
|
|
74
74
|
end
|
75
75
|
|
76
76
|
describe "#=" do
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
|
77
|
+
|
78
|
+
before(:each) do
|
79
|
+
# Create the underlying views
|
80
|
+
User.new(:items => [], :invites_received => [], :invites_sent => [])
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should not attempt to save the child objects when the relationship is established" do
|
84
|
+
RelaxDB.db.put_count = 0
|
85
|
+
i1, i2 = Item.new(:name => "i1"), Item.new(:name => "i2")
|
86
|
+
User.new(:items => [i1, i2])
|
87
|
+
RelaxDB.db.put_count.should == 0
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should preserve given relationships across save/load boundary" do
|
91
|
+
i1, i2 = Item.new(:name => "i1"), Item.new(:name => "i2")
|
92
|
+
u = User.new(:items => [i1, i2])
|
93
|
+
u.save_all!
|
94
|
+
u = RelaxDB.load u._id
|
95
|
+
u.items.map { |i| i.name }.sort.join.should == "i1i2"
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should invoke the derived properties writer" do
|
99
|
+
P = Class.new(RelaxDB::Document) do
|
100
|
+
property :foo, :derived => [:zongs, lambda {|f, o| o.zongs.first.z / 2 }]
|
101
|
+
has_many :zongs, :class => "Zong"
|
102
|
+
end
|
103
|
+
Zong = Class.new(RelaxDB::Document) do
|
104
|
+
property :z
|
105
|
+
belongs_to :p
|
106
|
+
end
|
107
|
+
oz = Zong.new(:z => 10)
|
108
|
+
op = P.new(:zongs => [oz])
|
109
|
+
op.foo.should == 5
|
81
110
|
end
|
82
111
|
|
83
112
|
end
|
data/spec/relaxdb_spec.rb
CHANGED
@@ -28,20 +28,69 @@ describe RelaxDB do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
|
+
# bulk_save and bulk_save! should match Document#save and Document#save! semantics
|
32
33
|
describe ".bulk_save" do
|
33
34
|
|
34
35
|
it "should be invokable multiple times" do
|
35
|
-
t1 = Tag.new
|
36
|
-
t2 = Tag.new(:name => "t2")
|
36
|
+
t1, t2 = Tag.new, Tag.new
|
37
37
|
RelaxDB.bulk_save(t1, t2)
|
38
38
|
RelaxDB.bulk_save(t1, t2)
|
39
39
|
end
|
40
40
|
|
41
|
+
it "should return the objects it was passed" do
|
42
|
+
t1, t2 = Tag.new, Tag.new
|
43
|
+
ta, tb = RelaxDB.bulk_save(t1, t2)
|
44
|
+
ta.should == t1
|
45
|
+
tb.should == t2
|
46
|
+
end
|
47
|
+
|
41
48
|
it "should succeed when passed no args" do
|
42
49
|
RelaxDB.bulk_save
|
43
50
|
end
|
44
51
|
|
52
|
+
it "should return false on failure" do
|
53
|
+
c = Class.new(RelaxDB::Document) do
|
54
|
+
property :foo, :validator => lambda { false }
|
55
|
+
end
|
56
|
+
x = c.new
|
57
|
+
RelaxDB.bulk_save(x).should be_false
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not attempt to save if a pre-save stage fails" do
|
61
|
+
c = Class.new(RelaxDB::Document) do
|
62
|
+
property :foo, :validator => lambda { false }
|
63
|
+
end
|
64
|
+
x = c.new
|
65
|
+
RelaxDB.bulk_save(x)
|
66
|
+
x.should be_new_document
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should invoke the after-save stage after a successful save" do
|
70
|
+
c = Class.new(RelaxDB::Document) do
|
71
|
+
attr_accessor :foo
|
72
|
+
after_save lambda { |c| c.foo = :bar }
|
73
|
+
end
|
74
|
+
x = c.new
|
75
|
+
RelaxDB.bulk_save(x).first.foo.should == :bar
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".bulk_save!" do
|
81
|
+
|
82
|
+
it "should raise an exception if a obj fails validation" do
|
83
|
+
c = Class.new(RelaxDB::Document) do
|
84
|
+
property :foo, :validator => lambda { false }
|
85
|
+
end
|
86
|
+
lambda { RelaxDB.bulk_save!(c.new) }.should raise_error(RelaxDB::ValidationFailure)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should raise an exception if a document update conflict occurs on save" do
|
90
|
+
Atom.new(:_id => "a1").save!
|
91
|
+
lambda { RelaxDB.bulk_save! Atom.new(:_id => "a1") }.should raise_error(RelaxDB::UpdateConflict)
|
92
|
+
end
|
93
|
+
|
45
94
|
end
|
46
95
|
|
47
96
|
describe ".replicate_db" do
|
@@ -68,7 +117,7 @@ describe RelaxDB do
|
|
68
117
|
|
69
118
|
it "should load an arbitrary number of documents" do
|
70
119
|
a1, a2 = Atom.new.save, Atom.new.save
|
71
|
-
ar1, ar2 = RelaxDB.load a1._id, a2._id
|
120
|
+
ar1, ar2 = RelaxDB.load [a1._id, a2._id]
|
72
121
|
ar1.should == a1
|
73
122
|
ar2.should == a2
|
74
123
|
end
|
@@ -79,7 +128,7 @@ describe RelaxDB do
|
|
79
128
|
|
80
129
|
it "should return an array with correctly placed nils when given a list containing non existant doc ids" do
|
81
130
|
a1, a2 = Atom.new.save, Atom.new.save
|
82
|
-
res = RelaxDB.load nil, a1._id, nil, a2._id, nil
|
131
|
+
res = RelaxDB.load [nil, a1._id, nil, a2._id, nil]
|
83
132
|
res.should == [nil, a1, nil, a2, nil]
|
84
133
|
end
|
85
134
|
|
@@ -95,7 +144,7 @@ describe RelaxDB do
|
|
95
144
|
|
96
145
|
it "should load multiple documents" do
|
97
146
|
a1, a2 = Atom.new.save, Atom.new.save
|
98
|
-
ar1, ar2 = RelaxDB.load! a1._id, a2._id
|
147
|
+
ar1, ar2 = RelaxDB.load! [a1._id, a2._id]
|
99
148
|
ar1.should == a1
|
100
149
|
ar2.should == a2
|
101
150
|
end
|
@@ -109,7 +158,7 @@ describe RelaxDB do
|
|
109
158
|
it "should throw an exception if any of a list of doc ids is for a non-existant doc" do
|
110
159
|
a = Atom.new.save
|
111
160
|
lambda do
|
112
|
-
RelaxDB.load! nil, a._id
|
161
|
+
RelaxDB.load! [nil, a._id]
|
113
162
|
end.should raise_error(RelaxDB::NotFound)
|
114
163
|
end
|
115
164
|
|
data/spec/spec_models.rb
CHANGED
@@ -1,139 +1,150 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
#
|
2
|
+
# RSpec loads this file multiple times, thus breaking invocations like Document.has_one_rels
|
3
|
+
# The following clause ensures the tests pass, but perhaps Document should raise a warning
|
4
|
+
# if it's loaded more than once...
|
5
|
+
#
|
6
|
+
unless @spec_models_loaded
|
7
|
+
|
8
|
+
class Atom < RelaxDB::Document
|
9
|
+
end
|
3
10
|
|
4
|
-
class Initiative < RelaxDB::Document
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
class Initiative < RelaxDB::Document
|
12
|
+
property :x
|
13
|
+
attr_reader :foo
|
14
|
+
def initialize(*data)
|
15
|
+
super *data
|
16
|
+
@foo = :bar
|
17
|
+
end
|
10
18
|
end
|
11
|
-
end
|
12
19
|
|
13
|
-
class Primitives < RelaxDB::Document
|
20
|
+
class Primitives < RelaxDB::Document
|
14
21
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
22
|
+
property :str
|
23
|
+
property :num
|
24
|
+
property :true_bool
|
25
|
+
property :false_bool
|
26
|
+
property :created_at
|
27
|
+
property :empty
|
21
28
|
|
22
|
-
end
|
29
|
+
end
|
23
30
|
|
24
|
-
class BespokeReader < RelaxDB::Document
|
25
|
-
|
26
|
-
|
27
|
-
end
|
31
|
+
class BespokeReader < RelaxDB::Document
|
32
|
+
property :val
|
33
|
+
def val; @val + 5; end
|
34
|
+
end
|
28
35
|
|
29
|
-
class BespokeWriter < RelaxDB::Document
|
30
|
-
|
31
|
-
|
32
|
-
end
|
36
|
+
class BespokeWriter < RelaxDB::Document
|
37
|
+
property :val
|
38
|
+
def val=(v); @val = v - 10; end
|
39
|
+
end
|
33
40
|
|
34
|
-
class Letter < RelaxDB::Document
|
41
|
+
class Letter < RelaxDB::Document
|
35
42
|
|
36
|
-
|
37
|
-
|
43
|
+
property :letter
|
44
|
+
property :number
|
38
45
|
|
39
|
-
end
|
46
|
+
end
|
40
47
|
|
41
|
-
class Invite < RelaxDB::Document
|
48
|
+
class Invite < RelaxDB::Document
|
42
49
|
|
43
|
-
|
50
|
+
property :message
|
44
51
|
|
45
|
-
|
46
|
-
|
52
|
+
belongs_to :sender
|
53
|
+
belongs_to :recipient
|
47
54
|
|
48
|
-
end
|
55
|
+
end
|
49
56
|
|
50
|
-
class Item < RelaxDB::Document
|
57
|
+
class Item < RelaxDB::Document
|
51
58
|
|
52
|
-
|
53
|
-
|
59
|
+
property :name
|
60
|
+
belongs_to :user
|
54
61
|
|
55
|
-
end
|
62
|
+
end
|
56
63
|
|
57
|
-
class User < RelaxDB::Document
|
64
|
+
class User < RelaxDB::Document
|
58
65
|
|
59
|
-
|
60
|
-
|
66
|
+
property :name, :default => "u"
|
67
|
+
property :age
|
61
68
|
|
62
|
-
|
69
|
+
has_many :items, :class => "Item"
|
63
70
|
|
64
|
-
|
65
|
-
|
71
|
+
has_many :invites_received, :class => "Invite", :known_as => :recipient
|
72
|
+
has_many :invites_sent, :class => "Invite", :known_as => :sender
|
66
73
|
|
67
|
-
end
|
74
|
+
end
|
68
75
|
|
69
|
-
class Post < RelaxDB::Document
|
76
|
+
class Post < RelaxDB::Document
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
78
|
+
property :subject
|
79
|
+
property :content
|
80
|
+
property :created_at
|
81
|
+
property :viewed_at
|
75
82
|
|
76
|
-
end
|
83
|
+
end
|
77
84
|
|
78
|
-
class Rating < RelaxDB::Document
|
85
|
+
class Rating < RelaxDB::Document
|
79
86
|
|
80
|
-
|
81
|
-
|
87
|
+
property :stars, :default => 5
|
88
|
+
belongs_to :photo
|
82
89
|
|
83
|
-
end
|
90
|
+
end
|
84
91
|
|
85
|
-
class Photo < RelaxDB::Document
|
92
|
+
class Photo < RelaxDB::Document
|
86
93
|
|
87
|
-
|
94
|
+
property :name
|
88
95
|
|
89
|
-
|
96
|
+
has_one :rating
|
90
97
|
|
91
|
-
|
98
|
+
references_many :tags, :class => "Tag", :known_as => :photos
|
92
99
|
|
93
|
-
|
100
|
+
has_many :taggings, :class => "Tagging"
|
94
101
|
|
95
|
-
end
|
102
|
+
end
|
96
103
|
|
97
|
-
class Tag < RelaxDB::Document
|
104
|
+
class Tag < RelaxDB::Document
|
98
105
|
|
99
|
-
|
100
|
-
|
106
|
+
property :name
|
107
|
+
references_many :photos, :class => "Photo", :known_as => :tags
|
101
108
|
|
102
|
-
|
109
|
+
has_many :taggings, :class => "Tagging"
|
103
110
|
|
104
|
-
end
|
111
|
+
end
|
105
112
|
|
106
|
-
class Tagging < RelaxDB::Document
|
113
|
+
class Tagging < RelaxDB::Document
|
107
114
|
|
108
|
-
|
109
|
-
|
110
|
-
|
115
|
+
belongs_to :photo
|
116
|
+
belongs_to :tag
|
117
|
+
property :relevance
|
111
118
|
|
112
|
-
end
|
119
|
+
end
|
113
120
|
|
114
|
-
class MultiWordClass < RelaxDB::Document
|
115
|
-
|
116
|
-
|
117
|
-
end
|
121
|
+
class MultiWordClass < RelaxDB::Document
|
122
|
+
has_one :multi_word_child
|
123
|
+
has_many :multi_word_children, :class => "MultiWordChild"
|
124
|
+
end
|
118
125
|
|
119
|
-
class MultiWordChild < RelaxDB::Document
|
120
|
-
|
121
|
-
end
|
126
|
+
class MultiWordChild < RelaxDB::Document
|
127
|
+
belongs_to :multi_word_class
|
128
|
+
end
|
122
129
|
|
123
|
-
class TwitterUser < RelaxDB::Document
|
130
|
+
class TwitterUser < RelaxDB::Document
|
124
131
|
|
125
|
-
|
126
|
-
|
127
|
-
|
132
|
+
property :name
|
133
|
+
references_many :followers, :class => "User", :known_as => :leaders
|
134
|
+
references_many :leaders, :class => "User", :known_as => :followers
|
128
135
|
|
129
|
-
end
|
136
|
+
end
|
130
137
|
|
131
|
-
class Dysfunctional < RelaxDB::Document
|
132
|
-
|
133
|
-
|
134
|
-
end
|
138
|
+
class Dysfunctional < RelaxDB::Document
|
139
|
+
has_one :failure
|
140
|
+
has_many :failures, :class => "Failure"
|
141
|
+
end
|
142
|
+
|
143
|
+
class Failure < RelaxDB::Document
|
144
|
+
property :pathological, :validator => lambda { false }
|
145
|
+
belongs_to :dysfunctional
|
146
|
+
end
|
147
|
+
|
148
|
+
@spec_models_loaded = true
|
135
149
|
|
136
|
-
class Failure < RelaxDB::Document
|
137
|
-
property :pathological, :validator => lambda { false }
|
138
|
-
belongs_to :dysfunctional
|
139
150
|
end
|
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.7
|
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: 2009-01-
|
12
|
+
date: 2009-01-18 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|