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