couchrest_model 2.0.0.beta2 → 2.0.0

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