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.
Files changed (49) hide show
  1. data/README.textile +21 -23
  2. data/Rakefile +2 -7
  3. data/docs/spec_results.html +5 -5
  4. data/lib/more/grapher.rb +1 -1
  5. data/lib/relaxdb.rb +3 -5
  6. data/lib/relaxdb/all_delegator.rb +19 -13
  7. data/lib/relaxdb/document.rb +150 -218
  8. data/lib/relaxdb/extlib.rb +7 -1
  9. data/lib/relaxdb/migration.rb +11 -8
  10. data/lib/relaxdb/net_http_server.rb +19 -1
  11. data/lib/relaxdb/paginator.rb +30 -11
  12. data/lib/relaxdb/query.rb +1 -1
  13. data/lib/relaxdb/{belongs_to_proxy.rb → references_proxy.rb} +3 -3
  14. data/lib/relaxdb/relaxdb.rb +87 -7
  15. data/lib/relaxdb/server.rb +8 -2
  16. data/lib/relaxdb/taf2_curb_server.rb +2 -1
  17. data/lib/relaxdb/uuid_generator.rb +38 -2
  18. data/lib/relaxdb/view_by_delegator.rb +34 -0
  19. data/lib/relaxdb/view_object.rb +1 -1
  20. data/lib/relaxdb/view_uploader.rb +16 -2
  21. data/lib/relaxdb/views.rb +23 -55
  22. data/readme.rb +3 -3
  23. data/spec/all_delegator_spec.rb +52 -0
  24. data/spec/callbacks_spec.rb +4 -4
  25. data/spec/derived_properties_spec.rb +4 -4
  26. data/spec/design_doc_spec.rb +2 -2
  27. data/spec/doc_inheritable_spec.rb +2 -2
  28. data/spec/document_spec.rb +47 -25
  29. data/spec/migration_spec.rb +12 -10
  30. data/spec/qpaginate_spec.rb +88 -0
  31. data/spec/query_spec.rb +2 -2
  32. data/spec/references_proxy_spec.rb +94 -0
  33. data/spec/relaxdb_spec.rb +29 -21
  34. data/spec/server_spec.rb +4 -3
  35. data/spec/spec_helper.rb +1 -0
  36. data/spec/spec_models.rb +48 -57
  37. data/spec/uuid_generator_spec.rb +34 -0
  38. data/spec/view_by_spec.rb +62 -54
  39. data/spec/view_docs_by_spec.rb +85 -0
  40. metadata +38 -27
  41. data/lib/more/atomic_bulk_save_support.rb +0 -18
  42. data/lib/relaxdb/has_many_proxy.rb +0 -101
  43. data/lib/relaxdb/has_one_proxy.rb +0 -42
  44. data/lib/relaxdb/references_many_proxy.rb +0 -97
  45. data/spec/belongs_to_spec.rb +0 -124
  46. data/spec/has_many_spec.rb +0 -202
  47. data/spec/has_one_spec.rb +0 -123
  48. data/spec/references_many_spec.rb +0 -173
  49. data/spec/view_spec.rb +0 -23
data/README.textile CHANGED
@@ -1,16 +1,15 @@
1
1
  h3. What's New?
2
2
 
3
- * 2009-08-15
4
- ** A few tweaks, patches and fixes push the version to 0.3.5, compatible with CouchDB 0.9.1 and the 0.10 branch.
5
- ** The Rails error_messages_for helper is now supported. Thanks to "Balint Erdi":http://github.com/balinterdi.
6
- * 2009-05-27
7
- ** Added minimal support for data migrations. Although CouchDB's nature removes the necessity for migrations, certain knowledge that all objects possess a particular property can simplify client logic. This desire for simplification is the rationale behind this change.
8
- * 2009-04-19
9
- ** Defaults to taf2-curb, falling back to Net/HTTP if it taf2-curb can't be loaded. Thanks to "Fred Cheung":http://www.spacevatican.org/2009/4/13/fun-with-ruby-http-clients.
10
- ** For those interested in using RelaxDB with an ETag based cache, "look here":http://github.com/fcheung/relaxdb/commit/1d9acfd5f6b3c23da0d275252b6a6e064865440e
11
-
12
- * 2009-03-31
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 # creates views when class definition is executed
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
- view_by :sender_id
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
- ix = Invite.by_sender_name(:key => "sofa").first
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
- if (rereduce) {
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.3.5"
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 %{sudo gem install --local pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
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'
@@ -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 belongs_to</dt>
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 belongs_to validator</dt>
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 belongs_to relationship</span></dd>
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">view_by</dt>
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">view_by view_by</dt>
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.belongs_to_rels.each do |relationship, opts|
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/references_many_proxy'
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 @objs
19
- @objs = RelaxDB.rf_view "#{@class_name}_all", @params
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
- @objs
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
- __getobj__
32
- @objs.each do |o|
33
- # A reload is required for deleting objects with a self referential references_many relationship
34
- # This makes all.destroy! very slow. Change if needed
35
- # obj = RelaxDB.load(o._id)
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
@@ -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 object to
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 :belongs_to_rels, :reader => true
28
- self.belongs_to_rels = {}
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
- define_method(prop) do
34
- instance_variable_get("@#{prop}".to_sym)
35
- end
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
- instance_variable_set("@#{prop}".to_sym, val)
52
+ @data[prop.to_s] = val
39
53
  end
40
54
 
41
55
  if opts[:default]
42
- define_method("set_default_#{prop}") do
43
- default = opts[:default]
44
- default = default.is_a?(Proc) ? default.call : default
45
- instance_variable_set("@#{prop}".to_sym, default)
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
- define_method(method_name) { |att_val| send(v, att_val) }
76
- end
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
- RelaxDB.logger.error "Deriving #{prop} from #{source} raised #{e}"
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
- # Set default properties if this object isn't being loaded from CouchDB
127
- unless hash["_rev"]
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
- if methods.include?("set_default_#{prop}")
130
- send("set_default_#{prop}")
131
- end
165
+ if default_methods.include? "__set_default_#{prop}__".to_sym
166
+ send("__set_default_#{prop}__")
167
+ end
132
168
  end
133
- end
134
-
135
- @set_derived_props = hash["_rev"] ? false : true
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
- # Ignore param keys that don't have a corresponding writer
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 = instance_variable_get("@#{prop}".to_sym)
181
+ prop_val = @data[prop.to_s]
160
182
  s << ", #{prop}: #{prop_val.inspect}" if prop_val
161
183
  end
162
- self.class.belongs_to_rels.each do |relationship, opts|
163
- id = instance_variable_get("@#{relationship}_id".to_sym)
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
- data = {}
175
- self.class.belongs_to_rels.each do |relationship, opts|
176
- id = instance_variable_get("@#{relationship}_id".to_sym)
177
- data["#{relationship}_id"] = id if id
178
- end
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| instance_variable_get("@#{prop}") }
269
+ prop_vals = props.map { |prop| @data[prop.to_s] }
255
270
 
256
- rels = self.class.belongs_to_rels.keys - validation_skip_list
257
- rel_vals = rels.map { |rel| instance_variable_get("@#{rel}_id") }
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 methods.include? "validate_#{att_name}"
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
- if methods.include? "#{att_name}_validation_msg"
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
- @_rev.nil?
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
- # If you're using this method, read the specs and make sure you understand
335
- # how it can be used and how it shouldn't be used
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(BelongsToProxy, relationship).target
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(BelongsToProxy, relationship).target = new_target
432
- write_derived_props(relationship) if @set_derived_props
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
- instance_variable_set("@#{relationship}_id".to_sym, id)
438
- write_derived_props(relationship) if @set_derived_props
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
- instance_variable_get("@#{relationship}_id")
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 and stores it in CouchDB
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.view_by *atts
431
+ def self.view_docs_by *atts
530
432
  opts = atts.last.is_a?(Hash) ? atts.pop : {}
531
- __view_by_list__ << atts
433
+ __view_docs_by_list__ << atts
532
434
 
533
435
  if RelaxDB.create_views?
534
- ViewCreator.by_att_list([self.name], *atts).save
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
- res = RelaxDB.rf_view view_name, opts
444
+ RelaxDB.rf_view view_name, opts
543
445
  elsif params[0].is_a? Hash
544
- res = RelaxDB.rf_view view_name, opts.merge(params[0])
446
+ RelaxDB.rf_view view_name, opts.merge(params[0])
545
447
  else
546
- res = RelaxDB.rf_view(view_name, :key => params[0]).first
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).save
592
- __view_by_list__.each do |atts|
593
- ViewCreator.by_att_list(@hierarchy, *atts).save
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