jchris-couchrest 0.22 → 0.23
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +10 -1
- data/lib/couchrest.rb +1 -1
- data/lib/couchrest/core/database.rb +1 -1
- data/lib/couchrest/core/design.rb +7 -17
- data/lib/couchrest/core/document.rb +0 -9
- data/lib/couchrest/mixins/class_proxy.rb +108 -0
- data/lib/couchrest/mixins/design_doc.rb +35 -9
- data/lib/couchrest/mixins/document_queries.rb +2 -6
- data/lib/couchrest/mixins/extended_document_mixins.rb +2 -1
- data/lib/couchrest/mixins/properties.rb +1 -1
- data/lib/couchrest/mixins/validation.rb +4 -1
- data/lib/couchrest/mixins/views.rb +22 -21
- data/lib/couchrest/monkeypatches.rb +0 -6
- data/lib/couchrest/more/extended_document.rb +3 -2
- data/lib/couchrest/validation/validation_errors.rb +1 -1
- data/spec/couchrest/core/database_spec.rb +9 -1
- data/spec/couchrest/core/design_spec.rb +12 -5
- data/spec/couchrest/more/extended_doc_subclass_spec.rb +44 -0
- data/spec/couchrest/more/extended_doc_view_spec.rb +158 -10
- data/spec/couchrest/more/property_spec.rb +6 -0
- metadata +2 -21
data/Rakefile
CHANGED
@@ -35,7 +35,7 @@ spec = Gem::Specification.new do |s|
|
|
35
35
|
end
|
36
36
|
|
37
37
|
|
38
|
-
desc "
|
38
|
+
desc "Create .gemspec file (useful for github)"
|
39
39
|
task :gemspec do
|
40
40
|
filename = "#{spec.name}.gemspec"
|
41
41
|
File.open(filename, "w") do |f|
|
@@ -43,6 +43,15 @@ task :gemspec do
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
47
|
+
pkg.gem_spec = spec
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Install the gem locally"
|
51
|
+
task :install => [:package] do
|
52
|
+
sh %{sudo gem install pkg/couchrest-#{CouchRest::VERSION}}
|
53
|
+
end
|
54
|
+
|
46
55
|
desc "Run all specs"
|
47
56
|
Spec::Rake::SpecTask.new('spec') do |t|
|
48
57
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
data/lib/couchrest.rb
CHANGED
@@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
|
|
28
28
|
|
29
29
|
# = CouchDB, close to the metal
|
30
30
|
module CouchRest
|
31
|
-
VERSION = '0.
|
31
|
+
VERSION = '0.23' unless self.const_defined?("VERSION")
|
32
32
|
|
33
33
|
autoload :Server, 'couchrest/core/server'
|
34
34
|
autoload :Database, 'couchrest/core/database'
|
@@ -17,7 +17,7 @@ module CouchRest
|
|
17
17
|
@name = name
|
18
18
|
@server = server
|
19
19
|
@host = server.uri
|
20
|
-
@uri = @root = "#{host}/#{name}"
|
20
|
+
@uri = @root = "#{host}/#{name.gsub('/','%2F')}"
|
21
21
|
@streamer = Streamer.new(self)
|
22
22
|
@bulk_save_cache = []
|
23
23
|
@bulk_save_cache_limit = 500 # must be smaller than the uuid count
|
@@ -35,11 +35,17 @@ JAVASCRIPT
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# Dispatches to any named view.
|
38
|
+
# (using the database where this design doc was saved)
|
38
39
|
def view view_name, query={}, &block
|
40
|
+
view_on database, view_name, query, &block
|
41
|
+
end
|
42
|
+
|
43
|
+
# Dispatches to any named view in a specific database
|
44
|
+
def view_on db, view_name, query={}, &block
|
39
45
|
view_name = view_name.to_s
|
40
46
|
view_slug = "#{name}/#{view_name}"
|
41
47
|
defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
|
42
|
-
|
48
|
+
db.view(view_slug, defaults.merge(query), &block)
|
43
49
|
end
|
44
50
|
|
45
51
|
def name
|
@@ -64,22 +70,6 @@ JAVASCRIPT
|
|
64
70
|
(self['views'][view]["couchrest-defaults"]||{})
|
65
71
|
end
|
66
72
|
|
67
|
-
# def fetch_view_with_docs name, opts, raw=false, &block
|
68
|
-
# if raw
|
69
|
-
# fetch_view name, opts, &block
|
70
|
-
# else
|
71
|
-
# begin
|
72
|
-
# view = fetch_view name, opts.merge({:include_docs => true}), &block
|
73
|
-
# view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
74
|
-
# rescue
|
75
|
-
# # fallback for old versions of couchdb that don't
|
76
|
-
# # have include_docs support
|
77
|
-
# view = fetch_view name, opts, &block
|
78
|
-
# view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
|
79
|
-
# end
|
80
|
-
# end
|
81
|
-
# end
|
82
|
-
|
83
73
|
def fetch_view view_name, opts, &block
|
84
74
|
database.view(view_name, opts, &block)
|
85
75
|
end
|
@@ -65,15 +65,6 @@ module CouchRest
|
|
65
65
|
result['ok']
|
66
66
|
end
|
67
67
|
|
68
|
-
# moves the document to a new id. If the destination id currently exists, a rev must be provided.
|
69
|
-
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
70
|
-
# hash with a '_rev' key
|
71
|
-
def move(dest)
|
72
|
-
raise ArgumentError, "doc.database required to copy" unless database
|
73
|
-
result = database.move_doc(self, dest)
|
74
|
-
result['ok']
|
75
|
-
end
|
76
|
-
|
77
68
|
# Returns the CouchDB uri for the document
|
78
69
|
def uri(append_rev = false)
|
79
70
|
return nil if new_document?
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Mixins
|
3
|
+
module ClassProxy
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# Return a proxy object which represents a model class on a
|
12
|
+
# chosen database instance. This allows you to DRY operations
|
13
|
+
# where a database is chosen dynamically.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
#
|
17
|
+
# db = CouchRest::Database.new(...)
|
18
|
+
# articles = Article.on(db)
|
19
|
+
#
|
20
|
+
# articles.all { ... }
|
21
|
+
# articles.by_title { ... }
|
22
|
+
#
|
23
|
+
# u = articles.get("someid")
|
24
|
+
#
|
25
|
+
# u = articles.new(:title => "I like plankton")
|
26
|
+
# u.save # saved on the correct database
|
27
|
+
|
28
|
+
def on(database)
|
29
|
+
Proxy.new(self, database)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Proxy #:nodoc:
|
34
|
+
def initialize(klass, database)
|
35
|
+
@klass = klass
|
36
|
+
@database = database
|
37
|
+
end
|
38
|
+
|
39
|
+
# ExtendedDocument
|
40
|
+
|
41
|
+
def new(*args)
|
42
|
+
doc = @klass.new(*args)
|
43
|
+
doc.database = @database
|
44
|
+
doc
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(m, *args, &block)
|
48
|
+
if has_view?(m)
|
49
|
+
query = args.shift || {}
|
50
|
+
view(m, query, *args, &block)
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Mixins::DocumentQueries
|
57
|
+
|
58
|
+
def all(opts = {}, &block)
|
59
|
+
@klass.all({:database => @database}.merge(opts), &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def first(opts = {})
|
63
|
+
@klass.first({:database => @database}.merge(opts))
|
64
|
+
end
|
65
|
+
|
66
|
+
def get(id)
|
67
|
+
@klass.get(id, @database)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Mixins::Views
|
71
|
+
|
72
|
+
def has_view?(view)
|
73
|
+
@klass.has_view?(view)
|
74
|
+
end
|
75
|
+
|
76
|
+
def view(name, query={}, &block)
|
77
|
+
@klass.view(name, {:database => @database}.merge(query), &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def all_design_doc_versions
|
81
|
+
@klass.all_design_doc_versions(@database)
|
82
|
+
end
|
83
|
+
|
84
|
+
def cleanup_design_docs!
|
85
|
+
@klass.cleanup_design_docs!(@database)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Mixins::DesignDoc
|
89
|
+
|
90
|
+
def design_doc
|
91
|
+
@klass.design_doc
|
92
|
+
end
|
93
|
+
|
94
|
+
def design_doc_fresh
|
95
|
+
@klass.design_doc_fresh
|
96
|
+
end
|
97
|
+
|
98
|
+
def refresh_design_doc
|
99
|
+
@klass.refresh_design_doc
|
100
|
+
end
|
101
|
+
|
102
|
+
def save_design_doc
|
103
|
+
@klass.save_design_doc_on(@database)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -9,6 +9,12 @@ module CouchRest
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
+
attr_accessor :design_doc, :design_doc_slug_cache, :design_doc_fresh
|
13
|
+
|
14
|
+
def design_doc
|
15
|
+
@design_doc ||= Design.new(default_design_doc)
|
16
|
+
end
|
17
|
+
|
12
18
|
def design_doc_id
|
13
19
|
"_design/#{design_doc_slug}"
|
14
20
|
end
|
@@ -16,7 +22,6 @@ module CouchRest
|
|
16
22
|
def design_doc_slug
|
17
23
|
return design_doc_slug_cache if (design_doc_slug_cache && design_doc_fresh)
|
18
24
|
funcs = []
|
19
|
-
design_doc ||= Design.new(default_design_doc)
|
20
25
|
design_doc['views'].each do |name, view|
|
21
26
|
funcs << "#{name}/#{view['map']}#{view['reduce']}"
|
22
27
|
end
|
@@ -40,21 +45,42 @@ module CouchRest
|
|
40
45
|
end
|
41
46
|
|
42
47
|
def refresh_design_doc
|
43
|
-
|
44
|
-
|
48
|
+
design_doc['_id'] = design_doc_id
|
49
|
+
design_doc.delete('_rev')
|
50
|
+
#design_doc.database = nil
|
51
|
+
self.design_doc_fresh = true
|
52
|
+
end
|
53
|
+
|
54
|
+
# Save the design doc onto the default database, and update the
|
55
|
+
# design_doc attribute
|
56
|
+
def save_design_doc
|
57
|
+
refresh_design_doc unless design_doc_fresh
|
58
|
+
self.design_doc = update_design_doc(design_doc)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Save the design doc onto a target database in a thread-safe way,
|
62
|
+
# not modifying the model's design_doc
|
63
|
+
def save_design_doc_on(db)
|
64
|
+
update_design_doc(Design.new(design_doc), db)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Writes out a design_doc to a given database, returning the
|
70
|
+
# updated design doc
|
71
|
+
def update_design_doc(design_doc, db = database)
|
72
|
+
saved = db.get(design_doc['_id']) rescue nil
|
45
73
|
if saved
|
46
74
|
design_doc['views'].each do |name, view|
|
47
75
|
saved['views'][name] = view
|
48
76
|
end
|
49
|
-
|
50
|
-
|
77
|
+
db.save_doc(saved)
|
78
|
+
saved
|
51
79
|
else
|
52
|
-
design_doc
|
53
|
-
design_doc.delete('_rev')
|
54
|
-
design_doc.database = database
|
80
|
+
design_doc.database = db
|
55
81
|
design_doc.save
|
82
|
+
design_doc
|
56
83
|
end
|
57
|
-
self.design_doc_fresh = true
|
58
84
|
end
|
59
85
|
|
60
86
|
end # module ClassMethods
|
@@ -12,10 +12,6 @@ module CouchRest
|
|
12
12
|
# name of the current class. Take the standard set of
|
13
13
|
# CouchRest::Database#view options.
|
14
14
|
def all(opts = {}, &block)
|
15
|
-
self.design_doc ||= Design.new(default_design_doc)
|
16
|
-
unless design_doc_fresh
|
17
|
-
refresh_design_doc
|
18
|
-
end
|
19
15
|
view(:all, opts, &block)
|
20
16
|
end
|
21
17
|
|
@@ -36,8 +32,8 @@ module CouchRest
|
|
36
32
|
end
|
37
33
|
|
38
34
|
# Load a document from the database by id
|
39
|
-
def get(id)
|
40
|
-
doc =
|
35
|
+
def get(id, db = database)
|
36
|
+
doc = db.get id
|
41
37
|
new(doc)
|
42
38
|
end
|
43
39
|
|
@@ -3,4 +3,5 @@ require File.join(File.dirname(__FILE__), 'document_queries')
|
|
3
3
|
require File.join(File.dirname(__FILE__), 'views')
|
4
4
|
require File.join(File.dirname(__FILE__), 'design_doc')
|
5
5
|
require File.join(File.dirname(__FILE__), 'validation')
|
6
|
-
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
6
|
+
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
7
|
+
require File.join(File.dirname(__FILE__), 'class_proxy')
|
@@ -9,7 +9,7 @@ module CouchRest
|
|
9
9
|
|
10
10
|
def self.included(base)
|
11
11
|
base.class_eval <<-EOS, __FILE__, __LINE__
|
12
|
-
extlib_inheritable_accessor(:properties)
|
12
|
+
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
13
13
|
self.properties ||= []
|
14
14
|
EOS
|
15
15
|
base.extend(ClassMethods)
|
@@ -63,7 +63,10 @@ module CouchRest
|
|
63
63
|
|
64
64
|
# share the validations with subclasses
|
65
65
|
def self.inherited(subklass)
|
66
|
-
|
66
|
+
self.validators.contexts.each do |k, v|
|
67
|
+
subklass.validators.contexts[k] = v.dup
|
68
|
+
end
|
69
|
+
super
|
67
70
|
end
|
68
71
|
EOS
|
69
72
|
|
@@ -4,13 +4,9 @@ module CouchRest
|
|
4
4
|
|
5
5
|
def self.included(base)
|
6
6
|
base.extend(ClassMethods)
|
7
|
-
base.send(:extlib_inheritable_accessor, :design_doc)
|
8
|
-
base.send(:extlib_inheritable_accessor, :design_doc_slug_cache)
|
9
|
-
base.send(:extlib_inheritable_accessor, :design_doc_fresh)
|
10
7
|
end
|
11
8
|
|
12
9
|
module ClassMethods
|
13
|
-
|
14
10
|
# Define a CouchDB view. The name of the view will be the concatenation
|
15
11
|
# of <tt>by</tt> and the keys joined by <tt>_and_</tt>
|
16
12
|
#
|
@@ -57,6 +53,10 @@ module CouchRest
|
|
57
53
|
# themselves. By default <tt>Post.by_date</tt> will return the
|
58
54
|
# documents included in the generated view.
|
59
55
|
#
|
56
|
+
# Calling with :database => [instance of CouchRest::Database] will
|
57
|
+
# send the query to a specific database, otherwise it will go to
|
58
|
+
# the model's default database (use_database)
|
59
|
+
#
|
60
60
|
# CouchRest::Database#view options can be applied at view definition
|
61
61
|
# time as defaults, and they will be curried and used at view query
|
62
62
|
# time. Or they can be overridden at query time.
|
@@ -70,12 +70,11 @@ module CouchRest
|
|
70
70
|
# that model won't be available until generation is complete. This can
|
71
71
|
# take some time with large databases. Strategies are in the works.
|
72
72
|
#
|
73
|
-
# To understand the capabilities of this view system more
|
73
|
+
# To understand the capabilities of this view system more completely,
|
74
74
|
# it is recommended that you read the RSpec file at
|
75
75
|
# <tt>spec/core/model_spec.rb</tt>.
|
76
76
|
|
77
77
|
def view_by(*keys)
|
78
|
-
self.design_doc ||= Design.new(default_design_doc)
|
79
78
|
opts = keys.pop if keys.last.is_a?(Hash)
|
80
79
|
opts ||= {}
|
81
80
|
ducktype = opts.delete(:ducktype)
|
@@ -100,12 +99,13 @@ module CouchRest
|
|
100
99
|
refresh_design_doc
|
101
100
|
end
|
102
101
|
query[:raw] = true if query[:reduce]
|
102
|
+
db = query.delete(:database) || database
|
103
103
|
raw = query.delete(:raw)
|
104
|
-
fetch_view_with_docs(name, query, raw, &block)
|
104
|
+
fetch_view_with_docs(db, name, query, raw, &block)
|
105
105
|
end
|
106
106
|
|
107
|
-
def all_design_doc_versions
|
108
|
-
|
107
|
+
def all_design_doc_versions(db = database)
|
108
|
+
db.documents :startkey => "_design/#{self.to_s}-",
|
109
109
|
:endkey => "_design/#{self.to_s}-\u9999"
|
110
110
|
end
|
111
111
|
|
@@ -114,11 +114,11 @@ module CouchRest
|
|
114
114
|
# and consistently using the latest code, is the way to clear out old design
|
115
115
|
# docs. Running it to early could mean that live code has to regenerate
|
116
116
|
# potentially large indexes.
|
117
|
-
def cleanup_design_docs!
|
118
|
-
ddocs = all_design_doc_versions
|
117
|
+
def cleanup_design_docs!(db = database)
|
118
|
+
ddocs = all_design_doc_versions(db)
|
119
119
|
ddocs["rows"].each do |row|
|
120
120
|
if (row['id'] != design_doc_id)
|
121
|
-
|
121
|
+
db.delete_doc({
|
122
122
|
"_id" => row['id'],
|
123
123
|
"_rev" => row['value']['rev']
|
124
124
|
})
|
@@ -128,30 +128,31 @@ module CouchRest
|
|
128
128
|
|
129
129
|
private
|
130
130
|
|
131
|
-
def fetch_view_with_docs(name, opts, raw=false, &block)
|
131
|
+
def fetch_view_with_docs(db, name, opts, raw=false, &block)
|
132
132
|
if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
|
133
|
-
fetch_view(name, opts, &block)
|
133
|
+
fetch_view(db, name, opts, &block)
|
134
134
|
else
|
135
135
|
begin
|
136
|
-
view = fetch_view name, opts.merge({:include_docs => true}), &block
|
136
|
+
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
|
137
137
|
view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
138
138
|
rescue
|
139
139
|
# fallback for old versions of couchdb that don't
|
140
140
|
# have include_docs support
|
141
|
-
view = fetch_view(name, opts, &block)
|
142
|
-
view['rows'].collect{|r|new(
|
141
|
+
view = fetch_view(db, name, opts, &block)
|
142
|
+
view['rows'].collect{|r|new(db.get(r['id']))} if view['rows']
|
143
143
|
end
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
-
def fetch_view view_name, opts, &block
|
147
|
+
def fetch_view(db, view_name, opts, &block)
|
148
|
+
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
|
148
149
|
retryable = true
|
149
150
|
begin
|
150
|
-
design_doc.
|
151
|
-
# the design doc
|
151
|
+
design_doc.view_on(db, view_name, opts, &block)
|
152
|
+
# the design doc may not have been saved yet on this database
|
152
153
|
rescue RestClient::ResourceNotFound => e
|
153
154
|
if retryable
|
154
|
-
|
155
|
+
save_design_doc_on(db)
|
155
156
|
retryable = false
|
156
157
|
retry
|
157
158
|
else
|
@@ -11,6 +11,7 @@ module CouchRest
|
|
11
11
|
include CouchRest::Mixins::Views
|
12
12
|
include CouchRest::Mixins::DesignDoc
|
13
13
|
include CouchRest::Mixins::ExtendedAttachments
|
14
|
+
include CouchRest::Mixins::ClassProxy
|
14
15
|
|
15
16
|
def self.inherited(subklass)
|
16
17
|
subklass.send(:include, CouchRest::Mixins::Properties)
|
@@ -77,10 +78,10 @@ module CouchRest
|
|
77
78
|
end
|
78
79
|
|
79
80
|
# Temp solution to make the view_by methods available
|
80
|
-
def self.method_missing(m, *args)
|
81
|
+
def self.method_missing(m, *args, &block)
|
81
82
|
if has_view?(m)
|
82
83
|
query = args.shift || {}
|
83
|
-
view(m, query, *args)
|
84
|
+
view(m, query, *args, &block)
|
84
85
|
else
|
85
86
|
super
|
86
87
|
end
|
@@ -89,7 +89,7 @@ module CouchRest
|
|
89
89
|
#
|
90
90
|
# @param <Symbol> field_name the name of the field you want an error for
|
91
91
|
def on(field_name)
|
92
|
-
errors_for_field = errors[field_name]
|
92
|
+
errors_for_field = errors[field_name.to_sym]
|
93
93
|
errors_for_field.blank? ? nil : errors_for_field
|
94
94
|
end
|
95
95
|
|
@@ -7,7 +7,15 @@ describe CouchRest::Database do
|
|
7
7
|
@db.delete! rescue nil
|
8
8
|
@db = @cr.create_db(TESTDB) rescue nil
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
|
+
describe "database name including slash" do
|
12
|
+
it "should escape the name in the URI" do
|
13
|
+
db = @cr.database("foo/bar")
|
14
|
+
db.name.should == "foo/bar"
|
15
|
+
db.uri.should == "#{COUCHHOST}/foo%2Fbar"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
11
19
|
describe "map query with _temp_view in Javascript" do
|
12
20
|
before(:each) do
|
13
21
|
@db.bulk_save([
|
@@ -14,7 +14,7 @@ describe CouchRest::Design do
|
|
14
14
|
describe "with an unsaved view" do
|
15
15
|
before(:each) do
|
16
16
|
@des = CouchRest::Design.new
|
17
|
-
|
17
|
+
@des.view_by :name
|
18
18
|
end
|
19
19
|
it "should accept a name" do
|
20
20
|
@des.name = "mytest"
|
@@ -31,7 +31,7 @@ describe CouchRest::Design do
|
|
31
31
|
describe "saving" do
|
32
32
|
before(:each) do
|
33
33
|
@des = CouchRest::Design.new
|
34
|
-
|
34
|
+
@des.view_by :name
|
35
35
|
@des.database = reset_test_db!
|
36
36
|
end
|
37
37
|
it "should fail without a name" do
|
@@ -49,7 +49,7 @@ describe CouchRest::Design do
|
|
49
49
|
@db.bulk_save([{"name" => "x"},{"name" => "y"}])
|
50
50
|
@des = CouchRest::Design.new
|
51
51
|
@des.database = @db
|
52
|
-
|
52
|
+
@des.view_by :name
|
53
53
|
end
|
54
54
|
it "should by queryable when it's saved" do
|
55
55
|
@des.name = "mydesign"
|
@@ -57,6 +57,13 @@ describe CouchRest::Design do
|
|
57
57
|
res = @des.view :by_name
|
58
58
|
res["rows"][0]["key"].should == "x"
|
59
59
|
end
|
60
|
+
it "should be queryable on specified database" do
|
61
|
+
@des.name = "mydesign"
|
62
|
+
@des.save
|
63
|
+
@des.database = nil
|
64
|
+
res = @des.view_on @db, :by_name
|
65
|
+
res["rows"][0]["key"].should == "x"
|
66
|
+
end
|
60
67
|
end
|
61
68
|
|
62
69
|
describe "from a saved document" do
|
@@ -92,7 +99,7 @@ describe CouchRest::Design do
|
|
92
99
|
@db = reset_test_db!
|
93
100
|
@des = CouchRest::Design.new
|
94
101
|
@des.name = "test"
|
95
|
-
|
102
|
+
@des.view_by :name, :descending => true
|
96
103
|
@des.database = @db
|
97
104
|
@des.save
|
98
105
|
@db.bulk_save([{"name" => "a"},{"name" => "z"}])
|
@@ -116,7 +123,7 @@ describe CouchRest::Design do
|
|
116
123
|
@db = reset_test_db!
|
117
124
|
@des = CouchRest::Design.new
|
118
125
|
@des.name = "test"
|
119
|
-
|
126
|
+
@des.view_by :name, :age
|
120
127
|
@des.database = @db
|
121
128
|
@des.save
|
122
129
|
@db.bulk_save([{"name" => "a", "age" => 2},
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
2
|
require File.join(FIXTURE_PATH, 'more', 'card')
|
3
|
+
require File.join(FIXTURE_PATH, 'more', 'course')
|
3
4
|
|
4
5
|
# add a default value
|
5
6
|
Card.property :bg_color, :default => '#ccc'
|
@@ -13,6 +14,18 @@ class DesignBusinessCard < BusinessCard
|
|
13
14
|
property :bg_color, :default => '#eee'
|
14
15
|
end
|
15
16
|
|
17
|
+
class OnlineCourse < Course
|
18
|
+
property :url
|
19
|
+
view_by :url
|
20
|
+
end
|
21
|
+
|
22
|
+
class Animal < CouchRest::ExtendedDocument
|
23
|
+
use_database TEST_SERVER.default_database
|
24
|
+
property :name
|
25
|
+
view_by :name
|
26
|
+
end
|
27
|
+
|
28
|
+
class Dog < Animal; end
|
16
29
|
|
17
30
|
describe "Subclassing an ExtendedDocument" do
|
18
31
|
|
@@ -42,6 +55,12 @@ describe "Subclassing an ExtendedDocument" do
|
|
42
55
|
validated_fields.should include(:job_title)
|
43
56
|
end
|
44
57
|
|
58
|
+
it "should not add to the parent's validations" do
|
59
|
+
validated_fields = Card.validators.contexts[:default].map{|v| v.field_name}
|
60
|
+
validated_fields.should_not include(:extension_code)
|
61
|
+
validated_fields.should_not include(:job_title)
|
62
|
+
end
|
63
|
+
|
45
64
|
it "should inherit default property values" do
|
46
65
|
@card.bg_color.should == '#ccc'
|
47
66
|
end
|
@@ -50,5 +69,30 @@ describe "Subclassing an ExtendedDocument" do
|
|
50
69
|
DesignBusinessCard.new.bg_color.should == '#eee'
|
51
70
|
end
|
52
71
|
|
72
|
+
it "should have a design doc slug based on the subclass name" do
|
73
|
+
Course.refresh_design_doc
|
74
|
+
OnlineCourse.design_doc_slug.should =~ /^OnlineCourse/
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should have its own design_doc_fresh" do
|
78
|
+
Animal.refresh_design_doc
|
79
|
+
Dog.design_doc_fresh.should_not == true
|
80
|
+
Dog.refresh_design_doc
|
81
|
+
Dog.design_doc_fresh.should == true
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should not add views to the parent's design_doc" do
|
85
|
+
Course.design_doc['views'].keys.should_not include('by_url')
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should not add the parent's views to its design doc" do
|
89
|
+
Course.refresh_design_doc
|
90
|
+
OnlineCourse.refresh_design_doc
|
91
|
+
OnlineCourse.design_doc['views'].keys.should_not include('by_title')
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should have an all view with a guard clause for couchrest-type == subclass name in the map function" do
|
95
|
+
OnlineCourse.design_doc['views']['all']['map'].should =~ /if \(doc\['couchrest-type'\] == 'OnlineCourse'\)/
|
96
|
+
end
|
53
97
|
end
|
54
98
|
|
@@ -3,6 +3,14 @@ require File.join(FIXTURE_PATH, 'more', 'article')
|
|
3
3
|
require File.join(FIXTURE_PATH, 'more', 'course')
|
4
4
|
|
5
5
|
describe "ExtendedDocument views" do
|
6
|
+
|
7
|
+
class Unattached < CouchRest::ExtendedDocument
|
8
|
+
# Note: no use_database here
|
9
|
+
property :title
|
10
|
+
property :questions
|
11
|
+
property :professor
|
12
|
+
view_by :title
|
13
|
+
end
|
6
14
|
|
7
15
|
describe "a model with simple views and a default param" do
|
8
16
|
before(:all) do
|
@@ -75,12 +83,18 @@ describe "ExtendedDocument views" do
|
|
75
83
|
end
|
76
84
|
it "should yield" do
|
77
85
|
courses = []
|
78
|
-
rs = Course.by_title # remove me
|
79
86
|
Course.view(:by_title) do |course|
|
80
87
|
courses << course
|
81
88
|
end
|
82
89
|
courses[0]["doc"]["title"].should =='aaa'
|
83
90
|
end
|
91
|
+
it "should yield with by_key method" do
|
92
|
+
courses = []
|
93
|
+
Course.by_title do |course|
|
94
|
+
courses << course
|
95
|
+
end
|
96
|
+
courses[0]["doc"]["title"].should =='aaa'
|
97
|
+
end
|
84
98
|
end
|
85
99
|
|
86
100
|
|
@@ -103,6 +117,144 @@ describe "ExtendedDocument views" do
|
|
103
117
|
end
|
104
118
|
end
|
105
119
|
|
120
|
+
describe "a model class not tied to a database" do
|
121
|
+
before(:all) do
|
122
|
+
reset_test_db!
|
123
|
+
@db = TEST_SERVER.default_database
|
124
|
+
%w{aaa bbb ddd eee}.each do |title|
|
125
|
+
u = Unattached.new(:title => title)
|
126
|
+
u.database = @db
|
127
|
+
u.save
|
128
|
+
@first_id ||= u.id
|
129
|
+
end
|
130
|
+
end
|
131
|
+
it "should barf on all if no database given" do
|
132
|
+
lambda{Unattached.all}.should raise_error
|
133
|
+
end
|
134
|
+
it "should query all" do
|
135
|
+
rs = Unattached.all :database=>@db
|
136
|
+
rs.length.should == 4
|
137
|
+
end
|
138
|
+
it "should barf on query if no database given" do
|
139
|
+
lambda{Unattached.view :by_title}.should raise_error
|
140
|
+
end
|
141
|
+
it "should make the design doc upon first query" do
|
142
|
+
Unattached.by_title :database=>@db
|
143
|
+
doc = Unattached.design_doc
|
144
|
+
doc['views']['all']['map'].should include('Unattached')
|
145
|
+
end
|
146
|
+
it "should merge query params" do
|
147
|
+
rs = Unattached.by_title :database=>@db, :startkey=>"bbb", :endkey=>"eee"
|
148
|
+
rs.length.should == 3
|
149
|
+
end
|
150
|
+
it "should query via view" do
|
151
|
+
view = Unattached.view :by_title, :database=>@db
|
152
|
+
designed = Unattached.by_title :database=>@db
|
153
|
+
view.should == designed
|
154
|
+
end
|
155
|
+
it "should yield" do
|
156
|
+
things = []
|
157
|
+
Unattached.view(:by_title, :database=>@db) do |thing|
|
158
|
+
things << thing
|
159
|
+
end
|
160
|
+
things[0]["doc"]["title"].should =='aaa'
|
161
|
+
end
|
162
|
+
it "should yield with by_key method" do
|
163
|
+
things = []
|
164
|
+
Unattached.by_title(:database=>@db) do |thing|
|
165
|
+
things << thing
|
166
|
+
end
|
167
|
+
things[0]["doc"]["title"].should =='aaa'
|
168
|
+
end
|
169
|
+
it "should barf on get if no database given" do
|
170
|
+
lambda{Unattached.get("aaa")}.should raise_error
|
171
|
+
end
|
172
|
+
it "should get from specific database" do
|
173
|
+
u = Unattached.get(@first_id, @db)
|
174
|
+
u.title.should == "aaa"
|
175
|
+
end
|
176
|
+
it "should barf on first if no database given" do
|
177
|
+
lambda{Unattached.first}.should raise_error
|
178
|
+
end
|
179
|
+
it "should get first" do
|
180
|
+
u = Unattached.first :database=>@db
|
181
|
+
u.title.should =~ /\A...\z/
|
182
|
+
end
|
183
|
+
it "should barf on all_design_doc_versions if no database given" do
|
184
|
+
lambda{Unattached.all_design_doc_versions}.should raise_error
|
185
|
+
end
|
186
|
+
it "should clean up design docs left around on specific database" do
|
187
|
+
Unattached.by_title :database=>@db
|
188
|
+
Unattached.all_design_doc_versions(@db)["rows"].length.should == 1
|
189
|
+
Unattached.view_by :questions
|
190
|
+
Unattached.by_questions :database=>@db
|
191
|
+
Unattached.all_design_doc_versions(@db)["rows"].length.should == 2
|
192
|
+
Unattached.cleanup_design_docs!(@db)
|
193
|
+
Unattached.all_design_doc_versions(@db)["rows"].length.should == 1
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe "class proxy" do
|
198
|
+
before(:all) do
|
199
|
+
reset_test_db!
|
200
|
+
@us = Unattached.on(TEST_SERVER.default_database)
|
201
|
+
%w{aaa bbb ddd eee}.each do |title|
|
202
|
+
u = @us.new(:title => title)
|
203
|
+
u.save
|
204
|
+
@first_id ||= u.id
|
205
|
+
end
|
206
|
+
end
|
207
|
+
it "should query all" do
|
208
|
+
rs = @us.all
|
209
|
+
rs.length.should == 4
|
210
|
+
end
|
211
|
+
it "should make the design doc upon first query" do
|
212
|
+
@us.by_title
|
213
|
+
doc = @us.design_doc
|
214
|
+
doc['views']['all']['map'].should include('Unattached')
|
215
|
+
end
|
216
|
+
it "should merge query params" do
|
217
|
+
rs = @us.by_title :startkey=>"bbb", :endkey=>"eee"
|
218
|
+
rs.length.should == 3
|
219
|
+
end
|
220
|
+
it "should query via view" do
|
221
|
+
view = @us.view :by_title
|
222
|
+
designed = @us.by_title
|
223
|
+
view.should == designed
|
224
|
+
end
|
225
|
+
it "should yield" do
|
226
|
+
things = []
|
227
|
+
@us.view(:by_title) do |thing|
|
228
|
+
things << thing
|
229
|
+
end
|
230
|
+
things[0]["doc"]["title"].should =='aaa'
|
231
|
+
end
|
232
|
+
it "should yield with by_key method" do
|
233
|
+
things = []
|
234
|
+
@us.by_title do |thing|
|
235
|
+
things << thing
|
236
|
+
end
|
237
|
+
things[0]["doc"]["title"].should =='aaa'
|
238
|
+
end
|
239
|
+
it "should get from specific database" do
|
240
|
+
u = @us.get(@first_id)
|
241
|
+
u.title.should == "aaa"
|
242
|
+
end
|
243
|
+
it "should get first" do
|
244
|
+
u = @us.first
|
245
|
+
u.title.should =~ /\A...\z/
|
246
|
+
end
|
247
|
+
it "should clean up design docs left around on specific database" do
|
248
|
+
@us.by_title
|
249
|
+
@us.all_design_doc_versions["rows"].length.should == 1
|
250
|
+
Unattached.view_by :professor
|
251
|
+
@us.by_professor
|
252
|
+
@us.all_design_doc_versions["rows"].length.should == 2
|
253
|
+
@us.cleanup_design_docs!
|
254
|
+
@us.all_design_doc_versions["rows"].length.should == 1
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
106
258
|
describe "a model with a compound key view" do
|
107
259
|
before(:all) do
|
108
260
|
Article.design_doc_fresh = false
|
@@ -177,14 +329,11 @@ describe "ExtendedDocument views" do
|
|
177
329
|
newdocs["rows"].length.should == @design_docs["rows"].length
|
178
330
|
end
|
179
331
|
it "should create a new version of the design document on view access" do
|
180
|
-
|
332
|
+
ddocs = Article.all_design_doc_versions["rows"].length
|
181
333
|
Article.view_by :updated_at
|
182
334
|
Article.by_updated_at
|
183
|
-
|
184
|
-
|
185
|
-
doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
|
186
|
-
doc["_rev"].should_not == old_design_doc["_rev"]
|
187
|
-
doc["views"].keys.should include("by_updated_at")
|
335
|
+
Article.all_design_doc_versions["rows"].length.should == ddocs + 1
|
336
|
+
Article.design_doc["views"].keys.should include("by_updated_at")
|
188
337
|
end
|
189
338
|
end
|
190
339
|
|
@@ -196,12 +345,11 @@ describe "ExtendedDocument views" do
|
|
196
345
|
Article.by_field
|
197
346
|
end
|
198
347
|
it "should clean them up" do
|
199
|
-
ddocs = Article.all_design_doc_versions
|
200
348
|
Article.view_by :stream
|
201
349
|
Article.by_stream
|
350
|
+
Article.all_design_doc_versions["rows"].length.should > 1
|
202
351
|
Article.cleanup_design_docs!
|
203
|
-
|
204
|
-
ddocs["rows"].length.should == 1
|
352
|
+
Article.all_design_doc_versions["rows"].length.should == 1
|
205
353
|
end
|
206
354
|
end
|
207
355
|
end
|
@@ -59,6 +59,12 @@ describe "ExtendedDocument properties" do
|
|
59
59
|
@card.errors.should_not be_empty
|
60
60
|
@card.errors.on(:first_name).should == ["First name must not be blank"]
|
61
61
|
end
|
62
|
+
|
63
|
+
it "should let you look up errors for a field by a string name" do
|
64
|
+
@card.first_name = nil
|
65
|
+
@card.should_not be_valid
|
66
|
+
@card.errors.on('first_name').should == ["First name must not be blank"]
|
67
|
+
end
|
62
68
|
|
63
69
|
it "should validate the presence of 2 attributes" do
|
64
70
|
@invoice.clear
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jchris-couchrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.23"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- J. Chris Anderson
|
@@ -93,6 +93,7 @@ files:
|
|
93
93
|
- lib/couchrest/mixins
|
94
94
|
- lib/couchrest/mixins/attachments.rb
|
95
95
|
- lib/couchrest/mixins/callbacks.rb
|
96
|
+
- lib/couchrest/mixins/class_proxy.rb
|
96
97
|
- lib/couchrest/mixins/design_doc.rb
|
97
98
|
- lib/couchrest/mixins/document_queries.rb
|
98
99
|
- lib/couchrest/mixins/extended_attachments.rb
|
@@ -149,26 +150,6 @@ files:
|
|
149
150
|
- spec/fixtures/attachments/couchdb.png
|
150
151
|
- spec/fixtures/attachments/README
|
151
152
|
- spec/fixtures/attachments/test.html
|
152
|
-
- spec/fixtures/couchapp
|
153
|
-
- spec/fixtures/couchapp/_attachments
|
154
|
-
- spec/fixtures/couchapp/_attachments/index.html
|
155
|
-
- spec/fixtures/couchapp/doc.json
|
156
|
-
- spec/fixtures/couchapp/foo
|
157
|
-
- spec/fixtures/couchapp/foo/bar.txt
|
158
|
-
- spec/fixtures/couchapp/foo/test.json
|
159
|
-
- spec/fixtures/couchapp/test.json
|
160
|
-
- spec/fixtures/couchapp/views
|
161
|
-
- spec/fixtures/couchapp/views/example-map.js
|
162
|
-
- spec/fixtures/couchapp/views/example-reduce.js
|
163
|
-
- spec/fixtures/couchapp-test
|
164
|
-
- spec/fixtures/couchapp-test/my-app
|
165
|
-
- spec/fixtures/couchapp-test/my-app/_attachments
|
166
|
-
- spec/fixtures/couchapp-test/my-app/_attachments/index.html
|
167
|
-
- spec/fixtures/couchapp-test/my-app/foo
|
168
|
-
- spec/fixtures/couchapp-test/my-app/foo/bar.txt
|
169
|
-
- spec/fixtures/couchapp-test/my-app/views
|
170
|
-
- spec/fixtures/couchapp-test/my-app/views/example-map.js
|
171
|
-
- spec/fixtures/couchapp-test/my-app/views/example-reduce.js
|
172
153
|
- spec/fixtures/more
|
173
154
|
- spec/fixtures/more/article.rb
|
174
155
|
- spec/fixtures/more/card.rb
|