yaanno-relaxdb 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +164 -0
- data/Rakefile +52 -0
- data/docs/spec_results.html +604 -0
- data/lib/more/grapher.rb +48 -0
- data/lib/relaxdb.rb +38 -0
- data/lib/relaxdb/all_delegator.rb +48 -0
- data/lib/relaxdb/belongs_to_proxy.rb +29 -0
- data/lib/relaxdb/design_doc.rb +50 -0
- data/lib/relaxdb/document.rb +377 -0
- data/lib/relaxdb/extlib.rb +3 -0
- data/lib/relaxdb/has_many_proxy.rb +81 -0
- data/lib/relaxdb/has_one_proxy.rb +45 -0
- data/lib/relaxdb/paginate_params.rb +54 -0
- data/lib/relaxdb/paginator.rb +78 -0
- data/lib/relaxdb/query.rb +74 -0
- data/lib/relaxdb/references_many_proxy.rb +99 -0
- data/lib/relaxdb/relaxdb.rb +151 -0
- data/lib/relaxdb/server.rb +132 -0
- data/lib/relaxdb/sorted_by_view.rb +62 -0
- data/lib/relaxdb/uuid_generator.rb +21 -0
- data/lib/relaxdb/view_object.rb +34 -0
- data/lib/relaxdb/view_uploader.rb +47 -0
- data/lib/relaxdb/views.rb +42 -0
- data/spec/belongs_to_spec.rb +80 -0
- data/spec/callbacks_spec.rb +64 -0
- data/spec/denormalisation_spec.rb +49 -0
- data/spec/design_doc_spec.rb +34 -0
- data/spec/document_spec.rb +355 -0
- data/spec/has_many_spec.rb +147 -0
- data/spec/has_one_spec.rb +128 -0
- data/spec/query_spec.rb +80 -0
- data/spec/references_many_spec.rb +141 -0
- data/spec/relaxdb_spec.rb +120 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/spec_models.rb +130 -0
- data/spec/view_object_spec.rb +47 -0
- metadata +117 -0
data/lib/more/grapher.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
#
|
4
|
+
# The GraphCreator uses dot to create a graphical model of an entire CouchDB database
|
5
|
+
# It probably only makes sense to run it on a database of a limited size
|
6
|
+
# The created graphs can be very useful for exploring relationships
|
7
|
+
# Run ruby scratch/grapher_demo.rb for an example
|
8
|
+
#
|
9
|
+
class GraphCreator
|
10
|
+
|
11
|
+
def self.create
|
12
|
+
system "mkdir -p graphs"
|
13
|
+
|
14
|
+
data = JSON.parse(RelaxDB.db.get("_all_docs").body)
|
15
|
+
all_ids = data["rows"].map { |r| r["id"] }
|
16
|
+
all_ids = all_ids.reject { |id| id =~ /_/ }
|
17
|
+
|
18
|
+
dot = "digraph G { \nrankdir=LR;\nnode [shape=record];\n"
|
19
|
+
all_ids.each do |id|
|
20
|
+
doc = RelaxDB.load(id)
|
21
|
+
atts = "#{doc.class}\\l|"
|
22
|
+
doc.properties.each do |prop|
|
23
|
+
# we don't care about the revision
|
24
|
+
next if prop == :_rev
|
25
|
+
|
26
|
+
prop_val = doc.instance_variable_get("@#{prop}".to_sym)
|
27
|
+
atts << "#{prop}\\l#{prop_val}|" if prop_val
|
28
|
+
end
|
29
|
+
atts = atts[0, atts.length-1]
|
30
|
+
|
31
|
+
dot << %Q%#{doc._id} [ label ="#{atts}"];\n%
|
32
|
+
|
33
|
+
doc.class.belongs_to_rels.each do |relationship, opts|
|
34
|
+
id = doc.instance_variable_get("@#{relationship}_id".to_sym)
|
35
|
+
dot << %Q%#{id} -> #{doc._id} [ label = "#{relationship}"];\n% if id
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
dot << "}"
|
40
|
+
|
41
|
+
File.open("graphs/data.dot", "w") { |f| f.write(dot) }
|
42
|
+
|
43
|
+
system "dot -Tpng -o graphs/all_docs.png graphs/data.dot"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/relaxdb.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'extlib'
|
3
|
+
require 'json'
|
4
|
+
require 'uuid'
|
5
|
+
|
6
|
+
require 'cgi'
|
7
|
+
require 'net/http'
|
8
|
+
require 'logger'
|
9
|
+
require 'parsedate'
|
10
|
+
require 'pp'
|
11
|
+
require 'tempfile'
|
12
|
+
|
13
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
14
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
15
|
+
|
16
|
+
require 'relaxdb/all_delegator'
|
17
|
+
require 'relaxdb/belongs_to_proxy'
|
18
|
+
require 'relaxdb/design_doc'
|
19
|
+
require 'relaxdb/document'
|
20
|
+
require 'relaxdb/extlib'
|
21
|
+
require 'relaxdb/has_many_proxy'
|
22
|
+
require 'relaxdb/has_one_proxy'
|
23
|
+
require 'relaxdb/paginate_params'
|
24
|
+
require 'relaxdb/paginator'
|
25
|
+
require 'relaxdb/query'
|
26
|
+
require 'relaxdb/references_many_proxy'
|
27
|
+
require 'relaxdb/relaxdb'
|
28
|
+
require 'relaxdb/server'
|
29
|
+
require 'relaxdb/sorted_by_view'
|
30
|
+
require 'relaxdb/uuid_generator'
|
31
|
+
require 'relaxdb/view_object'
|
32
|
+
require 'relaxdb/view_result'
|
33
|
+
require 'relaxdb/view_uploader'
|
34
|
+
require 'relaxdb/views'
|
35
|
+
require 'more/grapher.rb'
|
36
|
+
|
37
|
+
module RelaxDB
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
#
|
4
|
+
# The AllDelegator allows clients to query CouchDB in a natural way
|
5
|
+
# FooDoc.all - returns all docs in CouchDB of type FooDoc
|
6
|
+
# FooDoc.all.sorted_by(:att1, :att2) - returns all docs in CouchDB of type FooDoc sorted by att1, then att2
|
7
|
+
# FooDoc.all.sorted_by(:att1) { |q| q.key("bar") } - returns all docs of type FooDoc where att1 equals "bar"
|
8
|
+
# FooDoc.all.destroy! - does what it says on the tin
|
9
|
+
#
|
10
|
+
class AllDelegator < Delegator
|
11
|
+
|
12
|
+
def initialize(class_name)
|
13
|
+
super([])
|
14
|
+
@class_name = class_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def __getobj__
|
18
|
+
view_path = "_view/#{@class_name}/all"
|
19
|
+
map_function = ViewCreator.all(@class_name)
|
20
|
+
|
21
|
+
@all = RelaxDB.retrieve(view_path, @class_name, "all", map_function)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sorted_by(*atts)
|
25
|
+
view = SortedByView.new(@class_name, *atts)
|
26
|
+
|
27
|
+
query = Query.new(@class_name, view.view_name)
|
28
|
+
yield query if block_given?
|
29
|
+
|
30
|
+
view.query(query)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Note that this method leaves the corresponding DesignDoc for the associated class intact
|
34
|
+
def destroy!
|
35
|
+
each do |o|
|
36
|
+
# A reload is required for deleting objects with a self referential references_many relationship
|
37
|
+
# This makes all.destroy! very slow. Given that references_many is now deprecated and will
|
38
|
+
# soon be removed, the required reload is no longer performed.
|
39
|
+
# obj = RelaxDB.load(o._id)
|
40
|
+
# obj.destroy!
|
41
|
+
|
42
|
+
o.destroy!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class BelongsToProxy
|
4
|
+
|
5
|
+
attr_reader :target
|
6
|
+
|
7
|
+
def initialize(client, relationship)
|
8
|
+
@client = client
|
9
|
+
@relationship = relationship
|
10
|
+
@target = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def target
|
14
|
+
return @target if @target
|
15
|
+
|
16
|
+
id = @client.instance_variable_get("@#{@relationship}_id")
|
17
|
+
@target = RelaxDB.load(id) if id
|
18
|
+
end
|
19
|
+
|
20
|
+
def target=(new_target)
|
21
|
+
id = new_target ? new_target._id : nil
|
22
|
+
@client.instance_variable_set("@#{@relationship}_id", id)
|
23
|
+
|
24
|
+
@target = new_target
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class DesignDocument
|
4
|
+
|
5
|
+
def initialize(client_class, data)
|
6
|
+
@client_class = client_class
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_map_view(view_name, function)
|
11
|
+
add_view(view_name, "map", function)
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_reduce_view(view_name, function)
|
15
|
+
add_view(view_name, "reduce", function)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_view(view_name, type, function)
|
19
|
+
@data["views"] ||= {}
|
20
|
+
@data["views"][view_name] ||= {}
|
21
|
+
@data["views"][view_name][type] = function
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def save
|
26
|
+
database = RelaxDB.db
|
27
|
+
resp = database.put(::CGI::escape(@data["_id"]), @data.to_json)
|
28
|
+
@data["_rev"] = JSON.parse(resp.body)["rev"]
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.get(client_class)
|
33
|
+
begin
|
34
|
+
database = RelaxDB.db
|
35
|
+
resp = database.get(::CGI::escape("_design/#{client_class}"))
|
36
|
+
DesignDocument.new(client_class, JSON.parse(resp.body))
|
37
|
+
rescue => e
|
38
|
+
DesignDocument.new(client_class, {"_id" => "_design/#{client_class}"} )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy!
|
43
|
+
# Implicitly prevent the object from being resaved by failing to update its revision
|
44
|
+
RelaxDB.db.delete("#{::CGI::escape(@data["_id"])}?rev=#{@data["_rev"]}")
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,377 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class Document
|
4
|
+
|
5
|
+
# Used to store validation messages
|
6
|
+
attr_accessor :errors
|
7
|
+
|
8
|
+
# Define properties and property methods
|
9
|
+
|
10
|
+
def self.property(prop, opts={})
|
11
|
+
# Class instance varibles are not inherited, so the default properties must be explicitly listed
|
12
|
+
# Perhaps a better solution exists. Revise. I think extlib contains a solution for this...
|
13
|
+
@properties ||= [:_id, :_rev]
|
14
|
+
@properties << prop
|
15
|
+
|
16
|
+
define_method(prop) do
|
17
|
+
instance_variable_get("@#{prop}".to_sym)
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method("#{prop}=") do |val|
|
21
|
+
instance_variable_set("@#{prop}".to_sym, val)
|
22
|
+
end
|
23
|
+
|
24
|
+
if opts[:default]
|
25
|
+
define_method("set_default_#{prop}") do
|
26
|
+
default = opts[:default]
|
27
|
+
default = default.is_a?(Proc) ? default.call : default
|
28
|
+
instance_variable_set("@#{prop}".to_sym, default)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if opts[:validator]
|
33
|
+
define_method("validate_#{prop}") do |prop_val|
|
34
|
+
opts[:validator].call(prop_val)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if opts[:validation_msg]
|
39
|
+
define_method("#{prop}_validation_msg") do
|
40
|
+
opts[:validation_msg]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.properties
|
47
|
+
# Ensure that classes that don't define their own properties still function as CouchDB objects
|
48
|
+
@properties ||= [:_id, :_rev]
|
49
|
+
end
|
50
|
+
|
51
|
+
def properties
|
52
|
+
self.class.properties
|
53
|
+
end
|
54
|
+
|
55
|
+
# Specifying these properties here (after property method has been defined)
|
56
|
+
# is kinda ugly. Consider a better solution.
|
57
|
+
property :_id
|
58
|
+
property :_rev
|
59
|
+
|
60
|
+
def initialize(hash={})
|
61
|
+
# The default _id will be overwritten if loaded from CouchDB
|
62
|
+
self._id = UuidGenerator.uuid
|
63
|
+
|
64
|
+
@errors = Errors.new
|
65
|
+
|
66
|
+
# Set default properties if this object has not known CouchDB
|
67
|
+
unless hash["_rev"]
|
68
|
+
properties.each do |prop|
|
69
|
+
if methods.include?("set_default_#{prop}")
|
70
|
+
send("set_default_#{prop}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
set_attributes(hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_attributes(data)
|
79
|
+
data.each do |key, val|
|
80
|
+
# Only set instance variables on creation - object references are resolved on demand
|
81
|
+
|
82
|
+
# If the variable name ends in _at try to convert it to a Time
|
83
|
+
if key =~ /_at$/
|
84
|
+
val = Time.local(*ParseDate.parsedate(val)) rescue val
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ignore param keys that don't have a corresponding writer
|
88
|
+
# This allows us to comfortably accept a hash containing superflous data
|
89
|
+
# such as a params hash in a controller
|
90
|
+
if methods.include? "#{key}="
|
91
|
+
send("#{key}=".to_sym, val)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def inspect
|
98
|
+
s = "#<#{self.class}:#{self.object_id}"
|
99
|
+
properties.each do |prop|
|
100
|
+
prop_val = instance_variable_get("@#{prop}".to_sym)
|
101
|
+
s << ", #{prop}: #{prop_val.inspect}" if prop_val
|
102
|
+
end
|
103
|
+
self.class.belongs_to_rels.each do |relationship|
|
104
|
+
id = instance_variable_get("@#{relationship}_id".to_sym)
|
105
|
+
s << ", #{relationship}_id: #{id}" if id
|
106
|
+
end
|
107
|
+
s << ">"
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_json
|
111
|
+
data = {}
|
112
|
+
self.class.belongs_to_rels.each do |relationship, opts|
|
113
|
+
id = instance_variable_get("@#{relationship}_id".to_sym)
|
114
|
+
data["#{relationship}_id"] = id if id
|
115
|
+
if opts[:denormalise]
|
116
|
+
add_denormalised_data(data, relationship, opts)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
properties.each do |prop|
|
120
|
+
prop_val = instance_variable_get("@#{prop}".to_sym)
|
121
|
+
data["#{prop}"] = prop_val if prop_val
|
122
|
+
end
|
123
|
+
data["class"] = self.class.name
|
124
|
+
data.to_json
|
125
|
+
end
|
126
|
+
|
127
|
+
# quick n' dirty denormalisation - explicit denormalisation will probably become a
|
128
|
+
# permanent fixture of RelaxDB, but quite likely in a different form to this one
|
129
|
+
def add_denormalised_data(data, relationship, opts)
|
130
|
+
obj = send(relationship)
|
131
|
+
if obj
|
132
|
+
opts[:denormalise].each do |prop_name|
|
133
|
+
val = obj.send(prop_name)
|
134
|
+
data["#{relationship}_#{prop_name}"] = val
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def save
|
140
|
+
return false unless before_save
|
141
|
+
return false unless validates?
|
142
|
+
|
143
|
+
set_created_at if unsaved?
|
144
|
+
|
145
|
+
resp = RelaxDB.db.put("#{_id}", to_json)
|
146
|
+
self._rev = JSON.parse(resp.body)["rev"]
|
147
|
+
|
148
|
+
after_save
|
149
|
+
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
def validates?
|
154
|
+
success = true
|
155
|
+
properties.each do |prop|
|
156
|
+
if methods.include? "validate_#{prop}"
|
157
|
+
prop_val = instance_variable_get("@#{prop}")
|
158
|
+
success = send("validate_#{prop}", prop_val) rescue false
|
159
|
+
unless success
|
160
|
+
if methods.include? "#{prop}_validation_msg"
|
161
|
+
@errors["#{prop}".to_sym] = send("#{prop}_validation_msg")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
success
|
167
|
+
end
|
168
|
+
|
169
|
+
# Hmm. Rename... never_saved? newnew?
|
170
|
+
def unsaved?
|
171
|
+
@_rev.nil?
|
172
|
+
end
|
173
|
+
alias_method :new_record?, :unsaved?
|
174
|
+
|
175
|
+
def to_param
|
176
|
+
self._id
|
177
|
+
end
|
178
|
+
alias_method :id, :to_param
|
179
|
+
|
180
|
+
def set_created_at
|
181
|
+
if methods.include? "created_at"
|
182
|
+
# Don't override it if it's already been set
|
183
|
+
@created_at = Time.now if @created_at.nil?
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def create_or_get_proxy(klass, relationship, opts=nil)
|
188
|
+
proxy_sym = "@proxy_#{relationship}".to_sym
|
189
|
+
proxy = instance_variable_get(proxy_sym)
|
190
|
+
unless proxy
|
191
|
+
proxy = opts ? klass.new(self, relationship, opts) : klass.new(self, relationship)
|
192
|
+
end
|
193
|
+
instance_variable_set(proxy_sym, proxy)
|
194
|
+
proxy
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns true if CouchDB considers other to be the same as self
|
198
|
+
def ==(other)
|
199
|
+
other && _id == other._id
|
200
|
+
end
|
201
|
+
|
202
|
+
# Deprecated. This method was experimental and will be removed
|
203
|
+
# once multi key GETs are available in CouchDB.
|
204
|
+
def self.references_many(relationship, opts={})
|
205
|
+
# Treat the representation as a standard property
|
206
|
+
properties << relationship
|
207
|
+
|
208
|
+
# Keep track of the relationship so peers can be disassociated on destroy
|
209
|
+
@references_many_rels ||= []
|
210
|
+
@references_many_rels << relationship
|
211
|
+
|
212
|
+
define_method(relationship) do
|
213
|
+
array_sym = "@#{relationship}".to_sym
|
214
|
+
instance_variable_set(array_sym, []) unless instance_variable_defined? array_sym
|
215
|
+
|
216
|
+
create_or_get_proxy(RelaxDB::ReferencesManyProxy, relationship, opts)
|
217
|
+
end
|
218
|
+
|
219
|
+
define_method("#{relationship}=") do |val|
|
220
|
+
# Sharp edge - do not invoke this method
|
221
|
+
instance_variable_set("@#{relationship}".to_sym, val)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.references_many_rels
|
226
|
+
# Don't force clients to check its instantiated
|
227
|
+
@references_many_rels ||= []
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.has_many(relationship, opts={})
|
231
|
+
@has_many_rels ||= []
|
232
|
+
@has_many_rels << relationship
|
233
|
+
|
234
|
+
define_method(relationship) do
|
235
|
+
create_or_get_proxy(HasManyProxy, relationship, opts)
|
236
|
+
end
|
237
|
+
|
238
|
+
define_method("#{relationship}=") do
|
239
|
+
raise "You may not currently assign to a has_many relationship - may be implemented"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.has_many_rels
|
244
|
+
# Don't force clients to check its instantiated
|
245
|
+
@has_many_rels ||= []
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.has_one(relationship)
|
249
|
+
@has_one_rels ||= []
|
250
|
+
@has_one_rels << relationship
|
251
|
+
|
252
|
+
define_method(relationship) do
|
253
|
+
create_or_get_proxy(HasOneProxy, relationship).target
|
254
|
+
end
|
255
|
+
|
256
|
+
define_method("#{relationship}=") do |new_target|
|
257
|
+
create_or_get_proxy(HasOneProxy, relationship).target = new_target
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.has_one_rels
|
262
|
+
@has_one_rels ||= []
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.belongs_to(relationship, opts={})
|
266
|
+
@belongs_to_rels ||= {}
|
267
|
+
@belongs_to_rels[relationship] = opts
|
268
|
+
|
269
|
+
define_method(relationship) do
|
270
|
+
create_or_get_proxy(BelongsToProxy, relationship).target
|
271
|
+
end
|
272
|
+
|
273
|
+
define_method("#{relationship}=") do |new_target|
|
274
|
+
create_or_get_proxy(BelongsToProxy, relationship).target = new_target
|
275
|
+
end
|
276
|
+
|
277
|
+
# Allows all writers to be invoked from the hash passed to initialize
|
278
|
+
define_method("#{relationship}_id=") do |id|
|
279
|
+
instance_variable_set("@#{relationship}_id".to_sym, id)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Allows belongs_to relationships to be used by the paginator
|
283
|
+
define_method("#{relationship}_id") do
|
284
|
+
instance_variable_get("@#{relationship}_id")
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
def self.belongs_to_rels
|
290
|
+
# Don't force clients to check that it's instantiated
|
291
|
+
@belongs_to_rels ||= {}
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.all_relationships
|
295
|
+
belongs_to_rels + has_one_rels + has_many_rels + references_many_rels
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.all
|
299
|
+
@all_delegator ||= AllDelegator.new(self.name)
|
300
|
+
end
|
301
|
+
|
302
|
+
# destroy! nullifies all relationships with peers and children before deleting
|
303
|
+
# itself in CouchDB
|
304
|
+
# The nullification and deletion are not performed in a transaction
|
305
|
+
def destroy!
|
306
|
+
self.class.references_many_rels.each do |rel|
|
307
|
+
send(rel).clear
|
308
|
+
end
|
309
|
+
|
310
|
+
self.class.has_many_rels.each do |rel|
|
311
|
+
send(rel).clear
|
312
|
+
end
|
313
|
+
|
314
|
+
self.class.has_one_rels.each do |rel|
|
315
|
+
send("#{rel}=".to_sym, nil)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Implicitly prevent the object from being resaved by failing to update its revision
|
319
|
+
RelaxDB.db.delete("#{_id}?rev=#{_rev}")
|
320
|
+
self
|
321
|
+
end
|
322
|
+
|
323
|
+
#
|
324
|
+
# Callbacks - define these in a module and mix'em'in ?
|
325
|
+
#
|
326
|
+
def self.before_save(callback)
|
327
|
+
before_save_callbacks << callback
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.before_save_callbacks
|
331
|
+
@before_save ||= []
|
332
|
+
end
|
333
|
+
|
334
|
+
def before_save
|
335
|
+
self.class.before_save_callbacks.each do |callback|
|
336
|
+
resp = callback.is_a?(Proc) ? callback.call(self) : send(callback)
|
337
|
+
return false unless resp
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def self.after_save(callback)
|
342
|
+
after_save_callbacks << callback
|
343
|
+
end
|
344
|
+
|
345
|
+
def self.after_save_callbacks
|
346
|
+
@after_save_callbacks ||= []
|
347
|
+
end
|
348
|
+
|
349
|
+
def after_save
|
350
|
+
self.class.after_save_callbacks.each do |callback|
|
351
|
+
callback.is_a?(Proc) ? callback.call(self) : send(callback)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def self.paginate_by(page_params, *view_keys)
|
356
|
+
paginate_params = PaginateParams.new
|
357
|
+
yield paginate_params
|
358
|
+
raise paginate_params.error_msg if paginate_params.invalid?
|
359
|
+
|
360
|
+
paginator = Paginator.new(paginate_params, page_params)
|
361
|
+
|
362
|
+
design_doc_name = self.name
|
363
|
+
view = SortedByView.new(design_doc_name, *view_keys)
|
364
|
+
query = Query.new(design_doc_name, view.view_name)
|
365
|
+
query.merge(paginate_params)
|
366
|
+
|
367
|
+
docs = view.query(query)
|
368
|
+
docs.reverse! if paginate_params.order_inverted?
|
369
|
+
|
370
|
+
paginator.add_next_and_prev(docs, design_doc_name, view.view_name, view_keys)
|
371
|
+
|
372
|
+
docs
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|