cohitre-relaxdb 0.2.2
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/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 +386 -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 +157 -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_result.rb +18 -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 +364 -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 +137 -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 +119 -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,386 @@
|
|
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
|
+
# Order changed as of 30/10/2008 to be consistent with ActiveRecord
|
140
|
+
# Not yet sure of final implemention for hooks - may lean more towards DM than AR
|
141
|
+
def save
|
142
|
+
return false unless validates?
|
143
|
+
return false unless before_save
|
144
|
+
|
145
|
+
set_created_at if unsaved?
|
146
|
+
|
147
|
+
resp = RelaxDB.db.put("#{_id}", to_json)
|
148
|
+
self._rev = JSON.parse(resp.body)["rev"]
|
149
|
+
|
150
|
+
after_save
|
151
|
+
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
def validates?
|
156
|
+
total_success = true
|
157
|
+
properties.each do |prop|
|
158
|
+
if methods.include? "validate_#{prop}"
|
159
|
+
prop_val = instance_variable_get("@#{prop}")
|
160
|
+
success = send("validate_#{prop}", prop_val) rescue false
|
161
|
+
unless success
|
162
|
+
if methods.include? "#{prop}_validation_msg"
|
163
|
+
@errors["#{prop}".to_sym] = send("#{prop}_validation_msg")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
total_success &= success
|
167
|
+
end
|
168
|
+
end
|
169
|
+
total_success &= validate
|
170
|
+
total_success
|
171
|
+
end
|
172
|
+
|
173
|
+
def validate
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
# Hmm. Rename... never_saved? newnew?
|
178
|
+
def unsaved?
|
179
|
+
@_rev.nil?
|
180
|
+
end
|
181
|
+
alias_method :new_record?, :unsaved?
|
182
|
+
alias_method :new_document?, :unsaved?
|
183
|
+
|
184
|
+
def to_param
|
185
|
+
self._id
|
186
|
+
end
|
187
|
+
alias_method :id, :to_param
|
188
|
+
|
189
|
+
def set_created_at
|
190
|
+
if methods.include? "created_at"
|
191
|
+
# Don't override it if it's already been set
|
192
|
+
@created_at = Time.now if @created_at.nil?
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def create_or_get_proxy(klass, relationship, opts=nil)
|
197
|
+
proxy_sym = "@proxy_#{relationship}".to_sym
|
198
|
+
proxy = instance_variable_get(proxy_sym)
|
199
|
+
unless proxy
|
200
|
+
proxy = opts ? klass.new(self, relationship, opts) : klass.new(self, relationship)
|
201
|
+
end
|
202
|
+
instance_variable_set(proxy_sym, proxy)
|
203
|
+
proxy
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns true if CouchDB considers other to be the same as self
|
207
|
+
def ==(other)
|
208
|
+
other && _id == other._id
|
209
|
+
end
|
210
|
+
|
211
|
+
# Deprecated. This method was experimental and will be removed
|
212
|
+
# once multi key GETs are available in CouchDB.
|
213
|
+
def self.references_many(relationship, opts={})
|
214
|
+
# Treat the representation as a standard property
|
215
|
+
properties << relationship
|
216
|
+
|
217
|
+
# Keep track of the relationship so peers can be disassociated on destroy
|
218
|
+
@references_many_rels ||= []
|
219
|
+
@references_many_rels << relationship
|
220
|
+
|
221
|
+
define_method(relationship) do
|
222
|
+
array_sym = "@#{relationship}".to_sym
|
223
|
+
instance_variable_set(array_sym, []) unless instance_variable_defined? array_sym
|
224
|
+
|
225
|
+
create_or_get_proxy(RelaxDB::ReferencesManyProxy, relationship, opts)
|
226
|
+
end
|
227
|
+
|
228
|
+
define_method("#{relationship}=") do |val|
|
229
|
+
# Sharp edge - do not invoke this method
|
230
|
+
instance_variable_set("@#{relationship}".to_sym, val)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.references_many_rels
|
235
|
+
# Don't force clients to check its instantiated
|
236
|
+
@references_many_rels ||= []
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.has_many(relationship, opts={})
|
240
|
+
@has_many_rels ||= []
|
241
|
+
@has_many_rels << relationship
|
242
|
+
|
243
|
+
define_method(relationship) do
|
244
|
+
create_or_get_proxy(HasManyProxy, relationship, opts)
|
245
|
+
end
|
246
|
+
|
247
|
+
define_method("#{relationship}=") do
|
248
|
+
raise "You may not currently assign to a has_many relationship - may be implemented"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def self.has_many_rels
|
253
|
+
# Don't force clients to check its instantiated
|
254
|
+
@has_many_rels ||= []
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.has_one(relationship)
|
258
|
+
@has_one_rels ||= []
|
259
|
+
@has_one_rels << relationship
|
260
|
+
|
261
|
+
define_method(relationship) do
|
262
|
+
create_or_get_proxy(HasOneProxy, relationship).target
|
263
|
+
end
|
264
|
+
|
265
|
+
define_method("#{relationship}=") do |new_target|
|
266
|
+
create_or_get_proxy(HasOneProxy, relationship).target = new_target
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.has_one_rels
|
271
|
+
@has_one_rels ||= []
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.belongs_to(relationship, opts={})
|
275
|
+
@belongs_to_rels ||= {}
|
276
|
+
@belongs_to_rels[relationship] = opts
|
277
|
+
|
278
|
+
define_method(relationship) do
|
279
|
+
create_or_get_proxy(BelongsToProxy, relationship).target
|
280
|
+
end
|
281
|
+
|
282
|
+
define_method("#{relationship}=") do |new_target|
|
283
|
+
create_or_get_proxy(BelongsToProxy, relationship).target = new_target
|
284
|
+
end
|
285
|
+
|
286
|
+
# Allows all writers to be invoked from the hash passed to initialize
|
287
|
+
define_method("#{relationship}_id=") do |id|
|
288
|
+
instance_variable_set("@#{relationship}_id".to_sym, id)
|
289
|
+
end
|
290
|
+
|
291
|
+
# Allows belongs_to relationships to be used by the paginator
|
292
|
+
define_method("#{relationship}_id") do
|
293
|
+
instance_variable_get("@#{relationship}_id")
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.belongs_to_rels
|
299
|
+
# Don't force clients to check that it's instantiated
|
300
|
+
@belongs_to_rels ||= {}
|
301
|
+
end
|
302
|
+
|
303
|
+
def self.all_relationships
|
304
|
+
belongs_to_rels + has_one_rels + has_many_rels + references_many_rels
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.all
|
308
|
+
@all_delegator ||= AllDelegator.new(self.name)
|
309
|
+
end
|
310
|
+
|
311
|
+
# destroy! nullifies all relationships with peers and children before deleting
|
312
|
+
# itself in CouchDB
|
313
|
+
# The nullification and deletion are not performed in a transaction
|
314
|
+
def destroy!
|
315
|
+
self.class.references_many_rels.each do |rel|
|
316
|
+
send(rel).clear
|
317
|
+
end
|
318
|
+
|
319
|
+
self.class.has_many_rels.each do |rel|
|
320
|
+
send(rel).clear
|
321
|
+
end
|
322
|
+
|
323
|
+
self.class.has_one_rels.each do |rel|
|
324
|
+
send("#{rel}=".to_sym, nil)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Implicitly prevent the object from being resaved by failing to update its revision
|
328
|
+
RelaxDB.db.delete("#{_id}?rev=#{_rev}")
|
329
|
+
self
|
330
|
+
end
|
331
|
+
|
332
|
+
#
|
333
|
+
# Callbacks - define these in a module and mix'em'in ?
|
334
|
+
#
|
335
|
+
def self.before_save(callback)
|
336
|
+
before_save_callbacks << callback
|
337
|
+
end
|
338
|
+
|
339
|
+
def self.before_save_callbacks
|
340
|
+
@before_save ||= []
|
341
|
+
end
|
342
|
+
|
343
|
+
def before_save
|
344
|
+
self.class.before_save_callbacks.each do |callback|
|
345
|
+
resp = callback.is_a?(Proc) ? callback.call(self) : send(callback)
|
346
|
+
return false unless resp
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def self.after_save(callback)
|
351
|
+
after_save_callbacks << callback
|
352
|
+
end
|
353
|
+
|
354
|
+
def self.after_save_callbacks
|
355
|
+
@after_save_callbacks ||= []
|
356
|
+
end
|
357
|
+
|
358
|
+
def after_save
|
359
|
+
self.class.after_save_callbacks.each do |callback|
|
360
|
+
callback.is_a?(Proc) ? callback.call(self) : send(callback)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.paginate_by(page_params, *view_keys)
|
365
|
+
paginate_params = PaginateParams.new
|
366
|
+
yield paginate_params
|
367
|
+
raise paginate_params.error_msg if paginate_params.invalid?
|
368
|
+
|
369
|
+
paginator = Paginator.new(paginate_params, page_params)
|
370
|
+
|
371
|
+
design_doc_name = self.name
|
372
|
+
view = SortedByView.new(design_doc_name, *view_keys)
|
373
|
+
query = Query.new(design_doc_name, view.view_name)
|
374
|
+
query.merge(paginate_params)
|
375
|
+
|
376
|
+
docs = view.query(query)
|
377
|
+
docs.reverse! if paginate_params.order_inverted?
|
378
|
+
|
379
|
+
paginator.add_next_and_prev(docs, design_doc_name, view.view_name, view_keys)
|
380
|
+
|
381
|
+
docs
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|