couchrest_model 2.0.0.beta2 → 2.0.0

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 (41) hide show
  1. data/.travis.yml +8 -0
  2. data/Gemfile +1 -1
  3. data/README.md +1 -1
  4. data/Rakefile +9 -24
  5. data/VERSION +1 -1
  6. data/couchrest_model.gemspec +7 -5
  7. data/history.md +17 -1
  8. data/lib/couchrest/model/associations.rb +16 -11
  9. data/lib/couchrest/model/base.rb +17 -15
  10. data/lib/couchrest/model/casted_array.rb +7 -1
  11. data/lib/couchrest/model/core_extensions/time_parsing.rb +0 -23
  12. data/lib/couchrest/model/design.rb +282 -0
  13. data/lib/couchrest/model/designs/design_mapper.rb +79 -0
  14. data/lib/couchrest/model/designs/view.rb +9 -6
  15. data/lib/couchrest/model/designs.rb +37 -70
  16. data/lib/couchrest/model/persistence.rb +5 -5
  17. data/lib/couchrest/model/properties.rb +5 -16
  18. data/lib/couchrest/model/property.rb +34 -16
  19. data/lib/couchrest/model/translation.rb +22 -0
  20. data/lib/couchrest/model/typecast.rb +54 -43
  21. data/lib/couchrest/model/utils/migrate.rb +106 -0
  22. data/lib/couchrest_model.rb +4 -2
  23. data/lib/tasks/migrations.rake +5 -5
  24. data/spec/fixtures/models/course.rb +1 -0
  25. data/spec/fixtures/models/designs.rb +22 -0
  26. data/spec/spec_helper.rb +1 -0
  27. data/spec/unit/assocations_spec.rb +7 -0
  28. data/spec/unit/base_spec.rb +3 -1
  29. data/spec/unit/{designs/design_spec.rb → design_spec.rb} +6 -6
  30. data/spec/unit/designs/design_mapper_spec.rb +124 -0
  31. data/spec/unit/designs/view_spec.rb +30 -4
  32. data/spec/unit/designs_spec.rb +5 -140
  33. data/spec/unit/dirty_spec.rb +15 -1
  34. data/spec/unit/embeddable_spec.rb +2 -2
  35. data/spec/unit/property_spec.rb +70 -28
  36. data/spec/unit/translations_spec.rb +31 -0
  37. data/spec/unit/typecast_spec.rb +99 -19
  38. data/spec/unit/utils/migrate_spec.rb +25 -0
  39. metadata +43 -19
  40. data/lib/couchrest/model/designs/design.rb +0 -284
  41. data/lib/couchrest/model/migrate.rb +0 -92
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ rvm:
2
+ - 2.0.0
3
+ - 1.9.3
4
+ - jruby
5
+ services: couchdb
6
+ matrix:
7
+ allow_failures:
8
+ - rvm: jruby
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
 
2
- source :rubygems
2
+ source "https://rubygems.org"
3
3
  gemspec
4
4
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # CouchRest Model: CouchDB, close to shiny metal with rounded edges
1
+ # CouchRest Model: CouchDB, close to shiny metal with rounded edges [![Build Status](https://travis-ci.org/couchrest/couchrest_model.png)](https://travis-ci.org/couchrest/couchrest_model)
2
2
 
3
3
  CouchRest Models adds additional functionality to the standard CouchRest Document class such as
4
4
  setting properties, callbacks, typecasting, and validations.
data/Rakefile CHANGED
@@ -1,33 +1,18 @@
1
- require 'rubygems'
1
+ # encoding: utf-8
2
2
  require 'bundler'
3
- require 'rspec/core/rake_task'
4
- require "rake/rdoctask"
5
-
6
3
  Bundler::GemHelper.install_tasks
7
4
 
8
- desc "Run all specs"
9
- RSpec::Core::RakeTask.new(:spec) do |spec|
10
- spec.rspec_opts = ["--color"]
11
- spec.pattern = 'spec/**/*_spec.rb'
12
- end
5
+ require 'rspec/core/rake_task'
13
6
 
14
- desc "Print specdocs"
15
- RSpec::Core::RakeTask.new(:doc) do |spec|
16
- spec.rspec_opts = ["--format", "specdoc"]
17
- spec.pattern = 'spec/*_spec.rb'
18
- end
7
+ desc 'Default: run unit tests.'
8
+ task :default => :spec
19
9
 
20
- desc "Generate the rdoc"
21
- Rake::RDocTask.new do |rdoc|
22
- files = ["README.rdoc", "LICENSE", "lib/**/*.rb"]
23
- rdoc.rdoc_files.add(files)
24
- rdoc.main = "README.rdoc"
25
- rdoc.title = "CouchRest: Ruby CouchDB, close to the metal"
10
+ desc "Run all specs"
11
+ RSpec::Core::RakeTask.new do |t|
12
+ t.pattern = 'spec/**/*_spec.rb'
13
+ t.rspec_opts = ["-c", "-f progress"]
26
14
  end
27
15
 
28
- desc "Run the rspec"
29
- task :default => :spec
30
-
31
16
  module Rake
32
17
  def self.remove_task(task_name)
33
18
  Rake.application.instance_variable_get('@tasks').delete(task_name.to_s)
@@ -35,4 +20,4 @@ module Rake
35
20
  end
36
21
 
37
22
  Rake.remove_task("github:release")
38
- Rake.remove_task("release")
23
+ Rake.remove_task("release")
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta2
1
+ 2.0.0
@@ -24,14 +24,16 @@ Gem::Specification.new do |s|
24
24
  s.require_paths = ["lib"]
25
25
 
26
26
  s.add_dependency(%q<couchrest>, "~> 1.1.3")
27
- s.add_dependency(%q<mime-types>, "~> 1.15")
28
- s.add_dependency(%q<activemodel>, "~> 3.0")
29
- s.add_dependency(%q<tzinfo>, "~> 0.3.22")
27
+ s.add_dependency(%q<mime-types>, ">= 1.15")
28
+ s.add_dependency(%q<activemodel>, ">= 3.0")
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
33
  s.add_development_dependency("rake", ">= 0.8.0")
34
- s.add_development_dependency("debugger", "~> 1.2.0")
34
+ s.add_development_dependency(%q<activemodel>, ">= 4.0")
35
+ #s.add_development_dependency("debugger", "~> 1.2.0") # TODO put in Gemfile
36
+ #s.add_development_dependency(%q<oj>, "~> 1.3.4") # TODO put in Gemfile (fails in JRuby)
37
+ s.add_development_dependency("kaminari", "~> 0.14.1")
35
38
  # s.add_development_dependency("jruby-openssl", ">= 0.7.3")
36
39
  end
37
-
data/history.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CouchRest Model Change History
2
2
 
3
- ## 2.0.0.beta2 - 2012-08-02
3
+ ## 2.0.0 - 2013-10-04
4
4
 
5
5
  * Added design doc migration support, including for proxied models
6
6
  * Rake tasks available for migrations
@@ -10,6 +10,22 @@
10
10
  * Added :allow_blank option to properties so that empty strings are forced to nil.
11
11
  * Modified associations to use allow_blank property
12
12
  * Incorported Rails 3.2 support changes (Thanks @jodosha)
13
+ * Kaminari support upgraded to use 0.14.0 API (Thanks @amatsuda)
14
+ * JSON Oj support, fixed some Time handling issues
15
+ * Simplifying number typecasting to always provide a number, or nil.
16
+ * Reduce option in views now accepts symbols: `:sum` to `'_sum'`
17
+ * Dirty tracking now supports CastedArray#insert method.
18
+ * Support for Rails 4.0.
19
+ * Removing support for <= Ruby 1.9.2.
20
+ * Fixing model translation support.
21
+ * Fixing `belongs_to` setting foreign key cache issue.
22
+ * Support typecasting `Symbol`
23
+ * Added `:array` option to properties
24
+ * Typecasting Dates, Times, and Booleans, with invalid values returns nil
25
+
26
+ * API Breaking Changes
27
+ * Properties with blocks are now singular unless the `array: true` option is passed.
28
+
13
29
 
14
30
  ## 1.2.0.beta - 2012-06-08
15
31
 
@@ -40,6 +40,7 @@ module CouchRest
40
40
 
41
41
  property(opts[:foreign_key], String, opts)
42
42
 
43
+ create_association_property_setter(attrib, opts)
43
44
  create_belongs_to_getter(attrib, opts)
44
45
  create_belongs_to_setter(attrib, opts)
45
46
  end
@@ -90,7 +91,7 @@ module CouchRest
90
91
 
91
92
  property(opts[:foreign_key], [String], opts)
92
93
 
93
- create_collection_of_property_setter(attrib, opts)
94
+ create_association_property_setter(attrib, opts)
94
95
  create_collection_of_getter(attrib, opts)
95
96
  create_collection_of_setter(attrib, opts)
96
97
  end
@@ -120,6 +121,20 @@ module CouchRest
120
121
  opts
121
122
  end
122
123
 
124
+ ### Generic support methods
125
+
126
+ def create_association_property_setter(attrib, options)
127
+ # ensure CollectionOfProxy is nil, ready to be reloaded on request
128
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
129
+ def #{options[:foreign_key]}=(value)
130
+ @#{attrib} = nil
131
+ write_attribute("#{options[:foreign_key]}", value)
132
+ end
133
+ EOS
134
+ end
135
+
136
+ ### belongs_to support methods
137
+
123
138
  def create_belongs_to_getter(attrib, options)
124
139
  class_eval <<-EOS, __FILE__, __LINE__ + 1
125
140
  def #{attrib}
@@ -139,16 +154,6 @@ module CouchRest
139
154
 
140
155
  ### collection_of support methods
141
156
 
142
- def create_collection_of_property_setter(attrib, options)
143
- # ensure CollectionOfProxy is nil, ready to be reloaded on request
144
- class_eval <<-EOS, __FILE__, __LINE__ + 1
145
- def #{options[:foreign_key]}=(value)
146
- @#{attrib} = nil
147
- write_attribute("#{options[:foreign_key]}", value)
148
- end
149
- EOS
150
- end
151
-
152
157
  def create_collection_of_getter(attrib, options)
153
158
  class_eval <<-EOS, __FILE__, __LINE__ + 1
154
159
  def #{attrib}(reload = false)
@@ -2,22 +2,24 @@ module CouchRest
2
2
  module Model
3
3
  class Base < CouchRest::Document
4
4
 
5
- extend ActiveModel::Naming
6
5
  include ActiveModel::Conversion
7
6
 
8
- include CouchRest::Model::Configuration
9
- include CouchRest::Model::Connection
10
- include CouchRest::Model::Persistence
11
- include CouchRest::Model::DocumentQueries
12
- include CouchRest::Model::ExtendedAttachments
13
- include CouchRest::Model::Proxyable
14
- include CouchRest::Model::PropertyProtection
15
- include CouchRest::Model::Associations
16
- include CouchRest::Model::Validations
17
- include CouchRest::Model::Callbacks
18
- include CouchRest::Model::Designs
19
- include CouchRest::Model::CastedBy
20
- include CouchRest::Model::Dirty
7
+ extend Translation
8
+
9
+ include Configuration
10
+ include Connection
11
+ include Persistence
12
+ include DocumentQueries
13
+ include ExtendedAttachments
14
+ include Proxyable
15
+ include PropertyProtection
16
+ include Associations
17
+ include Validations
18
+ include Callbacks
19
+ include Designs
20
+ include CastedBy
21
+ include Dirty
22
+
21
23
 
22
24
  def self.subclasses
23
25
  @subclasses ||= []
@@ -25,7 +27,7 @@ module CouchRest
25
27
 
26
28
  def self.inherited(subklass)
27
29
  super
28
- subklass.send(:include, CouchRest::Model::Properties)
30
+ subklass.send(:include, Properties)
29
31
 
30
32
  subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
31
33
  def self.inherited(subklass)
@@ -35,6 +35,12 @@ module CouchRest::Model
35
35
  super(index, value)
36
36
  end
37
37
 
38
+ def insert index, *args
39
+ values = *args.map{|obj| instantiate_and_cast(obj, false)}
40
+ couchrest_parent_will_change! if use_dirty?
41
+ super(index, *values)
42
+ end
43
+
38
44
  def pop
39
45
  couchrest_parent_will_change! if use_dirty? && self.length > 0
40
46
  super
@@ -71,7 +77,7 @@ module CouchRest::Model
71
77
  def instantiate_and_cast(obj, change = true)
72
78
  property = casted_by_property
73
79
  couchrest_parent_will_change! if change && use_dirty?
74
- if casted_by && property && obj.class != property.type_class
80
+ if casted_by && property && obj.class != property.type
75
81
  property.cast_value(casted_by, obj)
76
82
  else
77
83
  obj.casted_by = casted_by if obj.respond_to?(:casted_by)
@@ -4,29 +4,6 @@ module CouchRest
4
4
 
5
5
  module TimeParsing
6
6
 
7
- if RUBY_VERSION < "1.9.0"
8
- # Overrwrite Ruby's standard new method to provide compatible support
9
- # of 1.9.2's Time.new method.
10
- #
11
- # Only supports syntax like:
12
- #
13
- # Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
14
- # # or
15
- # Time.new(2011, 4, 1, 18, 50, 32)
16
- #
17
- def new(*args)
18
- return super() if (args.empty?)
19
- zone = args.delete_at(6)
20
- time = mktime(*args)
21
- if zone =~ /([\+|\-]?)(\d{2}):?(\d{2})/
22
- tz_difference = ("#{$1 == '-' ? '+' : '-'}#{$2}".to_i * 3600) + ($3.to_i * 60)
23
- time + tz_difference + zone_offset(time.zone)
24
- else
25
- time
26
- end
27
- end
28
- end
29
-
30
7
  # Attemtps to parse a time string in ISO8601 format.
31
8
  # If no match is found, the standard time parse will be used.
32
9
  #
@@ -0,0 +1,282 @@
1
+
2
+ module CouchRest
3
+ module Model
4
+
5
+ class Design < ::CouchRest::Design
6
+
7
+ # The model Class that this design belongs to and method name
8
+ attr_accessor :model, :method_name
9
+
10
+ # Can this design save itself to the database?
11
+ # If false, the design will be loaded automatically before a view is executed.
12
+ attr_accessor :auto_update
13
+
14
+
15
+ # Instantiate a new design document for this model
16
+ def initialize(model, prefix = nil)
17
+ self.model = model
18
+ self.method_name = self.class.method_name(prefix)
19
+ suffix = prefix ? "_#{prefix}" : ''
20
+ self["_id"] = "_design/#{model.to_s}#{suffix}"
21
+ apply_defaults
22
+ end
23
+
24
+ def sync(db = nil)
25
+ if auto_update
26
+ db ||= database
27
+ if cache_checksum(db) != checksum
28
+ sync!(db)
29
+ set_cache_checksum(db, checksum)
30
+ end
31
+ end
32
+ self
33
+ end
34
+
35
+ def sync!(db = nil)
36
+ db ||= database
37
+
38
+ # Load up the last copy. We never blindly overwrite the remote copy
39
+ # as it may contain views that are not used or known about by
40
+ # our model.
41
+ doc = load_from_database(db)
42
+
43
+ if !doc || doc['couchrest-hash'] != checksum
44
+ # We need to save something
45
+ if doc
46
+ # Different! Update.
47
+ doc.merge!(to_hash)
48
+ else
49
+ # No previous doc, use a *copy* of our version.
50
+ # Using a copy prevents reverse updates.
51
+ doc = to_hash.dup
52
+ end
53
+ db.save_doc(doc)
54
+ end
55
+
56
+ self
57
+ end
58
+
59
+ # Migrate the design document preventing downtime on a production
60
+ # system. Typically this will be used when auto updates are disabled.
61
+ #
62
+ # Steps taken are:
63
+ #
64
+ # 1. Compare the checksum with the current version
65
+ # 2. If different, create a new design doc with timestamp
66
+ # 3. Wait until the view returns a result
67
+ # 4. Copy over the original design doc
68
+ #
69
+ # If a block is provided, it will be called with the result of the migration:
70
+ #
71
+ # * :no_change - Nothing performed as there are no changes.
72
+ # * :created - Add a new design doc as non existed
73
+ # * :migrated - Migrated the existing design doc.
74
+ #
75
+ # This can be used for progressivly printing the results of the migration.
76
+ #
77
+ # After completion, either a "cleanup" Proc object will be provided to finalize
78
+ # the process and copy the document into place, or simply nil if no cleanup is
79
+ # required. For example:
80
+ #
81
+ # print "Synchronising Cat model designs: "
82
+ # callback = Cat.design_doc.migrate do |res|
83
+ # puts res.to_s
84
+ # end
85
+ # if callback
86
+ # puts "Cleaning up."
87
+ # callback.call
88
+ # end
89
+ #
90
+ def migrate(db = nil, &block)
91
+ db ||= database
92
+ doc = load_from_database(db)
93
+ cleanup = nil
94
+ id = self['_id']
95
+
96
+ if !doc
97
+ # no need to migrate, just save it
98
+ new_doc = to_hash.dup
99
+ db.save_doc(new_doc)
100
+
101
+ result = :created
102
+ elsif doc['couchrest-hash'] != checksum
103
+ id += "_migration"
104
+
105
+ # Delete current migration if there is one
106
+ old_migration = load_from_database(db, id)
107
+ db.delete_doc(old_migration) if old_migration
108
+
109
+ # Save new design doc
110
+ new_doc = doc.merge(to_hash)
111
+ new_doc['_id'] = id
112
+ new_doc.delete('_rev')
113
+ db.save_doc(new_doc)
114
+
115
+ # Proc definition to copy the migration doc over the original
116
+ cleanup = Proc.new do
117
+ db.copy_doc(new_doc, doc)
118
+ db.delete_doc(new_doc)
119
+ self
120
+ end
121
+
122
+ result = :migrated
123
+ else
124
+ # Already up to date
125
+ result = :no_change
126
+ end
127
+
128
+ if new_doc && !new_doc['views'].empty?
129
+ # Create a view query and send
130
+ name = new_doc['views'].keys.first
131
+ view = new_doc['views'][name]
132
+ params = {:limit => 1}
133
+ params[:reduce] = false if view['reduce']
134
+ db.view("#{id}/_view/#{name}", params) do |res|
135
+ # Block to use streamer!
136
+ end
137
+ end
138
+
139
+ # Provide the result in block
140
+ yield result if block_given?
141
+
142
+ cleanup
143
+ end
144
+
145
+ # Perform a single migration and inmediatly request a cleanup operation:
146
+ #
147
+ # print "Synchronising Cat model designs: "
148
+ # Cat.design_doc.migrate! do |res|
149
+ # puts res.to_s
150
+ # end
151
+ #
152
+ def migrate!(db = nil, &block)
153
+ callback = migrate(db, &block)
154
+ if callback.is_a?(Proc)
155
+ callback.call
156
+ else
157
+ callback
158
+ end
159
+ end
160
+
161
+ def checksum
162
+ sum = self['couchrest-hash']
163
+ if sum && (@_original_hash == to_hash)
164
+ sum
165
+ else
166
+ checksum!
167
+ end
168
+ end
169
+
170
+ def database
171
+ model.database
172
+ end
173
+
174
+ # Override the default #uri method for one that accepts
175
+ # the current database.
176
+ # This is used by the caching code.
177
+ def uri(db = database)
178
+ "#{db.root}/#{self['_id']}"
179
+ end
180
+
181
+
182
+ ######## VIEW HANDLING ########
183
+
184
+ # Create a new view object.
185
+ # This overrides the normal CouchRest Design view method
186
+ def view(name, opts = {})
187
+ CouchRest::Model::Designs::View.new(self, model, opts, name)
188
+ end
189
+
190
+ # Helper method to provide a list of all the views
191
+ def view_names
192
+ self['views'].keys
193
+ end
194
+
195
+ def has_view?(name)
196
+ view_names.include?(name.to_s)
197
+ end
198
+
199
+ # Add the specified view to the design doc the definition was made in
200
+ # and create quick access methods in the model.
201
+ def create_view(name, opts = {})
202
+ Designs::View.define_and_create(self, name, opts)
203
+ end
204
+
205
+ ######## FILTER HANDLING ########
206
+
207
+ def create_filter(name, function)
208
+ filters = (self['filters'] ||= {})
209
+ filters[name.to_s] = function
210
+ end
211
+
212
+ protected
213
+
214
+ def load_from_database(db = database, id = nil)
215
+ id ||= self['_id']
216
+ db.get(id)
217
+ rescue RestClient::ResourceNotFound
218
+ nil
219
+ end
220
+
221
+ # Calculate and update the checksum of the Design document.
222
+ # Used for ensuring the latest version has been sent to the database.
223
+ #
224
+ # This will generate an flatterned, ordered array of all the elements of the
225
+ # design document, convert to string then generate an MD5 Hash. This should
226
+ # result in a consisitent Hash accross all platforms.
227
+ #
228
+ def checksum!
229
+ # Get a deep copy of hash to compare with
230
+ @_original_hash = Marshal.load(Marshal.dump(to_hash))
231
+ # create a copy of basic elements
232
+ base = self.dup
233
+ base.delete('_id')
234
+ base.delete('_rev')
235
+ base.delete('couchrest-hash')
236
+ result = nil
237
+ flatten =
238
+ lambda {|r|
239
+ (recurse = lambda {|v|
240
+ if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
241
+ v.to_a.map{|v| recurse.call(v)}.flatten
242
+ elsif v.is_a?(Array)
243
+ v.flatten.map{|v| recurse.call(v)}
244
+ else
245
+ v.to_s
246
+ end
247
+ }).call(r)
248
+ }
249
+ self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
250
+ end
251
+
252
+ def cache
253
+ Thread.current[:couchrest_design_cache] ||= {}
254
+ end
255
+ def cache_checksum(db)
256
+ cache[uri(db)]
257
+ end
258
+ def set_cache_checksum(db, checksum)
259
+ cache[uri(db)] = checksum
260
+ end
261
+
262
+ def apply_defaults
263
+ merge!(
264
+ "language" => "javascript",
265
+ "views" => { }
266
+ )
267
+ end
268
+
269
+
270
+ class << self
271
+
272
+ def method_name(prefix = nil)
273
+ (prefix ? "#{prefix}_" : '') + 'design_doc'
274
+ end
275
+
276
+ end
277
+
278
+ end
279
+ end
280
+ end
281
+
282
+
@@ -0,0 +1,79 @@
1
+ module CouchRest
2
+ module Model
3
+ module Designs
4
+
5
+ # Support class that allows for a model's design
6
+ # definition to be converted into an actual design document.
7
+ #
8
+ # The methods called in a DesignMapper instance will relay
9
+ # the parameters to the appropriate method in the design document.
10
+ #
11
+ class DesignMapper
12
+
13
+ # Basic mapper attributes
14
+ attr_accessor :model, :method, :prefix
15
+
16
+ # Temporary variable storing the design doc
17
+ attr_accessor :design_doc
18
+
19
+ def initialize(model, prefix = nil)
20
+ self.model = model
21
+ self.prefix = prefix
22
+ self.method = Design.method_name(prefix)
23
+
24
+ create_model_design_doc_reader
25
+ self.design_doc = model.send(method) || assign_model_design_doc
26
+ end
27
+
28
+ def disable_auto_update
29
+ design_doc.auto_update = false
30
+ end
31
+
32
+ def enable_auto_update
33
+ design_doc.auto_update = true
34
+ end
35
+
36
+ # Add the specified view to the design doc the definition was made in
37
+ # and create quick access methods in the model.
38
+ def view(name, opts = {})
39
+ design_doc.create_view(name, opts)
40
+ end
41
+
42
+ # Really simple design function that allows a filter
43
+ # to be added. Filters are simple functions used when listening
44
+ # to the _changes feed.
45
+ #
46
+ # No methods are created here, the design is simply updated.
47
+ # See the CouchDB API for more information on how to use this.
48
+ def filter(name, function)
49
+ design_doc.create_filter(name, function)
50
+ end
51
+
52
+ # Convenience wrapper to access model's type key option.
53
+ def model_type_key
54
+ model.model_type_key
55
+ end
56
+
57
+ protected
58
+
59
+ # Create accessor in model and assign a new design doc.
60
+ # New design doc is returned ready to use.
61
+ def create_model_design_doc_reader
62
+ model.instance_eval "def #{method}; @#{method}; end"
63
+ end
64
+
65
+ def assign_model_design_doc
66
+ doc = Design.new(model, prefix)
67
+ model.instance_variable_set("@#{method}", doc)
68
+ model.design_docs << doc
69
+
70
+ # Set defaults
71
+ doc.auto_update = model.auto_update_design_doc
72
+
73
+ doc
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -371,9 +371,10 @@ module CouchRest
371
371
  query[:limit]
372
372
  end
373
373
 
374
- def num_pages
374
+ def total_pages
375
375
  (total_count.to_f / limit_value).ceil
376
376
  end
377
+ alias num_pages total_pages
377
378
 
378
379
  def current_page
379
380
  (offset_value / limit_value) + 1
@@ -486,14 +487,16 @@ module CouchRest
486
487
  }
487
488
  EOF
488
489
  if opts[:reduce].nil?
489
- opts[:reduce] = <<-EOF
490
- function(key, values, rereduce) {
491
- return sum(values);
492
- }
493
- EOF
490
+ # Use built-in sum function by default
491
+ opts[:reduce] = "_sum"
494
492
  end
495
493
  end
496
494
 
495
+ if opts[:reduce].is_a?(Symbol)
496
+ # Assume calling a built in method, convert to a string
497
+ opts[:reduce] = "_#{opts[:reduce]}"
498
+ end
499
+
497
500
  design_doc['views'] ||= {}
498
501
  view = design_doc['views'][name.to_s] = { }
499
502
  view['map'] = opts[:map]