couchrest_model 1.1.2 → 1.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.md +8 -2
  2. data/VERSION +1 -1
  3. data/couchrest_model.gemspec +2 -1
  4. data/history.md +8 -0
  5. data/lib/couchrest/model/base.rb +0 -20
  6. data/lib/couchrest/model/configuration.rb +2 -0
  7. data/lib/couchrest/model/core_extensions/time_parsing.rb +35 -9
  8. data/lib/couchrest/model/designs/design.rb +182 -0
  9. data/lib/couchrest/model/designs/view.rb +91 -48
  10. data/lib/couchrest/model/designs.rb +72 -19
  11. data/lib/couchrest/model/document_queries.rb +15 -45
  12. data/lib/couchrest/model/properties.rb +43 -2
  13. data/lib/couchrest/model/proxyable.rb +20 -54
  14. data/lib/couchrest/model/typecast.rb +1 -1
  15. data/lib/couchrest/model/validations/uniqueness.rb +7 -6
  16. data/lib/couchrest_model.rb +1 -5
  17. data/spec/fixtures/models/article.rb +22 -20
  18. data/spec/fixtures/models/base.rb +15 -7
  19. data/spec/fixtures/models/course.rb +7 -4
  20. data/spec/fixtures/models/project.rb +4 -1
  21. data/spec/fixtures/models/sale_entry.rb +5 -3
  22. data/spec/unit/base_spec.rb +51 -5
  23. data/spec/unit/core_extensions/time_parsing.rb +41 -0
  24. data/spec/unit/designs/design_spec.rb +291 -0
  25. data/spec/unit/designs/view_spec.rb +135 -40
  26. data/spec/unit/designs_spec.rb +341 -30
  27. data/spec/unit/dirty_spec.rb +67 -0
  28. data/spec/unit/inherited_spec.rb +2 -2
  29. data/spec/unit/property_protection_spec.rb +3 -1
  30. data/spec/unit/property_spec.rb +43 -3
  31. data/spec/unit/proxyable_spec.rb +57 -98
  32. data/spec/unit/subclass_spec.rb +14 -5
  33. data/spec/unit/validations_spec.rb +14 -12
  34. metadata +172 -129
  35. data/lib/couchrest/model/class_proxy.rb +0 -135
  36. data/lib/couchrest/model/collection.rb +0 -273
  37. data/lib/couchrest/model/design_doc.rb +0 -115
  38. data/lib/couchrest/model/support/couchrest_design.rb +0 -33
  39. data/lib/couchrest/model/views.rb +0 -148
  40. data/spec/unit/class_proxy_spec.rb +0 -167
  41. data/spec/unit/collection_spec.rb +0 -86
  42. data/spec/unit/design_doc_spec.rb +0 -212
  43. data/spec/unit/view_spec.rb +0 -352
data/README.md CHANGED
@@ -21,7 +21,11 @@ it is not possible to load ActiveModel into programs that do not use ActiveSuppo
21
21
 
22
22
  CouchRest Model is only properly tested on CouchDB version 1.0 or newer.
23
23
 
24
- *WARNING:* As of April 2011 and the release of version 1.1.0, the default model type key is 'type' instead of 'couchrest-type'. Simply updating your project will not work unless you migrate your data or set the configuration option in your initializers:
24
+ ### Upgrading from an earlier version?
25
+
26
+ *Pre 1.2:* As of June 2012, couchrest model no longer supports the `view_by` and `view` calls from the model. Views are no only accessed via a design document. If you have older code and wish to upgrade, please ensure you move to the new syntax for using views.
27
+
28
+ *Pre 1.1:* As of April 2011 and the release of version 1.1.0, the default model type key is 'type' instead of 'couchrest-type'. Simply updating your project will not work unless you migrate your data or set the configuration option in your initializers:
25
29
 
26
30
  CouchRest::Model::Base.configure do |config|
27
31
  config.model_type_key = 'couchrest-type'
@@ -91,7 +95,9 @@ The example config above for example would use a database called "project_test".
91
95
 
92
96
  timestamps!
93
97
 
94
- view_by :name
98
+ design do
99
+ view :by_name
100
+ end
95
101
 
96
102
  end
97
103
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.2.0.beta
@@ -25,11 +25,12 @@ Gem::Specification.new do |s|
25
25
 
26
26
  s.add_dependency(%q<couchrest>, "~> 1.1.2")
27
27
  s.add_dependency(%q<mime-types>, "~> 1.15")
28
- s.add_dependency(%q<activemodel>, "~> 3.0")
28
+ s.add_dependency(%q<activemodel>, "~> 3.1.0")
29
29
  s.add_dependency(%q<tzinfo>, "~> 0.3.22")
30
30
  s.add_development_dependency(%q<rspec>, "~> 2.6.0")
31
31
  s.add_development_dependency(%q<json>, ["~> 1.5.1"])
32
32
  s.add_development_dependency(%q<rack-test>, ">= 0.5.7")
33
+ s.add_development_dependency("rake", ">= 0.8.0")
33
34
  # s.add_development_dependency("jruby-openssl", ">= 0.7.3")
34
35
  end
35
36
 
data/history.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CouchRest Model Change History
2
2
 
3
+ ## 1.2.0.beta - 2012-06-08
4
+
5
+ * Completely refactored Design Document handling.
6
+ * Removed old `view` and `view_by` methods.
7
+ * CouchRest::Model::Base.respond_to_missing? and respond_to? (Kim Burgestrand)
8
+ * Time#as_json now insists on using xmlschema with 3 fraction digits by default.
9
+ * Added time_fraction_digits configuration object
10
+
3
11
  ## 1.1.2 - 2011-07-23
4
12
 
5
13
  * Minor fixes
@@ -8,12 +8,8 @@ module CouchRest
8
8
  include CouchRest::Model::Connection
9
9
  include CouchRest::Model::Persistence
10
10
  include CouchRest::Model::DocumentQueries
11
- include CouchRest::Model::Views
12
- include CouchRest::Model::DesignDoc
13
11
  include CouchRest::Model::ExtendedAttachments
14
- include CouchRest::Model::ClassProxy
15
12
  include CouchRest::Model::Proxyable
16
- include CouchRest::Model::Collection
17
13
  include CouchRest::Model::PropertyProtection
18
14
  include CouchRest::Model::Associations
19
15
  include CouchRest::Model::Validations
@@ -21,7 +17,6 @@ module CouchRest
21
17
  include CouchRest::Model::Designs
22
18
  include CouchRest::Model::CastedBy
23
19
  include CouchRest::Model::Dirty
24
- include CouchRest::Model::Callbacks
25
20
 
26
21
  def self.subclasses
27
22
  @subclasses ||= []
@@ -67,21 +62,6 @@ module CouchRest
67
62
  run_callbacks(:initialize) { self }
68
63
  end
69
64
 
70
-
71
- # Temp solution to make the view_by methods available
72
- def self.method_missing(m, *args, &block)
73
- if has_view?(m)
74
- query = args.shift || {}
75
- return view(m, query, *args, &block)
76
- elsif m.to_s =~ /^find_(by_.+)/
77
- view_name = $1
78
- if has_view?(view_name)
79
- return first_from_view(view_name, *args)
80
- end
81
- end
82
- super
83
- end
84
-
85
65
  def to_key
86
66
  new? ? nil : [id]
87
67
  end
@@ -14,11 +14,13 @@ module CouchRest
14
14
  add_config :environment
15
15
  add_config :connection
16
16
  add_config :connection_config_file
17
+ add_config :time_fraction_digits
17
18
 
18
19
  configure do |config|
19
20
  config.model_type_key = 'type' # was 'couchrest-type'
20
21
  config.mass_assign_any_attribute = false
21
22
  config.auto_update_design_doc = true
23
+ config.time_fraction_digits = 3
22
24
 
23
25
  config.environment = :development
24
26
  config.connection_config_file = File.join(Dir.pwd, 'config', 'couchdb.yml')
@@ -1,6 +1,7 @@
1
1
  module CouchRest
2
2
  module Model
3
3
  module CoreExtensions
4
+
4
5
  module TimeParsing
5
6
 
6
7
  if RUBY_VERSION < "1.9.0"
@@ -33,22 +34,22 @@ module CouchRest
33
34
  # UTC.
34
35
  #
35
36
  def parse_iso8601(string)
36
- if (string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2})(Z| ?([\+|\s|\-])?(\d{2}):?(\d{2}))?/)
37
+ if (string =~ /(\d{4})[\-|\/](\d{2})[\-|\/](\d{2})[T|\s](\d{2}):(\d{2}):(\d{2}(\.\d+)?)(Z| ?([\+|\s|\-])?(\d{2}):?(\d{2}))?/)
37
38
  # $1 = year
38
39
  # $2 = month
39
40
  # $3 = day
40
41
  # $4 = hours
41
42
  # $5 = minutes
42
- # $6 = seconds
43
- # $7 = UTC or Timezone
44
- # $8 = time zone direction
45
- # $9 = tz difference hours
46
- # $10 = tz difference minutes
43
+ # $6 = seconds (with $7 for fraction)
44
+ # $8 = UTC or Timezone
45
+ # $9 = time zone direction
46
+ # $10 = tz difference hours
47
+ # $11 = tz difference minutes
47
48
 
48
- if (!$7.to_s.empty? && $7 != 'Z')
49
- new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, "#{$8 == '-' ? '-' : '+'}#{$9}:#{$10}")
49
+ if $8 == 'Z' || $8.to_s.empty?
50
+ utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_f)
50
51
  else
51
- utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
52
+ new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_f, "#{$9 == '-' ? '-' : '+'}#{$10}:#{$11}")
52
53
  end
53
54
  else
54
55
  parse(string)
@@ -56,11 +57,36 @@ module CouchRest
56
57
  end
57
58
 
58
59
  end
60
+
59
61
  end
60
62
  end
61
63
  end
62
64
 
63
65
  Time.class_eval do
64
66
  extend CouchRest::Model::CoreExtensions::TimeParsing
67
+
68
+ # Override the ActiveSupport's Time#as_json method to ensure that we *always* encode
69
+ # using the iso8601 format and include fractional digits (3 by default).
70
+ #
71
+ # Including miliseconds in Time is very important for CouchDB to ensure that order
72
+ # is preserved between models created in the same second.
73
+ #
74
+ # The number of fraction digits can be set by providing it in the options:
75
+ #
76
+ # time.as_json(:fraction_digits => 6)
77
+ #
78
+ # The CouchRest Model +time_fraction_digits+ configuration option is used for the
79
+ # default fraction. Given the global nature of Time#as_json method, this configuration
80
+ # option can only be set for the whole project.
81
+ #
82
+ # CouchRest::Model::Base.time_fraction_digits = 6
83
+ #
84
+
85
+ def as_json(options = {})
86
+ digits = options ? options[:fraction_digits] : nil
87
+ fraction = digits || CouchRest::Model::Base.time_fraction_digits
88
+ xmlschema(fraction)
89
+ end
90
+
65
91
  end
66
92
 
@@ -0,0 +1,182 @@
1
+
2
+ module CouchRest
3
+ module Model
4
+ module Designs
5
+
6
+ class Design < ::CouchRest::Design
7
+
8
+ # The model Class that this design belongs to and method name
9
+ attr_accessor :model, :method_name
10
+
11
+ # Can this design save itself to the database?
12
+ # If false, the design will be loaded automatically before a view is executed.
13
+ attr_accessor :auto_update
14
+
15
+
16
+ # Instantiate a new design document for this model
17
+ def initialize(model, prefix = nil)
18
+ self.model = model
19
+ self.method_name = self.class.method_name(prefix)
20
+ suffix = prefix ? "_#{prefix}" : ''
21
+ self["_id"] = "_design/#{model.to_s}#{suffix}"
22
+ apply_defaults
23
+ end
24
+
25
+ def sync(db = nil)
26
+ if auto_update
27
+ db ||= database
28
+ if cache_checksum(db) != checksum
29
+ sync!(db)
30
+ set_cache_checksum(db, checksum)
31
+ end
32
+ end
33
+ self
34
+ end
35
+
36
+ def sync!(db = nil)
37
+ db ||= database
38
+
39
+ # Load up the last copy. We never blindly overwrite the remote copy
40
+ # as it may contain views that are not used or known about by
41
+ # our model.
42
+ doc = load_from_database(db)
43
+
44
+ if !doc || doc['couchrest-hash'] != checksum
45
+ # We need to save something
46
+ if doc
47
+ # Different! Update.
48
+ doc.merge!(to_hash)
49
+ else
50
+ # No previous doc, use a *copy* of our version.
51
+ # Using a copy prevents reverse updates.
52
+ doc = to_hash.dup
53
+ end
54
+ db.save_doc(doc)
55
+ end
56
+
57
+ self
58
+ end
59
+
60
+
61
+ def checksum
62
+ sum = self['couchrest-hash']
63
+ if sum && (@_original_hash == to_hash)
64
+ sum
65
+ else
66
+ checksum!
67
+ end
68
+ end
69
+
70
+ def database
71
+ model.database
72
+ end
73
+
74
+ # Override the default #uri method for one that accepts
75
+ # the current database.
76
+ # This is used by the caching code.
77
+ def uri(db = database)
78
+ "#{db.root}/#{self['_id']}"
79
+ end
80
+
81
+
82
+ ######## VIEW HANDLING ########
83
+
84
+ # Create a new view object.
85
+ # This overrides the normal CouchRest Design view method
86
+ def view(name, opts = {})
87
+ CouchRest::Model::Designs::View.new(self, model, opts, name)
88
+ end
89
+
90
+ # Helper method to provide a list of all the views
91
+ def view_names
92
+ self['views'].keys
93
+ end
94
+
95
+ def has_view?(name)
96
+ view_names.include?(name.to_s)
97
+ end
98
+
99
+ # Add the specified view to the design doc the definition was made in
100
+ # and create quick access methods in the model.
101
+ def create_view(name, opts = {})
102
+ View.define_and_create(self, name, opts)
103
+ end
104
+
105
+ ######## FILTER HANDLING ########
106
+
107
+ def create_filter(name, function)
108
+ filters = (self['filters'] ||= {})
109
+ filters[name.to_s] = function
110
+ end
111
+
112
+ protected
113
+
114
+ def load_from_database(db = database)
115
+ db.get(self['_id'])
116
+ rescue RestClient::ResourceNotFound
117
+ nil
118
+ end
119
+
120
+ # Calculate and update the checksum of the Design document.
121
+ # Used for ensuring the latest version has been sent to the database.
122
+ #
123
+ # This will generate an flatterned, ordered array of all the elements of the
124
+ # design document, convert to string then generate an MD5 Hash. This should
125
+ # result in a consisitent Hash accross all platforms.
126
+ #
127
+ def checksum!
128
+ # Get a deep copy of hash to compare with
129
+ @_original_hash = Marshal.load(Marshal.dump(to_hash))
130
+ # create a copy of basic elements
131
+ base = self.dup
132
+ base.delete('_id')
133
+ base.delete('_rev')
134
+ base.delete('couchrest-hash')
135
+ result = nil
136
+ flatten =
137
+ lambda {|r|
138
+ (recurse = lambda {|v|
139
+ if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
140
+ v.to_a.map{|v| recurse.call(v)}.flatten
141
+ elsif v.is_a?(Array)
142
+ v.flatten.map{|v| recurse.call(v)}
143
+ else
144
+ v.to_s
145
+ end
146
+ }).call(r)
147
+ }
148
+ self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
149
+ end
150
+
151
+ def cache
152
+ Thread.current[:couchrest_design_cache] ||= {}
153
+ end
154
+ def cache_checksum(db)
155
+ cache[uri(db)]
156
+ end
157
+ def set_cache_checksum(db, checksum)
158
+ cache[uri(db)] = checksum
159
+ end
160
+
161
+ def apply_defaults
162
+ merge!(
163
+ "language" => "javascript",
164
+ "views" => { }
165
+ )
166
+ end
167
+
168
+
169
+ class << self
170
+
171
+ def method_name(prefix = nil)
172
+ (prefix ? "#{prefix}_" : '') + 'design_doc'
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+
@@ -14,20 +14,22 @@ module CouchRest
14
14
  class View
15
15
  include Enumerable
16
16
 
17
- attr_accessor :owner, :model, :name, :query, :result
17
+ attr_accessor :owner, :model, :design_doc, :name, :query, :result
18
18
 
19
19
  # Initialize a new View object. This method should not be called from
20
20
  # outside CouchRest Model.
21
- def initialize(parent, new_query = {}, name = nil)
21
+ def initialize(design_doc, parent, new_query = {}, name = nil)
22
+ self.design_doc = design_doc
23
+ proxy = new_query.delete(:proxy)
22
24
  if parent.is_a?(Class) && parent < CouchRest::Model::Base
23
25
  raise "Name must be provided for view to be initialized" if name.nil?
24
- self.model = parent
26
+ self.model = (proxy || parent)
25
27
  self.owner = parent
26
28
  self.name = name.to_s
27
29
  # Default options:
28
30
  self.query = { }
29
31
  elsif parent.is_a?(self.class)
30
- self.model = (new_query.delete(:proxy) || parent.model)
32
+ self.model = (proxy || parent.model)
31
33
  self.owner = parent.owner
32
34
  self.name = parent.name
33
35
  self.query = parent.query.dup
@@ -271,7 +273,7 @@ module CouchRest
271
273
  end
272
274
 
273
275
  # Use the reduce function on the view. If none is available this method
274
- # will fail.
276
+ # will fail.
275
277
  def reduce
276
278
  raise "Cannot reduce a view without a reduce method" unless can_reduce?
277
279
  update_query(:reduce => true, :include_docs => nil)
@@ -301,6 +303,17 @@ module CouchRest
301
303
 
302
304
  ### Special View Filter Methods
303
305
 
306
+ # Allow the results of a query to be provided "stale". Setting to 'ok'
307
+ # will disable all view updates for the query.
308
+ # When 'update_after' is provided the index will be update after the
309
+ # result has been returned.
310
+ def stale(value)
311
+ unless (['ok', 'update_after'].include?(value.to_s))
312
+ raise "View#stale can only be set with 'ok' or 'update_after'."
313
+ end
314
+ update_query(:stale => value.to_s)
315
+ end
316
+
304
317
  # Specify the database the view should use. If not defined,
305
318
  # an attempt will be made to load its value from the model.
306
319
  def database(value)
@@ -309,8 +322,8 @@ module CouchRest
309
322
 
310
323
  # Set the view's proxy that will be used instead of the model
311
324
  # for any future searches. As soon as this enters the
312
- # new object's initializer it will be removed and replace
313
- # the model object.
325
+ # new view's initializer it will be removed and set as the model
326
+ # object.
314
327
  #
315
328
  # See the Proxyable mixin for more details.
316
329
  #
@@ -380,11 +393,7 @@ module CouchRest
380
393
  end
381
394
 
382
395
  def update_query(new_query = {})
383
- self.class.new(self, new_query)
384
- end
385
-
386
- def design_doc
387
- model.design_doc
396
+ self.class.new(design_doc, self, new_query)
388
397
  end
389
398
 
390
399
  def can_reduce?
@@ -402,27 +411,33 @@ module CouchRest
402
411
  # Remove the reduce value if its not needed to prevent CouchDB errors
403
412
  query.delete(:reduce) unless can_reduce?
404
413
 
405
- model.save_design_doc(use_database)
414
+ design_doc.sync(use_database)
406
415
 
407
- self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
416
+ self.result = design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
408
417
  end
409
418
 
410
419
  # Class Methods
411
420
  class << self
412
- # Simplified view creation. A new view will be added to the
413
- # provided model's design document using the name and options.
421
+
422
+ def define_and_create(design_doc, name, opts = {})
423
+ define(design_doc, name, opts)
424
+ create_model_methods(design_doc, name, opts)
425
+ end
426
+
427
+ # Simplified view definition. A new view will be added to the
428
+ # provided design document using the name and options.
414
429
  #
415
430
  # If the view name starts with "by_" and +:by+ is not provided in
416
431
  # the options, the new view's map method will be interpreted and
417
432
  # generated automatically. For example:
418
433
  #
419
- # View.create(Meeting, "by_date_and_name")
434
+ # View.define(Meeting, design, "by_date_and_name")
420
435
  #
421
436
  # Will create a view that searches by the date and name properties.
422
437
  # Explicity setting the attributes to use is possible using the
423
438
  # +:by+ option. For example:
424
439
  #
425
- # View.create(Meeting, "by_date_and_name", :by => [:date, :firstname, :lastname])
440
+ # View.define(Meeting, design, "by_date_and_name", :by => [:date, :firstname, :lastname])
426
441
  #
427
442
  # The view name is the same, but three keys would be used in the
428
443
  # subsecuent index.
@@ -437,44 +452,72 @@ module CouchRest
437
452
  # like to enable this, set the <tt>:allow_blank</tt> option to false. The default
438
453
  # is true, empty strings are permited in the indexes.
439
454
  #
440
- def create(model, name, opts = {})
441
-
442
- unless opts[:map]
443
- if opts[:by].nil? && name.to_s =~ /^by_(.+)/
444
- opts[:by] = $1.split(/_and_/)
445
- end
446
-
447
- raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
448
-
449
- opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
450
- opts[:guards] ||= []
451
- opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
452
-
453
- keys = opts[:by].map{|o| "doc['#{o}']"}
454
- emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
455
- opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
456
- opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
457
- opts[:map] = <<-EOF
458
- function(doc) {
459
- if (#{opts[:guards].join(' && ')}) {
460
- emit(#{emit}, 1);
455
+ def define(design_doc, name, opts = {})
456
+ # Don't create the map or reduce method if auto updates are disabled
457
+ if design_doc.auto_update
458
+ model = design_doc.model
459
+ # Is this an all view?
460
+ if name.to_s == 'all'
461
+ opts[:map] = <<-EOF
462
+ function(doc) {
463
+ if (doc['#{model.model_type_key}'] == '#{model.to_s}') {
464
+ emit(doc._id, null);
465
+ }
466
+ }
467
+ EOF
468
+ elsif !opts[:map]
469
+ if opts[:by].nil? && name.to_s =~ /^by_(.+)/
470
+ opts[:by] = $1.split(/_and_/)
471
+ end
472
+
473
+ raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
474
+
475
+ opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
476
+ opts[:guards] ||= []
477
+ opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
478
+
479
+ keys = opts[:by].map{|o| "doc['#{o}']"}
480
+ emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
481
+ opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
482
+ opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
483
+ opts[:map] = <<-EOF
484
+ function(doc) {
485
+ if (#{opts[:guards].join(' && ')}) {
486
+ emit(#{emit}, 1);
487
+ }
461
488
  }
462
- }
463
- EOF
464
- opts[:reduce] = <<-EOF
465
- function(key, values, rereduce) {
466
- return sum(values);
467
- }
468
- EOF
489
+ EOF
490
+ opts[:reduce] = <<-EOF
491
+ function(key, values, rereduce) {
492
+ return sum(values);
493
+ }
494
+ EOF
495
+ end
496
+ else
497
+ # Assume there is always a map method
498
+ opts[:map] ||= true
469
499
  end
470
500
 
471
- model.design_doc['views'] ||= {}
472
- view = model.design_doc['views'][name.to_s] = { }
501
+ design_doc['views'] ||= {}
502
+ view = design_doc['views'][name.to_s] = { }
473
503
  view['map'] = opts[:map]
474
504
  view['reduce'] = opts[:reduce] if opts[:reduce]
475
505
  view
476
506
  end
477
507
 
508
+
509
+ def create_model_methods(design_doc, name, opts = {})
510
+ method = design_doc.method_name
511
+ design_doc.model.instance_eval <<-EOS, __FILE__, __LINE__ + 1
512
+ def #{name}(opts = {})
513
+ #{method}.view('#{name}', opts)
514
+ end
515
+ def find_#{name}(*key)
516
+ #{name}.key(*key).first()
517
+ end
518
+ EOS
519
+ end
520
+
478
521
  end
479
522
 
480
523
  end