relaxdb 0.3.5 → 0.5
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 +21 -23
- data/Rakefile +2 -7
- data/docs/spec_results.html +5 -5
- data/lib/more/grapher.rb +1 -1
- data/lib/relaxdb.rb +3 -5
- data/lib/relaxdb/all_delegator.rb +19 -13
- data/lib/relaxdb/document.rb +150 -218
- data/lib/relaxdb/extlib.rb +7 -1
- data/lib/relaxdb/migration.rb +11 -8
- data/lib/relaxdb/net_http_server.rb +19 -1
- data/lib/relaxdb/paginator.rb +30 -11
- data/lib/relaxdb/query.rb +1 -1
- data/lib/relaxdb/{belongs_to_proxy.rb → references_proxy.rb} +3 -3
- data/lib/relaxdb/relaxdb.rb +87 -7
- data/lib/relaxdb/server.rb +8 -2
- data/lib/relaxdb/taf2_curb_server.rb +2 -1
- data/lib/relaxdb/uuid_generator.rb +38 -2
- data/lib/relaxdb/view_by_delegator.rb +34 -0
- data/lib/relaxdb/view_object.rb +1 -1
- data/lib/relaxdb/view_uploader.rb +16 -2
- data/lib/relaxdb/views.rb +23 -55
- data/readme.rb +3 -3
- data/spec/all_delegator_spec.rb +52 -0
- data/spec/callbacks_spec.rb +4 -4
- data/spec/derived_properties_spec.rb +4 -4
- data/spec/design_doc_spec.rb +2 -2
- data/spec/doc_inheritable_spec.rb +2 -2
- data/spec/document_spec.rb +47 -25
- data/spec/migration_spec.rb +12 -10
- data/spec/qpaginate_spec.rb +88 -0
- data/spec/query_spec.rb +2 -2
- data/spec/references_proxy_spec.rb +94 -0
- data/spec/relaxdb_spec.rb +29 -21
- data/spec/server_spec.rb +4 -3
- data/spec/spec_helper.rb +1 -0
- data/spec/spec_models.rb +48 -57
- data/spec/uuid_generator_spec.rb +34 -0
- data/spec/view_by_spec.rb +62 -54
- data/spec/view_docs_by_spec.rb +85 -0
- metadata +38 -27
- data/lib/more/atomic_bulk_save_support.rb +0 -18
- data/lib/relaxdb/has_many_proxy.rb +0 -101
- data/lib/relaxdb/has_one_proxy.rb +0 -42
- data/lib/relaxdb/references_many_proxy.rb +0 -97
- data/spec/belongs_to_spec.rb +0 -124
- data/spec/has_many_spec.rb +0 -202
- data/spec/has_one_spec.rb +0 -123
- data/spec/references_many_spec.rb +0 -173
- data/spec/view_spec.rb +0 -23
data/README.textile
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
h3. What's New?
|
2
2
|
|
3
|
-
*
|
4
|
-
**
|
5
|
-
**
|
6
|
-
|
7
|
-
** Added
|
8
|
-
|
9
|
-
**
|
10
|
-
**
|
11
|
-
|
12
|
-
|
13
|
-
** RelaxDB 0.3 released - compatible with CouchDB 0.9.
|
3
|
+
* 2010-04-10
|
4
|
+
** RelaxDB 0.4 released. Supports Ruby 1.9.1 and CouchDB 0.11.0.
|
5
|
+
** Auto-generated views no longer emit the document as a value by default
|
6
|
+
** Erlang view shorthand supported e.g. _sum and _count
|
7
|
+
** Added single query pagination
|
8
|
+
** Performance improvements
|
9
|
+
** Time.to_json fix. Thanks to "Karmi":http://github.com/karmi
|
10
|
+
** *Note*: This release includes a number of breaking changes. Please see the "release notes":http://github.com/paulcarey/relaxdb/blob/master/RELEASE_NOTES.textile for upgrading notes.
|
11
|
+
|
12
|
+
For those interested in using RelaxDB with an ETag based cache, please see "Fred Cheung's work":http://github.com/fcheung/relaxdb/commit/1d9acfd5f6b3c23da0d275252b6a6e064865440e
|
14
13
|
|
15
14
|
h2. Overview
|
16
15
|
|
@@ -22,8 +21,6 @@ A basic merb plugin, "merb_relaxdb":http://github.com/paulcarey/merb_relaxdb/tre
|
|
22
21
|
|
23
22
|
For more complete documentation take a look at docs/spec_results.html and the corresponding specs.
|
24
23
|
|
25
|
-
*Note*: While RelaxDB 0.3 is explicitly compatible with CouchDB 0.9, HEAD typically tracks CouchDB HEAD.
|
26
|
-
|
27
24
|
h2. Details
|
28
25
|
|
29
26
|
h3. Getting started
|
@@ -36,7 +33,9 @@ h3. Getting started
|
|
36
33
|
RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "app"
|
37
34
|
RelaxDB.use_db "relaxdb_scratch"
|
38
35
|
|
39
|
-
RelaxDB.enable_view_creation
|
36
|
+
RelaxDB.enable_view_creation # creates views when class definition is executed
|
37
|
+
|
38
|
+
RelaxDB::View.design_doc.save # save views to CouchDB after executing class definitions
|
40
39
|
</code>
|
41
40
|
</pre>
|
42
41
|
|
@@ -65,8 +64,9 @@ class Invite < RelaxDB::Document
|
|
65
64
|
property :sender_name,
|
66
65
|
:derived => [:sender, lambda { |p, o| o.sender.name } ]
|
67
66
|
|
68
|
-
view_by :sender_name
|
69
|
-
|
67
|
+
view_by :sender_name # Emits 1 as the map value
|
68
|
+
view_docs_by :sender_id # Emits the doc as the map value
|
69
|
+
|
70
70
|
view_by :recipient_id, :created_at, :descending => true
|
71
71
|
|
72
72
|
def on_update_conflict
|
@@ -95,10 +95,11 @@ i.save!
|
|
95
95
|
il = RelaxDB.load i._id
|
96
96
|
puts i == il # true
|
97
97
|
|
98
|
-
ir = Invite.by_sender_name "sofa"
|
98
|
+
ir = Invite.by_sender_name "sofa"
|
99
99
|
puts i == ir # true
|
100
100
|
|
101
|
-
|
101
|
+
ix_ids = Invite.by_sender_name :key => "sofa"
|
102
|
+
ix = ix_ids.load!.first
|
102
103
|
puts i == ix # true
|
103
104
|
|
104
105
|
# Denormalization
|
@@ -162,12 +163,9 @@ h3. Creating views by hand
|
|
162
163
|
emit(doc.state, doc);
|
163
164
|
}
|
164
165
|
|
166
|
+
// Uses the CouchDB builtin to invoke an Erlang reduce fun
|
165
167
|
function Invites_by_state-reduce(keys, values, rereduce) {
|
166
|
-
|
167
|
-
return sum(values);
|
168
|
-
} else {
|
169
|
-
return values.length;
|
170
|
-
}
|
168
|
+
_count
|
171
169
|
}
|
172
170
|
$
|
173
171
|
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require 'spec/rake/spectask'
|
|
4
4
|
|
5
5
|
PLUGIN = "relaxdb"
|
6
6
|
NAME = "relaxdb"
|
7
|
-
GEM_VERSION = "0.
|
7
|
+
GEM_VERSION = "0.5"
|
8
8
|
AUTHOR = "Paul Carey"
|
9
9
|
EMAIL = "paul.p.carey@gmail.com"
|
10
10
|
HOMEPAGE = "http://github.com/paulcarey/relaxdb/"
|
@@ -37,7 +37,7 @@ end
|
|
37
37
|
|
38
38
|
desc "Install"
|
39
39
|
task :install => [:package] do
|
40
|
-
sh %{
|
40
|
+
sh %{gem install --no-rdoc --no-ri --local pkg/#{NAME}-#{GEM_VERSION}}
|
41
41
|
end
|
42
42
|
|
43
43
|
desc "Run specs"
|
@@ -51,11 +51,6 @@ Spec::Rake::SpecTask.new('spec:html') do |t|
|
|
51
51
|
t.spec_opts = ["--format", "html:docs/spec_results.html"]
|
52
52
|
end
|
53
53
|
|
54
|
-
desc "Supports atomic bulk save"
|
55
|
-
task :atomic_bulk_save_support do |t|
|
56
|
-
require 'lib/more/atomic_bulk_save_support.rb'
|
57
|
-
end
|
58
|
-
|
59
54
|
desc "Create base spec db"
|
60
55
|
task :create_base_db do
|
61
56
|
require 'spec/spec_helper'
|
data/docs/spec_results.html
CHANGED
@@ -183,7 +183,7 @@ a {
|
|
183
183
|
</div>
|
184
184
|
<div class="example_group">
|
185
185
|
<dl>
|
186
|
-
<dt id="example_group_2">RelaxDB::BelongsToProxy
|
186
|
+
<dt id="example_group_2">RelaxDB::BelongsToProxy references</dt>
|
187
187
|
<script type="text/javascript">moveProgressBar('0.4');</script>
|
188
188
|
<dd class="spec passed"><span class="passed_spec_name">should return nil when accessed before assignment</span></dd>
|
189
189
|
<script type="text/javascript">moveProgressBar('0.9');</script>
|
@@ -208,7 +208,7 @@ a {
|
|
208
208
|
</div>
|
209
209
|
<div class="example_group">
|
210
210
|
<dl>
|
211
|
-
<dt id="example_group_3">RelaxDB::BelongsToProxy
|
211
|
+
<dt id="example_group_3">RelaxDB::BelongsToProxy references validator</dt>
|
212
212
|
<script type="text/javascript">moveProgressBar('5.0');</script>
|
213
213
|
<dd class="spec passed"><span class="passed_spec_name">should be passed the _id and object</span></dd>
|
214
214
|
<script type="text/javascript">moveProgressBar('5.5');</script>
|
@@ -620,7 +620,7 @@ a {
|
|
620
620
|
<dl>
|
621
621
|
<dt id="example_group_42">RelaxDB::HasManyProxy has_many#delete</dt>
|
622
622
|
<script type="text/javascript">moveProgressBar('51.8');</script>
|
623
|
-
<dd class="spec passed"><span class="passed_spec_name">should nullify the
|
623
|
+
<dd class="spec passed"><span class="passed_spec_name">should nullify the references relationship</span></dd>
|
624
624
|
</dl>
|
625
625
|
</div>
|
626
626
|
<div class="example_group">
|
@@ -988,12 +988,12 @@ a {
|
|
988
988
|
</div>
|
989
989
|
<div class="example_group">
|
990
990
|
<dl>
|
991
|
-
<dt id="example_group_80">
|
991
|
+
<dt id="example_group_80">view_docs_by</dt>
|
992
992
|
</dl>
|
993
993
|
</div>
|
994
994
|
<div class="example_group">
|
995
995
|
<dl>
|
996
|
-
<dt id="example_group_81">
|
996
|
+
<dt id="example_group_81">view_docs_by view_docs_by</dt>
|
997
997
|
<script type="text/javascript">moveProgressBar('93.5');</script>
|
998
998
|
<dd class="spec passed"><span class="passed_spec_name">should create corresponding views</span></dd>
|
999
999
|
<script type="text/javascript">moveProgressBar('93.9');</script>
|
data/lib/more/grapher.rb
CHANGED
@@ -30,7 +30,7 @@ module RelaxDB
|
|
30
30
|
|
31
31
|
dot << %Q%#{doc._id} [ label ="#{atts}"];\n%
|
32
32
|
|
33
|
-
doc.class.
|
33
|
+
doc.class.references_rels.each do |relationship, opts|
|
34
34
|
id = doc.instance_variable_get("@#{relationship}_id".to_sym)
|
35
35
|
dot << %Q%#{id} -> #{doc._id} [ label = "#{relationship}"];\n% if id
|
36
36
|
end
|
data/lib/relaxdb.rb
CHANGED
@@ -6,7 +6,7 @@ require 'uuid'
|
|
6
6
|
require 'cgi'
|
7
7
|
require 'net/http'
|
8
8
|
require 'logger'
|
9
|
-
require 'parsedate'
|
9
|
+
require 'parsedate' if RUBY_VERSION.to_f < 1.9
|
10
10
|
require 'pp'
|
11
11
|
require 'tempfile'
|
12
12
|
|
@@ -24,20 +24,18 @@ rescue LoadError
|
|
24
24
|
end
|
25
25
|
|
26
26
|
require 'relaxdb/all_delegator'
|
27
|
-
require 'relaxdb/belongs_to_proxy'
|
28
27
|
require 'relaxdb/design_doc'
|
29
28
|
require 'relaxdb/document'
|
30
29
|
require 'relaxdb/extlib'
|
31
|
-
require 'relaxdb/has_many_proxy'
|
32
|
-
require 'relaxdb/has_one_proxy'
|
33
30
|
require 'relaxdb/migration'
|
34
31
|
require 'relaxdb/paginate_params'
|
35
32
|
require 'relaxdb/paginator'
|
36
33
|
require 'relaxdb/query'
|
37
|
-
require 'relaxdb/
|
34
|
+
require 'relaxdb/references_proxy'
|
38
35
|
require 'relaxdb/relaxdb'
|
39
36
|
require 'relaxdb/server'
|
40
37
|
require 'relaxdb/uuid_generator'
|
38
|
+
require 'relaxdb/view_by_delegator'
|
41
39
|
require 'relaxdb/view_object'
|
42
40
|
require 'relaxdb/view_result'
|
43
41
|
require 'relaxdb/view_uploader'
|
@@ -15,10 +15,21 @@ module RelaxDB
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def __getobj__
|
18
|
-
unless @
|
19
|
-
|
18
|
+
unless @ids
|
19
|
+
params = {:raw => true}.merge @params
|
20
|
+
result = RelaxDB.rf_view "#{@class_name}_all", params
|
21
|
+
@ids = RelaxDB.ids_from_view result
|
20
22
|
end
|
21
|
-
@
|
23
|
+
@ids
|
24
|
+
end
|
25
|
+
|
26
|
+
def __setobj__ obj
|
27
|
+
# Intentionally empty
|
28
|
+
end
|
29
|
+
|
30
|
+
def load!
|
31
|
+
__getobj__
|
32
|
+
@objs = RelaxDB.load! @ids
|
22
33
|
end
|
23
34
|
|
24
35
|
def size
|
@@ -26,17 +37,12 @@ module RelaxDB
|
|
26
37
|
size || 0
|
27
38
|
end
|
28
39
|
|
29
|
-
# TODO: destroy in a bulk_save if feasible
|
30
40
|
def destroy!
|
31
|
-
|
32
|
-
@objs.each
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# obj.destroy!
|
37
|
-
|
38
|
-
o.destroy!
|
39
|
-
end
|
41
|
+
load!
|
42
|
+
@objs.each { |o| o.data["_deleted"] = true }
|
43
|
+
# Direct post rather than bulk save as we don't want validators to be run
|
44
|
+
resp = RelaxDB.db.post("_bulk_docs", {"docs" => @objs}.to_json)
|
45
|
+
JSON.parse resp.body
|
40
46
|
end
|
41
47
|
|
42
48
|
end
|
data/lib/relaxdb/document.rb
CHANGED
@@ -8,41 +8,57 @@ module RelaxDB
|
|
8
8
|
attr_accessor :errors
|
9
9
|
|
10
10
|
# A call issued to save_all will save this object and the
|
11
|
-
# contents of the save_list. This allows secondary
|
11
|
+
# contents of the save_list. This allows secondary objects to
|
12
12
|
# be saved at the same time as this object.
|
13
13
|
attr_accessor :save_list
|
14
14
|
|
15
15
|
# Attribute symbols added to this list won't be validated on save
|
16
16
|
attr_accessor :validation_skip_list
|
17
17
|
|
18
|
+
# Not part of the public API - should only be used by clients with caution.
|
19
|
+
# The data keys are Strings as this is what JSON.parse gives us.
|
20
|
+
attr_accessor :data
|
21
|
+
|
18
22
|
class_inheritable_accessor :properties, :reader => true
|
19
23
|
self.properties = []
|
20
24
|
|
21
25
|
class_inheritable_accessor :derived_prop_writers
|
22
26
|
self.derived_prop_writers = {}
|
23
27
|
|
28
|
+
class_inheritable_accessor :__view_docs_by_list__
|
29
|
+
self.__view_docs_by_list__ = []
|
30
|
+
|
24
31
|
class_inheritable_accessor :__view_by_list__
|
25
|
-
self.__view_by_list__ = []
|
32
|
+
self.__view_by_list__ = []
|
26
33
|
|
27
|
-
class_inheritable_accessor :
|
28
|
-
self.
|
34
|
+
class_inheritable_accessor :references_rels, :reader => true
|
35
|
+
self.references_rels = {}
|
29
36
|
|
30
37
|
def self.property(prop, opts={})
|
31
38
|
properties << prop
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
|
40
|
+
if prop.to_s =~ /_at$|_on$|_date$|_time$/
|
41
|
+
define_method(prop) do
|
42
|
+
val = @data[prop.to_s]
|
43
|
+
Time.parse(val).utc rescue val
|
44
|
+
end
|
45
|
+
else
|
46
|
+
define_method(prop) do
|
47
|
+
@data[prop.to_s]
|
48
|
+
end
|
49
|
+
end
|
36
50
|
|
37
51
|
define_method("#{prop}=") do |val|
|
38
|
-
|
52
|
+
@data[prop.to_s] = val
|
39
53
|
end
|
40
54
|
|
41
55
|
if opts[:default]
|
42
|
-
define_method("
|
43
|
-
|
44
|
-
|
45
|
-
|
56
|
+
define_method("__set_default_#{prop}__") do
|
57
|
+
if @data[prop.to_s].nil?
|
58
|
+
default = opts[:default]
|
59
|
+
val = default.is_a?(Proc) ? default.call : default
|
60
|
+
@data[prop.to_s] = val
|
61
|
+
end
|
46
62
|
end
|
47
63
|
end
|
48
64
|
|
@@ -69,11 +85,15 @@ module RelaxDB
|
|
69
85
|
v.arity == 1 ?
|
70
86
|
define_method(method_name) { |att_val| v.call(att_val) } :
|
71
87
|
define_method(method_name) { |att_val| v.call(att_val, self) }
|
72
|
-
elsif instance_methods.include? "validator_#{v}"
|
73
|
-
define_method(method_name) { |att_val| send("validator_#{v}", att_val, self) }
|
74
88
|
else
|
75
|
-
|
76
|
-
|
89
|
+
v_meths = instance_methods.select { |m| m =~ /validator_/ }
|
90
|
+
v_meths.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9
|
91
|
+
if v_meths.include? "validator_#{v}".to_sym
|
92
|
+
define_method(method_name) { |att_val| send("validator_#{v}", att_val, self) }
|
93
|
+
else
|
94
|
+
define_method(method_name) { |att_val| send(v, att_val) }
|
95
|
+
end
|
96
|
+
end
|
77
97
|
end
|
78
98
|
|
79
99
|
def self.create_validation_msg(att, validation_msg)
|
@@ -82,7 +102,7 @@ module RelaxDB
|
|
82
102
|
define_method("#{att}_validation_msg") { |att_val| validation_msg.call(att_val) } :
|
83
103
|
define_method("#{att}_validation_msg") { |att_val| validation_msg.call(att_val, self) }
|
84
104
|
else
|
85
|
-
define_method("#{att}_validation_msg") { validation_msg }
|
105
|
+
define_method("#{att}_validation_msg") { |att_val| validation_msg }
|
86
106
|
end
|
87
107
|
end
|
88
108
|
|
@@ -105,62 +125,64 @@ module RelaxDB
|
|
105
125
|
if writers
|
106
126
|
writers.each do |prop, writer|
|
107
127
|
current_val = send(prop)
|
108
|
-
begin
|
128
|
+
# begin
|
109
129
|
send("#{prop}=", writer.call(current_val, self))
|
110
|
-
rescue => e
|
111
|
-
|
112
|
-
end
|
130
|
+
# rescue => e
|
131
|
+
# RelaxDB.logger.error "Deriving #{prop} from #{source} raised #{e}"
|
132
|
+
# end
|
113
133
|
end
|
114
134
|
end
|
115
135
|
end
|
116
136
|
|
117
|
-
def initialize(hash={})
|
118
|
-
unless hash["_id"]
|
119
|
-
self._id = UuidGenerator.uuid
|
120
|
-
end
|
121
|
-
|
137
|
+
def initialize(hash={})
|
122
138
|
@errors = Errors.new
|
123
139
|
@save_list = []
|
124
140
|
@validation_skip_list = []
|
125
|
-
|
126
|
-
#
|
127
|
-
|
141
|
+
|
142
|
+
# hash.dup because assigning references properties and defaults both
|
143
|
+
# modify the internal representation - @data. This messes with the
|
144
|
+
# iterator below that assigns vals to @data.
|
145
|
+
params = hash.dup
|
146
|
+
|
147
|
+
# If there's no rev, it's a new document
|
148
|
+
if hash["_rev"].nil?
|
149
|
+
# Clients may use symbols as keys so convert all to strings first.
|
150
|
+
@data = hash.map { |k,v| [k.to_s, v] }.to_hash
|
151
|
+
else
|
152
|
+
@data = hash
|
153
|
+
end
|
154
|
+
|
155
|
+
unless @data["_id"]
|
156
|
+
@data["_id"] = UuidGenerator.uuid
|
157
|
+
end
|
158
|
+
|
159
|
+
# It's a new doc, set default properties. We only do this after ensuring
|
160
|
+
# this obj first has an _id.
|
161
|
+
unless @data["_rev"]
|
162
|
+
default_methods = methods.select { |m| m =~ /__set_default/ }
|
163
|
+
default_methods.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9
|
128
164
|
properties.each do |prop|
|
129
|
-
|
130
|
-
|
131
|
-
|
165
|
+
if default_methods.include? "__set_default_#{prop}__".to_sym
|
166
|
+
send("__set_default_#{prop}__")
|
167
|
+
end
|
132
168
|
end
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
set_attributes(hash)
|
137
|
-
@set_derived_props = true
|
138
|
-
end
|
139
|
-
|
140
|
-
def set_attributes(data)
|
141
|
-
data.each do |key, val|
|
142
|
-
# Only set instance variables on creation - object references are resolved on demand
|
143
|
-
|
144
|
-
# If the variable name ends in _at, _on or _date try to convert it to a Time
|
145
|
-
if [/_at$/, /_on$/, /_date$/, /_time$/].inject(nil) { |i, r| i ||= (key =~ r) }
|
146
|
-
val = Time.parse(val).utc rescue val
|
169
|
+
|
170
|
+
params.each do |key, val|
|
171
|
+
send("#{key}=".to_sym, val)
|
147
172
|
end
|
148
173
|
|
149
|
-
|
150
|
-
# This allows us to comfortably accept a hash containing superflous data
|
151
|
-
# such as a params hash in a controller
|
152
|
-
send("#{key}=".to_sym, val) if methods.include? "#{key}="
|
174
|
+
@data["relaxdb_class"] = self.class.name
|
153
175
|
end
|
154
|
-
end
|
155
|
-
|
176
|
+
end
|
177
|
+
|
156
178
|
def inspect
|
157
179
|
s = "#<#{self.class}:#{self.object_id}"
|
158
180
|
properties.each do |prop|
|
159
|
-
prop_val =
|
181
|
+
prop_val = @data[prop.to_s]
|
160
182
|
s << ", #{prop}: #{prop_val.inspect}" if prop_val
|
161
183
|
end
|
162
|
-
self.class.
|
163
|
-
id =
|
184
|
+
self.class.references_rels.each do |relationship, opts|
|
185
|
+
id = @data["#{relationship}_id"]
|
164
186
|
s << ", #{relationship}_id: #{id}" if id
|
165
187
|
end
|
166
188
|
s << ", errors: #{errors.inspect}" unless errors.empty?
|
@@ -170,19 +192,12 @@ module RelaxDB
|
|
170
192
|
|
171
193
|
alias_method :to_s, :inspect
|
172
194
|
|
173
|
-
def to_json
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
properties.each do |prop|
|
180
|
-
prop_val = instance_variable_get("@#{prop}".to_sym)
|
181
|
-
data["#{prop}"] = prop_val if prop_val
|
182
|
-
end
|
183
|
-
data["errors"] = errors unless errors.empty?
|
184
|
-
data["relaxdb_class"] = self.class.name
|
185
|
-
data.to_json
|
195
|
+
def to_json
|
196
|
+
ref_rels = self.class.references_rels.map { |k, v| k.to_s }
|
197
|
+
@data.delete_if { |k,v| ref_rels.include? k }
|
198
|
+
|
199
|
+
@data["errors"] = errors unless errors.empty?
|
200
|
+
@data.to_json
|
186
201
|
end
|
187
202
|
|
188
203
|
# Not yet sure of final implemention for hooks - may lean more towards DM than AR
|
@@ -251,18 +266,20 @@ module RelaxDB
|
|
251
266
|
|
252
267
|
def validates?
|
253
268
|
props = properties - validation_skip_list
|
254
|
-
prop_vals = props.map { |prop|
|
269
|
+
prop_vals = props.map { |prop| @data[prop.to_s] }
|
255
270
|
|
256
|
-
rels = self.class.
|
257
|
-
rel_vals = rels.map { |rel|
|
271
|
+
rels = self.class.references_rels.keys - validation_skip_list
|
272
|
+
rel_vals = rels.map { |rel| @data["#{rel}_id"] }
|
258
273
|
|
259
274
|
att_names = props + rels
|
260
275
|
att_vals = prop_vals + rel_vals
|
261
276
|
|
262
|
-
total_success = true
|
277
|
+
total_success = true
|
278
|
+
validate_methods = methods.select { |m| m =~ /validate_/ }
|
279
|
+
validate_methods.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9
|
263
280
|
att_names.each_index do |i|
|
264
281
|
att_name, att_val = att_names[i], att_vals[i]
|
265
|
-
if
|
282
|
+
if validate_methods.include? "validate_#{att_name}".to_sym
|
266
283
|
total_success &= validate_att(att_name, att_val)
|
267
284
|
end
|
268
285
|
end
|
@@ -280,7 +297,9 @@ module RelaxDB
|
|
280
297
|
end
|
281
298
|
|
282
299
|
unless success
|
283
|
-
|
300
|
+
v_msg_meths = methods.select { |m | m =~ /_validation_msg/ }
|
301
|
+
v_msg_meths.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9
|
302
|
+
if v_msg_meths.include? "#{att_name}_validation_msg".to_sym
|
284
303
|
begin
|
285
304
|
@errors[att_name] = send("#{att_name}_validation_msg", att_val)
|
286
305
|
rescue => e
|
@@ -296,7 +315,7 @@ module RelaxDB
|
|
296
315
|
end
|
297
316
|
|
298
317
|
def new_document?
|
299
|
-
|
318
|
+
self._rev.nil?
|
300
319
|
end
|
301
320
|
alias_method :new_record?, :new_document?
|
302
321
|
alias_method :unsaved?, :new_document?
|
@@ -310,10 +329,10 @@ module RelaxDB
|
|
310
329
|
now = Time.now
|
311
330
|
if new_document? && respond_to?(:created_at)
|
312
331
|
# Don't override it if it's already been set
|
313
|
-
@created_at = now if @created_at.nil?
|
332
|
+
@data["created_at"] = now if @data["created_at"].nil?
|
314
333
|
end
|
315
334
|
|
316
|
-
@updated_at = now if respond_to?(:updated_at)
|
335
|
+
@data["updated_at"] = now if respond_to?(:updated_at)
|
317
336
|
end
|
318
337
|
|
319
338
|
def create_or_get_proxy(klass, relationship, opts=nil)
|
@@ -330,117 +349,28 @@ module RelaxDB
|
|
330
349
|
def ==(other)
|
331
350
|
other && _id == other._id
|
332
351
|
end
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
def self.references_many(relationship, opts={})
|
337
|
-
# Treat the representation as a standard property
|
338
|
-
properties << relationship
|
339
|
-
|
340
|
-
# Keep track of the relationship so peers can be disassociated on destroy
|
341
|
-
@references_many_rels ||= []
|
342
|
-
@references_many_rels << relationship
|
343
|
-
|
344
|
-
id_arr_sym = "@#{relationship}".to_sym
|
345
|
-
|
346
|
-
if RelaxDB.create_views?
|
347
|
-
target_class = opts[:class]
|
348
|
-
relationship_as_viewed_by_target = opts[:known_as].to_s
|
349
|
-
ViewCreator.references_many(self.name, relationship, target_class, relationship_as_viewed_by_target).save
|
350
|
-
end
|
351
|
-
|
352
|
-
define_method(relationship) do
|
353
|
-
instance_variable_set(id_arr_sym, []) unless instance_variable_defined? id_arr_sym
|
354
|
-
create_or_get_proxy(ReferencesManyProxy, relationship, opts)
|
355
|
-
end
|
356
|
-
|
357
|
-
define_method("#{relationship}_ids") do
|
358
|
-
instance_variable_set(id_arr_sym, []) unless instance_variable_defined? id_arr_sym
|
359
|
-
instance_variable_get(id_arr_sym)
|
360
|
-
end
|
361
|
-
|
362
|
-
define_method("#{relationship}=") do |val|
|
363
|
-
# Don't invoke this method unless you know what you're doing
|
364
|
-
instance_variable_set(id_arr_sym, val)
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
def self.references_many_rels
|
369
|
-
@references_many_rels ||= []
|
370
|
-
end
|
371
|
-
|
372
|
-
def self.has_many(relationship, opts={})
|
373
|
-
@has_many_rels ||= []
|
374
|
-
@has_many_rels << relationship
|
375
|
-
|
376
|
-
if RelaxDB.create_views?
|
377
|
-
target_class = opts[:class] || relationship.to_s.singularize.camel_case
|
378
|
-
relationship_as_viewed_by_target = (opts[:known_as] || self.name.snake_case).to_s
|
379
|
-
ViewCreator.has_n(self.name, relationship, target_class, relationship_as_viewed_by_target).save
|
380
|
-
end
|
381
|
-
|
382
|
-
define_method(relationship) do
|
383
|
-
create_or_get_proxy(HasManyProxy, relationship, opts)
|
384
|
-
end
|
385
|
-
|
386
|
-
define_method("#{relationship}=") do |children|
|
387
|
-
create_or_get_proxy(HasManyProxy, relationship, opts).children = children
|
388
|
-
write_derived_props(relationship) if @set_derived_props
|
389
|
-
children
|
390
|
-
end
|
391
|
-
end
|
392
|
-
|
393
|
-
def self.has_many_rels
|
394
|
-
# Don't force clients to check its instantiated
|
395
|
-
@has_many_rels ||= []
|
396
|
-
end
|
397
|
-
|
398
|
-
def self.has_one(relationship)
|
399
|
-
@has_one_rels ||= []
|
400
|
-
@has_one_rels << relationship
|
401
|
-
|
402
|
-
if RelaxDB.create_views?
|
403
|
-
target_class = relationship.to_s.camel_case
|
404
|
-
relationship_as_viewed_by_target = self.name.snake_case
|
405
|
-
ViewCreator.has_n(self.name, relationship, target_class, relationship_as_viewed_by_target).save
|
406
|
-
end
|
407
|
-
|
408
|
-
define_method(relationship) do
|
409
|
-
create_or_get_proxy(HasOneProxy, relationship).target
|
410
|
-
end
|
411
|
-
|
412
|
-
define_method("#{relationship}=") do |new_target|
|
413
|
-
create_or_get_proxy(HasOneProxy, relationship).target = new_target
|
414
|
-
write_derived_props(relationship) if @set_derived_props
|
415
|
-
new_target
|
416
|
-
end
|
417
|
-
end
|
418
|
-
|
419
|
-
def self.has_one_rels
|
420
|
-
@has_one_rels ||= []
|
421
|
-
end
|
422
|
-
|
423
|
-
def self.belongs_to(relationship, opts={})
|
424
|
-
belongs_to_rels[relationship] = opts
|
352
|
+
|
353
|
+
def self.references(relationship, opts={})
|
354
|
+
references_rels[relationship] = opts
|
425
355
|
|
426
356
|
define_method(relationship) do
|
427
|
-
create_or_get_proxy(
|
357
|
+
create_or_get_proxy(ReferencesProxy, relationship).target
|
428
358
|
end
|
429
359
|
|
430
360
|
define_method("#{relationship}=") do |new_target|
|
431
|
-
create_or_get_proxy(
|
432
|
-
write_derived_props(relationship)
|
361
|
+
create_or_get_proxy(ReferencesProxy, relationship).target = new_target
|
362
|
+
write_derived_props(relationship)
|
433
363
|
end
|
434
364
|
|
435
365
|
# Allows all writers to be invoked from the hash passed to initialize
|
436
366
|
define_method("#{relationship}_id=") do |id|
|
437
|
-
|
438
|
-
write_derived_props(relationship)
|
367
|
+
@data["#{relationship}_id"] = id
|
368
|
+
write_derived_props(relationship)
|
439
369
|
id
|
440
370
|
end
|
441
371
|
|
442
372
|
define_method("#{relationship}_id") do
|
443
|
-
|
373
|
+
@data["#{relationship}_id"]
|
444
374
|
end
|
445
375
|
|
446
376
|
create_validator(relationship, opts[:validator]) if opts[:validator]
|
@@ -449,39 +379,11 @@ module RelaxDB
|
|
449
379
|
create_validation_msg(relationship, opts[:validation_msg]) if opts[:validation_msg]
|
450
380
|
end
|
451
381
|
|
452
|
-
class << self
|
453
|
-
alias_method :references, :belongs_to
|
454
|
-
end
|
455
|
-
|
456
|
-
self.belongs_to_rels = {}
|
457
|
-
|
458
|
-
def self.all_relationships
|
459
|
-
belongs_to_rels + has_one_rels + has_many_rels + references_many_rels
|
460
|
-
end
|
461
|
-
|
462
382
|
def self.all params = {}
|
463
383
|
AllDelegator.new self.name, params
|
464
384
|
end
|
465
385
|
|
466
|
-
# destroy! nullifies all relationships with peers and children before deleting
|
467
|
-
# itself in CouchDB
|
468
|
-
# The nullification and deletion are not performed in a transaction
|
469
|
-
#
|
470
|
-
# TODO: Current implemention may be inappropriate - causing CouchDB to try to JSON
|
471
|
-
# encode undefined. Ensure nil is serialized? See has_many_spec#should nullify its child relationships
|
472
386
|
def destroy!
|
473
|
-
self.class.references_many_rels.each do |rel|
|
474
|
-
send(rel).clear
|
475
|
-
end
|
476
|
-
|
477
|
-
self.class.has_many_rels.each do |rel|
|
478
|
-
send(rel).clear
|
479
|
-
end
|
480
|
-
|
481
|
-
self.class.has_one_rels.each do |rel|
|
482
|
-
send("#{rel}=".to_sym, nil)
|
483
|
-
end
|
484
|
-
|
485
387
|
# Implicitly prevent the object from being resaved by failing to update its revision
|
486
388
|
RelaxDB.db.delete("#{_id}?rev=#{_rev}")
|
487
389
|
self
|
@@ -523,15 +425,15 @@ module RelaxDB
|
|
523
425
|
end
|
524
426
|
|
525
427
|
#
|
526
|
-
# Creates the corresponding view
|
428
|
+
# Creates the corresponding view, emitting the doc as the val
|
527
429
|
# Adds by_ and paginate_by_ methods to the class
|
528
430
|
#
|
529
|
-
def self.
|
431
|
+
def self.view_docs_by *atts
|
530
432
|
opts = atts.last.is_a?(Hash) ? atts.pop : {}
|
531
|
-
|
433
|
+
__view_docs_by_list__ << atts
|
532
434
|
|
533
435
|
if RelaxDB.create_views?
|
534
|
-
ViewCreator.
|
436
|
+
ViewCreator.docs_by_att_list([self.name], *atts).add_to_design_doc
|
535
437
|
end
|
536
438
|
|
537
439
|
by_name = "by_#{atts.join "_and_"}"
|
@@ -539,11 +441,11 @@ module RelaxDB
|
|
539
441
|
define_method by_name do |*params|
|
540
442
|
view_name = "#{self.name}_#{by_name}"
|
541
443
|
if params.empty?
|
542
|
-
|
444
|
+
RelaxDB.rf_view view_name, opts
|
543
445
|
elsif params[0].is_a? Hash
|
544
|
-
|
446
|
+
RelaxDB.rf_view view_name, opts.merge(params[0])
|
545
447
|
else
|
546
|
-
|
448
|
+
RelaxDB.rf_view(view_name, :key => params[0]).first
|
547
449
|
end
|
548
450
|
end
|
549
451
|
end
|
@@ -559,12 +461,38 @@ module RelaxDB
|
|
559
461
|
end
|
560
462
|
end
|
561
463
|
|
464
|
+
#
|
465
|
+
# Creates the corresponding view, emitting 1 as the val
|
466
|
+
# Adds a by_ method to this class, but does not add a
|
467
|
+
# paginate method.
|
468
|
+
#
|
469
|
+
def self.view_by *atts
|
470
|
+
opts = atts.last.is_a?(Hash) ? atts.pop : {}
|
471
|
+
opts = opts.merge :reduce => false
|
472
|
+
__view_by_list__ << atts
|
473
|
+
|
474
|
+
if RelaxDB.create_views?
|
475
|
+
ViewCreator.by_att_list([self.name], *atts).add_to_design_doc
|
476
|
+
end
|
477
|
+
|
478
|
+
by_name = "by_#{atts.join "_and_"}"
|
479
|
+
meta_class.instance_eval do
|
480
|
+
define_method by_name do |*params|
|
481
|
+
view_name = "#{self.name}_#{by_name}"
|
482
|
+
if params.empty?
|
483
|
+
ViewByDelegator.new(view_name, opts)
|
484
|
+
elsif params[0].is_a? Hash
|
485
|
+
ViewByDelegator.new(view_name, opts.merge(params[0]))
|
486
|
+
else
|
487
|
+
ViewByDelegator.new(view_name, opts.merge(:key => params[0])).load!.first
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
562
493
|
# Create a view allowing all instances of a particular class to be retreived
|
563
494
|
def self.create_all_by_class_view
|
564
|
-
if RelaxDB.create_views?
|
565
|
-
view = ViewCreator.all
|
566
|
-
view.save unless view.exists?
|
567
|
-
end
|
495
|
+
ViewCreator.all.add_to_design_doc if RelaxDB.create_views?
|
568
496
|
end
|
569
497
|
|
570
498
|
def self.inherited subclass
|
@@ -588,10 +516,14 @@ module RelaxDB
|
|
588
516
|
@hierarchy.uniq!
|
589
517
|
|
590
518
|
if RelaxDB.create_views?
|
591
|
-
ViewCreator.all(@hierarchy).
|
592
|
-
|
593
|
-
ViewCreator.
|
519
|
+
ViewCreator.all(@hierarchy).add_to_design_doc
|
520
|
+
__view_docs_by_list__.each do |atts|
|
521
|
+
ViewCreator.docs_by_att_list(@hierarchy, *atts).add_to_design_doc
|
594
522
|
end
|
523
|
+
|
524
|
+
__view_by_list__.each do |atts|
|
525
|
+
ViewCreator.by_att_list(@hierarchy, *atts).add_to_design_doc
|
526
|
+
end
|
595
527
|
end
|
596
528
|
end
|
597
529
|
|