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.
- 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
|