couchrest_model 1.1.2 → 1.2.0.beta
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/README.md +8 -2
- data/VERSION +1 -1
- data/couchrest_model.gemspec +2 -1
- data/history.md +8 -0
- data/lib/couchrest/model/base.rb +0 -20
- data/lib/couchrest/model/configuration.rb +2 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +35 -9
- data/lib/couchrest/model/designs/design.rb +182 -0
- data/lib/couchrest/model/designs/view.rb +91 -48
- data/lib/couchrest/model/designs.rb +72 -19
- data/lib/couchrest/model/document_queries.rb +15 -45
- data/lib/couchrest/model/properties.rb +43 -2
- data/lib/couchrest/model/proxyable.rb +20 -54
- data/lib/couchrest/model/typecast.rb +1 -1
- data/lib/couchrest/model/validations/uniqueness.rb +7 -6
- data/lib/couchrest_model.rb +1 -5
- data/spec/fixtures/models/article.rb +22 -20
- data/spec/fixtures/models/base.rb +15 -7
- data/spec/fixtures/models/course.rb +7 -4
- data/spec/fixtures/models/project.rb +4 -1
- data/spec/fixtures/models/sale_entry.rb +5 -3
- data/spec/unit/base_spec.rb +51 -5
- data/spec/unit/core_extensions/time_parsing.rb +41 -0
- data/spec/unit/designs/design_spec.rb +291 -0
- data/spec/unit/designs/view_spec.rb +135 -40
- data/spec/unit/designs_spec.rb +341 -30
- data/spec/unit/dirty_spec.rb +67 -0
- data/spec/unit/inherited_spec.rb +2 -2
- data/spec/unit/property_protection_spec.rb +3 -1
- data/spec/unit/property_spec.rb +43 -3
- data/spec/unit/proxyable_spec.rb +57 -98
- data/spec/unit/subclass_spec.rb +14 -5
- data/spec/unit/validations_spec.rb +14 -12
- metadata +172 -129
- data/lib/couchrest/model/class_proxy.rb +0 -135
- data/lib/couchrest/model/collection.rb +0 -273
- data/lib/couchrest/model/design_doc.rb +0 -115
- data/lib/couchrest/model/support/couchrest_design.rb +0 -33
- data/lib/couchrest/model/views.rb +0 -148
- data/spec/unit/class_proxy_spec.rb +0 -167
- data/spec/unit/collection_spec.rb +0 -86
- data/spec/unit/design_doc_spec.rb +0 -212
- 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
|
-
|
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
|
-
|
98
|
+
design do
|
99
|
+
view :by_name
|
100
|
+
end
|
95
101
|
|
96
102
|
end
|
97
103
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0.beta
|
data/couchrest_model.gemspec
CHANGED
@@ -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
|
data/lib/couchrest/model/base.rb
CHANGED
@@ -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
|
-
# $
|
44
|
-
# $
|
45
|
-
# $
|
46
|
-
# $
|
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
|
49
|
-
|
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
|
-
|
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 = (
|
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
|
313
|
-
#
|
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
|
-
|
414
|
+
design_doc.sync(use_database)
|
406
415
|
|
407
|
-
self.result =
|
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
|
-
|
413
|
-
|
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.
|
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.
|
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
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
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
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
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
|
-
|
472
|
-
view =
|
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
|