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.
- data/.travis.yml +8 -0
- data/Gemfile +1 -1
- data/README.md +1 -1
- data/Rakefile +9 -24
- data/VERSION +1 -1
- data/couchrest_model.gemspec +7 -5
- data/history.md +17 -1
- data/lib/couchrest/model/associations.rb +16 -11
- data/lib/couchrest/model/base.rb +17 -15
- data/lib/couchrest/model/casted_array.rb +7 -1
- data/lib/couchrest/model/core_extensions/time_parsing.rb +0 -23
- data/lib/couchrest/model/design.rb +282 -0
- data/lib/couchrest/model/designs/design_mapper.rb +79 -0
- data/lib/couchrest/model/designs/view.rb +9 -6
- data/lib/couchrest/model/designs.rb +37 -70
- data/lib/couchrest/model/persistence.rb +5 -5
- data/lib/couchrest/model/properties.rb +5 -16
- data/lib/couchrest/model/property.rb +34 -16
- data/lib/couchrest/model/translation.rb +22 -0
- data/lib/couchrest/model/typecast.rb +54 -43
- data/lib/couchrest/model/utils/migrate.rb +106 -0
- data/lib/couchrest_model.rb +4 -2
- data/lib/tasks/migrations.rake +5 -5
- data/spec/fixtures/models/course.rb +1 -0
- data/spec/fixtures/models/designs.rb +22 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/assocations_spec.rb +7 -0
- data/spec/unit/base_spec.rb +3 -1
- data/spec/unit/{designs/design_spec.rb → design_spec.rb} +6 -6
- data/spec/unit/designs/design_mapper_spec.rb +124 -0
- data/spec/unit/designs/view_spec.rb +30 -4
- data/spec/unit/designs_spec.rb +5 -140
- data/spec/unit/dirty_spec.rb +15 -1
- data/spec/unit/embeddable_spec.rb +2 -2
- data/spec/unit/property_spec.rb +70 -28
- data/spec/unit/translations_spec.rb +31 -0
- data/spec/unit/typecast_spec.rb +99 -19
- data/spec/unit/utils/migrate_spec.rb +25 -0
- metadata +43 -19
- data/lib/couchrest/model/designs/design.rb +0 -284
- data/lib/couchrest/model/migrate.rb +0 -92
data/.travis.yml
ADDED
data/Gemfile
CHANGED
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 [](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
|
-
|
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
|
-
|
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
|
15
|
-
|
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 "
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
1
|
+
2.0.0
|
data/couchrest_model.gemspec
CHANGED
@@ -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>, "
|
28
|
-
s.add_dependency(%q<activemodel>, "
|
29
|
-
s.add_dependency(%q<tzinfo>, "
|
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(
|
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
|
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
|
-
|
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)
|
data/lib/couchrest/model/base.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
include
|
11
|
-
include
|
12
|
-
include
|
13
|
-
include
|
14
|
-
include
|
15
|
-
include
|
16
|
-
include
|
17
|
-
include
|
18
|
-
include
|
19
|
-
include
|
20
|
-
include
|
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,
|
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.
|
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
|
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
|
-
|
490
|
-
|
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]
|