mattetti-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']
@@ -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?
@@ -2,9 +2,19 @@ module CouchRest
2
2
  class Server
3
3
  attr_accessor :uri, :uuid_batch_count, :available_databases
4
4
  def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
5
- @uri = server
5
+ case server
6
+ when %r{\A(http://[^/]+)/(.*)\z}
7
+ @uri, @prefix = $1, $2
8
+ else
9
+ @uri, @prefix = server, ""
10
+ end
6
11
  @uuid_batch_count = uuid_batch_count
7
12
  end
13
+
14
+ # Add default prefix to database name
15
+ def expand(name)
16
+ @prefix + name
17
+ end
8
18
 
9
19
  # Lists all "available" databases.
10
20
  # An available database, is a database that was specified
@@ -32,7 +42,7 @@ module CouchRest
32
42
  # @couch.available_database?(:default)
33
43
  #
34
44
  def available_database?(ref_or_name)
35
- ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
45
+ ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(expand(ref_or_name))
36
46
  end
37
47
 
38
48
  def default_database=(name, create_unless_exists = true)
@@ -45,12 +55,17 @@ module CouchRest
45
55
 
46
56
  # Lists all databases on the server
47
57
  def databases
48
- CouchRest.get "#{@uri}/_all_dbs"
58
+ dbs = CouchRest.get "#{@uri}/_all_dbs"
59
+ unless @prefix.empty?
60
+ pfx = @prefix.gsub('/','%2F')
61
+ dbs.reject! { |db| db.index(pfx) != 0 }
62
+ end
63
+ dbs
49
64
  end
50
65
 
51
66
  # Returns a CouchRest::Database for the given name
52
67
  def database(name)
53
- CouchRest::Database.new(self, name)
68
+ CouchRest::Database.new(self, expand(name))
54
69
  end
55
70
 
56
71
  # Creates the database if it doesn't exist
@@ -66,7 +81,7 @@ module CouchRest
66
81
 
67
82
  # Create a database
68
83
  def create_db(name)
69
- CouchRest.put "#{@uri}/#{name}"
84
+ CouchRest.put "#{@uri}/#{expand(name).gsub('/','%2F')}"
70
85
  database(name)
71
86
  end
72
87
 
@@ -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
@@ -16,7 +16,7 @@ module CouchRest
16
16
  def design_doc_slug
17
17
  return design_doc_slug_cache if (design_doc_slug_cache && design_doc_fresh)
18
18
  funcs = []
19
- design_doc ||= Design.new(default_design_doc)
19
+ self.design_doc ||= Design.new(default_design_doc)
20
20
  design_doc['views'].each do |name, view|
21
21
  funcs << "#{name}/#{view['map']}#{view['reduce']}"
22
22
  end
@@ -40,21 +40,42 @@ module CouchRest
40
40
  end
41
41
 
42
42
  def refresh_design_doc
43
- did = design_doc_id
44
- saved = database.get(did) rescue nil
43
+ design_doc['_id'] = design_doc_id
44
+ design_doc.delete('_rev')
45
+ #design_doc.database = nil
46
+ self.design_doc_fresh = true
47
+ end
48
+
49
+ # Save the design doc onto the default database, and update the
50
+ # design_doc attribute
51
+ def save_design_doc
52
+ refresh_design_doc unless design_doc_fresh
53
+ self.design_doc = update_design_doc(design_doc)
54
+ end
55
+
56
+ # Save the design doc onto a target database in a thread-safe way,
57
+ # not modifying the model's design_doc
58
+ def save_design_doc_on(db)
59
+ update_design_doc(Design.new(design_doc), db)
60
+ end
61
+
62
+ private
63
+
64
+ # Writes out a design_doc to a given database, returning the
65
+ # updated design doc
66
+ def update_design_doc(design_doc, db = database)
67
+ saved = db.get(design_doc['_id']) rescue nil
45
68
  if saved
46
69
  design_doc['views'].each do |name, view|
47
70
  saved['views'][name] = view
48
71
  end
49
- database.save_doc(saved)
50
- self.design_doc = saved
72
+ db.save_doc(saved)
73
+ saved
51
74
  else
52
- design_doc['_id'] = did
53
- design_doc.delete('_rev')
54
- design_doc.database = database
75
+ design_doc.database = db
55
76
  design_doc.save
77
+ design_doc
56
78
  end
57
- self.design_doc_fresh = true
58
79
  end
59
80
 
60
81
  end # module ClassMethods
@@ -36,8 +36,8 @@ module CouchRest
36
36
  end
37
37
 
38
38
  # Load a document from the database by id
39
- def get(id)
40
- doc = database.get id
39
+ def get(id, db = database)
40
+ doc = db.get id
41
41
  new(doc)
42
42
  end
43
43
 
@@ -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,12 +4,14 @@ 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
10
+ attr_accessor :design_doc, :design_doc_slug_cache, :design_doc_fresh
11
+
12
+ def design_doc
13
+ @design_doc ||= Design.new(default_design_doc)
14
+ end
13
15
 
14
16
  # Define a CouchDB view. The name of the view will be the concatenation
15
17
  # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
@@ -57,6 +59,10 @@ module CouchRest
57
59
  # themselves. By default <tt>Post.by_date</tt> will return the
58
60
  # documents included in the generated view.
59
61
  #
62
+ # Calling with :database => [instance of CouchRest::Database] will
63
+ # send the query to a specific database, otherwise it will go to
64
+ # the model's default database (use_database)
65
+ #
60
66
  # CouchRest::Database#view options can be applied at view definition
61
67
  # time as defaults, and they will be curried and used at view query
62
68
  # time. Or they can be overridden at query time.
@@ -70,12 +76,11 @@ module CouchRest
70
76
  # that model won't be available until generation is complete. This can
71
77
  # take some time with large databases. Strategies are in the works.
72
78
  #
73
- # To understand the capabilities of this view system more compeletly,
79
+ # To understand the capabilities of this view system more completely,
74
80
  # it is recommended that you read the RSpec file at
75
81
  # <tt>spec/core/model_spec.rb</tt>.
76
82
 
77
83
  def view_by(*keys)
78
- self.design_doc ||= Design.new(default_design_doc)
79
84
  opts = keys.pop if keys.last.is_a?(Hash)
80
85
  opts ||= {}
81
86
  ducktype = opts.delete(:ducktype)
@@ -100,12 +105,13 @@ module CouchRest
100
105
  refresh_design_doc
101
106
  end
102
107
  query[:raw] = true if query[:reduce]
108
+ db = query.delete(:database) || database
103
109
  raw = query.delete(:raw)
104
- fetch_view_with_docs(name, query, raw, &block)
110
+ fetch_view_with_docs(db, name, query, raw, &block)
105
111
  end
106
112
 
107
- def all_design_doc_versions
108
- database.documents :startkey => "_design/#{self.to_s}-",
113
+ def all_design_doc_versions(db = database)
114
+ db.documents :startkey => "_design/#{self.to_s}-",
109
115
  :endkey => "_design/#{self.to_s}-\u9999"
110
116
  end
111
117
 
@@ -114,11 +120,11 @@ module CouchRest
114
120
  # and consistently using the latest code, is the way to clear out old design
115
121
  # docs. Running it to early could mean that live code has to regenerate
116
122
  # potentially large indexes.
117
- def cleanup_design_docs!
118
- ddocs = all_design_doc_versions
123
+ def cleanup_design_docs!(db = database)
124
+ ddocs = all_design_doc_versions(db)
119
125
  ddocs["rows"].each do |row|
120
126
  if (row['id'] != design_doc_id)
121
- database.delete_doc({
127
+ db.delete_doc({
122
128
  "_id" => row['id'],
123
129
  "_rev" => row['value']['rev']
124
130
  })
@@ -128,30 +134,31 @@ module CouchRest
128
134
 
129
135
  private
130
136
 
131
- def fetch_view_with_docs(name, opts, raw=false, &block)
137
+ def fetch_view_with_docs(db, name, opts, raw=false, &block)
132
138
  if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
133
- fetch_view(name, opts, &block)
139
+ fetch_view(db, name, opts, &block)
134
140
  else
135
141
  begin
136
- view = fetch_view name, opts.merge({:include_docs => true}), &block
142
+ view = fetch_view db, name, opts.merge({:include_docs => true}), &block
137
143
  view['rows'].collect{|r|new(r['doc'])} if view['rows']
138
144
  rescue
139
145
  # fallback for old versions of couchdb that don't
140
146
  # have include_docs support
141
- view = fetch_view(name, opts, &block)
142
- view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
147
+ view = fetch_view(db, name, opts, &block)
148
+ view['rows'].collect{|r|new(db.get(r['id']))} if view['rows']
143
149
  end
144
150
  end
145
151
  end
146
152
 
147
- def fetch_view view_name, opts, &block
153
+ def fetch_view(db, view_name, opts, &block)
154
+ raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
148
155
  retryable = true
149
156
  begin
150
- design_doc.view(view_name, opts, &block)
151
- # the design doc could have been deleted by a rouge process
157
+ design_doc.view_on(db, view_name, opts, &block)
158
+ # the design doc may not have been saved yet on this database
152
159
  rescue RestClient::ResourceNotFound => e
153
160
  if retryable
154
- refresh_design_doc
161
+ save_design_doc_on(db)
155
162
  retryable = false
156
163
  retry
157
164
  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
 
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'
@@ -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},
@@ -2,6 +2,36 @@ require File.dirname(__FILE__) + '/../../spec_helper'
2
2
 
3
3
  describe CouchRest::Server do
4
4
 
5
+ describe "named databases" do
6
+ it "should generate database without prefix" do
7
+ couch = CouchRest::Server.new "http://192.0.2.1:1234"
8
+ db = couch.database("foo")
9
+ db.name.should == "foo"
10
+ db.uri.should == "http://192.0.2.1:1234/foo"
11
+ end
12
+
13
+ it "should generate database with prefix" do
14
+ couch = CouchRest::Server.new "http://192.0.2.1:1234/dev"
15
+ db = couch.database("foo")
16
+ db.name.should == "devfoo"
17
+ db.uri.should == "http://192.0.2.1:1234/devfoo"
18
+ end
19
+
20
+ it "should generate database with prefix and slash" do
21
+ couch = CouchRest::Server.new "http://192.0.2.1:1234/dev/"
22
+ db = couch.database("foo")
23
+ db.name.should == "dev/foo"
24
+ db.uri.should == "http://192.0.2.1:1234/dev%2Ffoo"
25
+ end
26
+
27
+ it "should generate database with slashes" do
28
+ couch = CouchRest::Server.new "http://192.0.2.1:1234/dev/sample/"
29
+ db = couch.database("foo/bar")
30
+ db.name.should == "dev/sample/foo/bar"
31
+ db.uri.should == "http://192.0.2.1:1234/dev%2Fsample%2Ffoo%2Fbar"
32
+ end
33
+ end
34
+
5
35
  describe "available databases" do
6
36
  before(:each) do
7
37
  @couch = CouchRest::Server.new
@@ -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: mattetti-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