jchris-couchrest 0.22 → 0.23
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/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
|