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 CHANGED
@@ -35,7 +35,7 @@ spec = Gem::Specification.new do |s|
35
35
  end
36
36
 
37
37
 
38
- desc "create .gemspec file (useful for github)"
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.22' unless self.const_defined?("VERSION")
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
- fetch_view(view_slug, defaults.merge(query), &block)
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
- did = design_doc_id
44
- saved = database.get(did) rescue nil
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
- database.save_doc(saved)
50
- self.design_doc = saved
77
+ db.save_doc(saved)
78
+ saved
51
79
  else
52
- design_doc['_id'] = did
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 = database.get id
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
- subklass.instance_variable_set(:@validations, self.validators.dup)
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 compeletly,
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
- database.documents :startkey => "_design/#{self.to_s}-",
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
- database.delete_doc({
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(database.get(r['id']))} if view['rows']
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.view(view_name, opts, &block)
151
- # the design doc could have been deleted by a rouge process
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
- refresh_design_doc
155
+ save_design_doc_on(db)
155
156
  retryable = false
156
157
  retry
157
158
  else
@@ -56,12 +56,6 @@ module RestClient
56
56
  :url => url,
57
57
  :headers => headers)
58
58
  end
59
-
60
- def self.move(url, headers={})
61
- Request.execute(:method => :move,
62
- :url => url,
63
- :headers => headers)
64
- end
65
59
 
66
60
  # class Request
67
61
  #
@@ -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
- method = @des.view_by :name
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
- method = @des.view_by :name
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
- method = @des.view_by :name
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
- method = @des.view_by :name, :descending => true
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
- method = @des.view_by :name, :age
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
- old_design_doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
332
+ ddocs = Article.all_design_doc_versions["rows"].length
181
333
  Article.view_by :updated_at
182
334
  Article.by_updated_at
183
- newdocs = Article.database.documents({:startkey => "_design/", :endkey => "_design/\u9999"})
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
- ddocs = Article.all_design_doc_versions
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.22"
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