relaxdb 0.3.5 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|