jchris-couchrest 0.9.12 → 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/{README.rdoc → README.md} +10 -8
  2. data/Rakefile +60 -39
  3. data/THANKS.md +18 -0
  4. data/examples/word_count/markov +1 -1
  5. data/examples/word_count/views/word_count/count-reduce.js +2 -2
  6. data/examples/word_count/word_count.rb +2 -2
  7. data/examples/word_count/word_count_query.rb +2 -2
  8. data/lib/couchrest/commands/push.rb +8 -4
  9. data/lib/couchrest/core/database.rb +95 -14
  10. data/lib/couchrest/core/design.rb +89 -0
  11. data/lib/couchrest/core/document.rb +63 -0
  12. data/lib/couchrest/core/model.rb +203 -120
  13. data/lib/couchrest/core/server.rb +1 -1
  14. data/lib/couchrest/core/view.rb +4 -0
  15. data/lib/couchrest/helper/pager.rb +10 -10
  16. data/lib/couchrest/monkeypatches.rb +1 -1
  17. data/lib/couchrest.rb +20 -2
  18. data/spec/couchrest/core/couchrest_spec.rb +33 -23
  19. data/spec/couchrest/core/database_spec.rb +163 -8
  20. data/spec/couchrest/core/design_spec.rb +131 -0
  21. data/spec/couchrest/core/document_spec.rb +130 -0
  22. data/spec/couchrest/core/model_spec.rb +319 -35
  23. data/spec/couchrest/helpers/pager_spec.rb +2 -2
  24. data/spec/fixtures/attachments/README +3 -0
  25. data/spec/spec_helper.rb +17 -3
  26. data/utils/remap.rb +2 -2
  27. data/utils/subset.rb +2 -2
  28. metadata +24 -33
  29. data/THANKS +0 -15
  30. data/bin/couchapp +0 -55
  31. data/bin/couchview +0 -48
  32. data/lib/couchrest/helper/file_manager.rb +0 -285
  33. data/lib/couchrest/helper/templates/example-map.js +0 -8
  34. data/lib/couchrest/helper/templates/example-reduce.js +0 -10
  35. data/lib/couchrest/helper/templates/index.html +0 -26
  36. data/spec/couchapp_spec.rb +0 -82
  37. data/spec/couchrest/helpers/file_manager_spec.rb +0 -170
@@ -1,4 +1,4 @@
1
- == CouchRest - CouchDB, close to the metal
1
+ # CouchRest: CouchDB, close to the metal
2
2
 
3
3
  CouchRest is based on [CouchDB's couch.js test
4
4
  library](http://svn.apache.org/repos/asf/incubator/couchdb/trunk/share/www/script/couch.js),
@@ -9,29 +9,29 @@ to CouchDB's API endpoints so you don't have to.
9
9
  CouchRest's lighweight is designed to make a simple base for application and
10
10
  framework-specific object oriented APIs.
11
11
 
12
- === Easy Install
12
+ ## Easy Install
13
13
 
14
- sudo gem install jchris-couchrest -s http://gems.github.com
14
+ Easy Install is moving to RubyForge, heads up for the gem.
15
15
 
16
- === Relax, it's RESTful
16
+ ### Relax, it's RESTful
17
17
 
18
18
  The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper.
19
19
  REST Client takes all the nastyness of Net::HTTP and gives is a pretty face,
20
20
  while still giving you more control than Open-URI. I recommend it anytime
21
21
  you’re interfacing with a well-defined web service.
22
22
 
23
- === Running the Specs
23
+ ### Running the Specs
24
24
 
25
25
  The most complete documentation is the spec/ directory. To validate your
26
26
  CouchRest install, from the project root directory run `rake`, or `autotest`
27
27
  (requires RSpec and optionally ZenTest for autotest support).
28
28
 
29
- === Examples
29
+ ## Examples
30
30
 
31
31
  Quick Start:
32
32
 
33
33
  # with !, it creates the database if it doesn't already exist
34
- @db = CouchRest.database!("http://localhost:5984/couchrest-test")
34
+ @db = CouchRest.database!("http://127.0.0.1:5984/couchrest-test")
35
35
  response = @db.save({:key => 'value', 'another key' => 'another value'})
36
36
  doc = @db.get(response['id'])
37
37
  puts doc.inspect
@@ -58,10 +58,12 @@ Creating and Querying Views:
58
58
  })
59
59
  puts @db.view('first/test')['rows'].inspect
60
60
 
61
- == CouchRest::Model
61
+ ## CouchRest::Model
62
62
 
63
63
  CouchRest::Model is a module designed along the lines of DataMapper::Resource.
64
64
  By subclassing, suddenly you get all sorts of powerful sugar, so that working
65
65
  with CouchDB in your Rails or Merb app is no harder than working with the
66
66
  standard SQL alternatives. See the CouchRest::Model documentation for an
67
67
  example article class that illustrates usage.
68
+
69
+ CouchRest::Model will be removed from this package.
data/Rakefile CHANGED
@@ -1,64 +1,85 @@
1
1
  require 'rake'
2
2
  require "rake/rdoctask"
3
- require 'spec/rake/spectask'
3
+ require 'rake/gempackagetask'
4
+ require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest')
4
5
 
5
6
 
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
6
17
  spec = Gem::Specification.new do |s|
7
18
  s.name = "couchrest"
8
- s.version = "0.9.12"
9
- s.date = "2008-10-14"
19
+ s.version = CouchRest::VERSION
20
+ s.date = "2008-11-22"
10
21
  s.summary = "Lean and RESTful interface to CouchDB."
11
- s.email = "jchris@grabb.it"
22
+ s.email = "jchris@apache.org"
12
23
  s.homepage = "http://github.com/jchris/couchrest"
13
24
  s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
14
25
  s.has_rdoc = true
15
26
  s.authors = ["J. Chris Anderson"]
16
- s.files = %w( LICENSE README.rdoc Rakefile THANKS ) + Dir["{bin,examples,lib,spec,utils}/**/*"]
17
- s.extra_rdoc_files = %w( README.rdoc LICENSE THANKS )
27
+ s.files = %w( LICENSE README.md Rakefile THANKS.md ) +
28
+ Dir["{bin,examples,lib,spec,utils}/**/*"] -
29
+ Dir["spec/tmp"]
30
+ s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
18
31
  s.require_path = "lib"
19
32
  s.bindir = 'bin'
20
- s.executables << 'couchview'
21
33
  s.executables << 'couchdir'
22
- s.executables << 'couchapp'
23
34
  s.add_dependency("json", ">= 1.1.2")
24
35
  s.add_dependency("rest-client", ">= 0.5")
36
+ s.add_dependency("mime-types", ">= 1.15")
25
37
  s.add_dependency("extlib", ">= 0.9.6")
26
38
  end
27
39
 
28
- desc "Update Github Gemspec"
29
- task :gemspec do
30
- skip_fields = %w(new_platform original_platform)
31
- integer_fields = %w(specification_version)
32
40
 
33
- result = "Gem::Specification.new do |s|\n"
34
- spec.instance_variables.each do |ivar|
35
- value = spec.instance_variable_get(ivar)
36
- name = ivar.split("@").last
37
- next if skip_fields.include?(name) || value.nil? || value == "" || (value.respond_to?(:empty?) && value.empty?)
38
- if name == "dependencies"
39
- value.each do |d|
40
- dep, *ver = d.to_s.split(" ")
41
- result << " s.add_dependency #{dep.inspect}, [#{ /\(([^\,]*)/ . match(ver.join(" "))[1].inspect}]\n"
42
- end
43
- else
44
- case value
45
- when Array
46
- value = name != "files" ? value.inspect : value.inspect.split(",").join(",\n")
47
- when Fixnum
48
- # leave as-is
49
- when String
50
- value = value.to_i if integer_fields.include?(name)
51
- value = value.inspect
52
- else
53
- value = value.to_s.inspect
54
- end
55
- result << " s.#{name} = #{value}\n"
56
- end
41
+ desc "create .gemspec file (useful for github)"
42
+ task :gemspec do
43
+ filename = "#{spec.name}.gemspec"
44
+ File.open(filename, "w") do |f|
45
+ f.puts spec.to_ruby
57
46
  end
58
- result << "end"
59
- File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
60
47
  end
61
48
 
49
+ # desc "Update Github Gemspec"
50
+ # task :gemspec do
51
+ # skip_fields = %w(new_platform original_platform)
52
+ # integer_fields = %w(specification_version)
53
+ #
54
+ # result = "Gem::Specification.new do |s|\n"
55
+ # spec.instance_variables.each do |ivar|
56
+ # value = spec.instance_variable_get(ivar)
57
+ # name = ivar.split("@").last
58
+ # next if skip_fields.include?(name) || value.nil? || value == "" || (value.respond_to?(:empty?) && value.empty?)
59
+ # if name == "dependencies"
60
+ # value.each do |d|
61
+ # dep, *ver = d.to_s.split(" ")
62
+ # result << " s.add_dependency #{dep.inspect}, [#{ /\(([^\,]*)/ . match(ver.join(" "))[1].inspect}]\n"
63
+ # end
64
+ # else
65
+ # case value
66
+ # when Array
67
+ # value = name != "files" ? value.inspect : value.inspect.split(",").join(",\n")
68
+ # when Fixnum
69
+ # # leave as-is
70
+ # when String
71
+ # value = value.to_i if integer_fields.include?(name)
72
+ # value = value.inspect
73
+ # else
74
+ # value = value.to_s.inspect
75
+ # end
76
+ # result << " s.#{name} = #{value}\n"
77
+ # end
78
+ # end
79
+ # result << "end"
80
+ # File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
81
+ # end
82
+
62
83
  desc "Run all specs"
63
84
  Spec::Rake::SpecTask.new('spec') do |t|
64
85
  t.spec_files = FileList['spec/**/*_spec.rb']
@@ -78,5 +99,5 @@ Rake::RDocTask.new do |rdoc|
78
99
  rdoc.title = "CouchRest: Ruby CouchDB, close to the metal"
79
100
  end
80
101
 
81
- desc "Generate the gemspec"
102
+ desc "Run the rspec"
82
103
  task :default => :spec
data/THANKS.md ADDED
@@ -0,0 +1,18 @@
1
+ CouchRest THANKS
2
+ =====================
3
+
4
+ CouchRest was originally developed by J. Chris Anderson <jchris@grabb.it>
5
+ and a number of other contributors. Many people further contributed to
6
+ CouchRest by reporting problems, suggesting various improvements or submitting
7
+ changes. A list of these people is included below.
8
+
9
+ * [Matt Aimonetti](http://merbist.com/about/)
10
+ * [Greg Borenstein](http://ideasfordozens.com)
11
+ * [Geoffrey Grosenbach](http://nubyonrails.com/)
12
+ * [Jonathan S. Katz](http://github.com/jkatz)
13
+ * [Matt Lyon](http://mattly.tumblr.com/)
14
+ * Simon Rozet (simon /at/ rozet /dot/ name)
15
+
16
+ Patches are welcome. The primary source for this software project is [on Github](http://github.com/jchris/couchrest/tree/master)
17
+
18
+ A lot of people have active forks - thank you all - even the patches I don't end up using are helpful.
@@ -2,7 +2,7 @@
2
2
 
3
3
  require File.expand_path(File.dirname(__FILE__)) + '/../../couchrest'
4
4
 
5
- cr = CouchRest.new("http://localhost:5984")
5
+ cr = CouchRest.new("http://127.0.0.1:5984")
6
6
  @db = cr.database('word-count-example')
7
7
  @word_memoizer = {}
8
8
 
@@ -1,3 +1,3 @@
1
- function(key,combine){
2
- return sum(combine);
1
+ function(key,values){
2
+ return sum(values);
3
3
  }
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/../../couchrest'
2
2
 
3
- couch = CouchRest.new("http://localhost:5984")
3
+ couch = CouchRest.new("http://127.0.0.1:5984")
4
4
  db = couch.database('word-count-example')
5
5
  db.delete! rescue nil
6
6
  db = couch.create_db('word-count-example')
@@ -62,6 +62,6 @@ end
62
62
  # }
63
63
  # })
64
64
 
65
- # puts "The books have been stored in your CouchDB. To initiate the MapReduce process, visit http://localhost:5984/_utils/ in your browser and click 'word-count-example', then select view 'words' or 'count'. The process could take about 15 minutes on an average MacBook."
65
+ # puts "The books have been stored in your CouchDB. To initiate the MapReduce process, visit http://127.0.0.1:5984/_utils/ in your browser and click 'word-count-example', then select view 'words' or 'count'. The process could take about 15 minutes on an average MacBook."
66
66
  #
67
67
 
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/../../couchrest'
2
2
 
3
- couch = CouchRest.new("http://localhost:5984")
3
+ couch = CouchRest.new("http://127.0.0.1:5984")
4
4
  db = couch.database('word-count-example')
5
5
 
6
6
  puts "Now that we've parsed all those books into CouchDB, the queries we can run are incredibly flexible."
@@ -35,5 +35,5 @@ puts "\nHere are the params for 'flight' in the da-vinci book:"
35
35
  puts params.inspect
36
36
  puts
37
37
  puts 'The url looks like this:'
38
- puts 'http://localhost:5984/word-count-example/_view/word_count/count?key=["flight","da-vinci"]'
38
+ puts 'http://127.0.0.1:5984/word-count-example/_view/word_count/count?key=["flight","da-vinci"]'
39
39
  puts "\nTry dropping that in your browser..."
@@ -10,7 +10,11 @@ module CouchRest
10
10
 
11
11
  fm = CouchRest::FileManager.new(database)
12
12
  fm.loud = options[:loud]
13
- puts "Pushing views from directory #{directory} to database #{fm.db}"
13
+
14
+ if options[:loud]
15
+ puts "Pushing views from directory #{directory} to database #{fm.db}"
16
+ end
17
+
14
18
  fm.push_views(directory)
15
19
  end
16
20
 
@@ -48,7 +52,7 @@ module CouchRest
48
52
  foo-project/bar-views/my-design/viewname-reduce.js
49
53
  foo-project/bar-views/my-design/noreduce-map.js
50
54
 
51
- Pushed to => http://localhost:5984/baz-database/_design/my-design
55
+ Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
52
56
 
53
57
  And the design document:
54
58
  {
@@ -77,11 +81,11 @@ module CouchRest
77
81
  (for project global libs). These libraries are only inserted into views which
78
82
  include the text
79
83
 
80
- //include-lib
84
+ // !include lib
81
85
 
82
86
  or
83
87
 
84
- #include-lib
88
+ # !include lib
85
89
 
86
90
  Couchview is a result of scratching my own itch. I'd be happy to make it more
87
91
  general, so please contact me at jchris@grabb.it if you'd like to see anything
@@ -4,6 +4,7 @@ require "base64"
4
4
  module CouchRest
5
5
  class Database
6
6
  attr_reader :server, :host, :name, :root
7
+ attr_accessor :bulk_save_cache_limit
7
8
 
8
9
  # Create a CouchRest::Database adapter for the supplied CouchRest::Server
9
10
  # and database name.
@@ -18,6 +19,8 @@ module CouchRest
18
19
  @host = server.uri
19
20
  @root = "#{host}/#{name}"
20
21
  @streamer = Streamer.new(self)
22
+ @bulk_save_cache = []
23
+ @bulk_save_cache_limit = 50
21
24
  end
22
25
 
23
26
  # returns the database's uri
@@ -44,12 +47,15 @@ module CouchRest
44
47
  # POST a temporary view function to CouchDB for querying. This is not
45
48
  # recommended, as you don't get any performance benefit from CouchDB's
46
49
  # materialized views. Can be quite slow on large databases.
47
- def temp_view funcs, params = {}
50
+ def slow_view funcs, params = {}
48
51
  keys = params.delete(:keys)
49
52
  funcs = funcs.merge({:keys => keys}) if keys
50
- url = CouchRest.paramify_url "#{@root}/_temp_view", params
53
+ url = CouchRest.paramify_url "#{@root}/_slow_view", params
51
54
  JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
52
55
  end
56
+
57
+ # backwards compatibility is a plus
58
+ alias :temp_view :slow_view
53
59
 
54
60
  # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
55
61
  # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
@@ -69,20 +75,27 @@ module CouchRest
69
75
 
70
76
  # GET a document from CouchDB, by id. Returns a Ruby Hash.
71
77
  def get id
72
- slug = CGI.escape(id)
73
- CouchRest.get "#{@root}/#{slug}"
78
+ slug = escape_docid(id)
79
+ hash = CouchRest.get("#{@root}/#{slug}")
80
+ doc = if /^_design/ =~ hash["_id"]
81
+ Design.new(hash)
82
+ else
83
+ Document.new(hash)
84
+ end
85
+ doc.database = self
86
+ doc
74
87
  end
75
88
 
76
89
  # GET an attachment directly from CouchDB
77
- def fetch_attachment doc, name
78
- doc = CGI.escape(doc)
90
+ def fetch_attachment docid, name
91
+ slug = escape_docid(docid)
79
92
  name = CGI.escape(name)
80
- RestClient.get "#{@root}/#{doc}/#{name}"
93
+ RestClient.get "#{@root}/#{slug}/#{name}"
81
94
  end
82
95
 
83
96
  # PUT an attachment directly to CouchDB
84
97
  def put_attachment doc, name, file, options = {}
85
- docid = CGI.escape(doc['_id'])
98
+ docid = escape_docid(doc['_id'])
86
99
  name = CGI.escape(name)
87
100
  uri = if doc['_rev']
88
101
  "#{@root}/#{docid}/#{name}?rev=#{doc['_rev']}"
@@ -99,12 +112,22 @@ module CouchRest
99
112
  # documents on the client side because POST has the curious property of
100
113
  # being automatically retried by proxies in the event of network
101
114
  # segmentation and lost responses.
102
- def save doc
115
+ #
116
+ # If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
117
+ # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
118
+ def save (doc, bulk = false)
103
119
  if doc['_attachments']
104
120
  doc['_attachments'] = encode_attachments(doc['_attachments'])
105
121
  end
106
- if doc['_id']
107
- slug = CGI.escape(doc['_id'])
122
+ if bulk
123
+ @bulk_save_cache << doc
124
+ return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
125
+ return {"ok" => true} # Compatibility with Document#save
126
+ elsif !bulk && @bulk_save_cache.length > 0
127
+ bulk_save
128
+ end
129
+ result = if doc['_id']
130
+ slug = escape_docid(doc['_id'])
108
131
  CouchRest.put "#{@root}/#{slug}", doc
109
132
  else
110
133
  begin
@@ -114,11 +137,23 @@ module CouchRest
114
137
  CouchRest.post @root, doc
115
138
  end
116
139
  end
140
+ if result['ok']
141
+ doc['_id'] = result['id']
142
+ doc['_rev'] = result['rev']
143
+ doc.database = self if doc.respond_to?(:database=)
144
+ end
145
+ result
117
146
  end
118
147
 
119
148
  # POST an array of documents to CouchDB. If any of the documents are
120
149
  # missing ids, supply one from the uuid cache.
121
- def bulk_save docs
150
+ #
151
+ # If called with no arguments, bulk saves the cache of documents to be bulk saved.
152
+ def bulk_save (docs = nil)
153
+ if docs.nil?
154
+ docs = @bulk_save_cache
155
+ @bulk_save_cache = []
156
+ end
122
157
  ids, noids = docs.partition{|d|d['_id']}
123
158
  uuid_count = [noids.length, @server.uuid_batch_count].max
124
159
  noids.each do |doc|
@@ -130,11 +165,53 @@ module CouchRest
130
165
 
131
166
  # DELETE the document from CouchDB that has the given <tt>_id</tt> and
132
167
  # <tt>_rev</tt>.
133
- def delete doc
134
- slug = CGI.escape(doc['_id'])
168
+ #
169
+ # If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
170
+ # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
171
+ def delete (doc, bulk = false)
172
+ raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
173
+ if bulk
174
+ @bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
175
+ return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
176
+ return { "ok" => true } # Mimic the non-deferred version
177
+ end
178
+ slug = escape_docid(doc['_id'])
135
179
  CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
136
180
  end
137
181
 
182
+ # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
183
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
184
+ # hash with a '_rev' key
185
+ def copy doc, dest
186
+ raise ArgumentError, "_id is required for copying" unless doc['_id']
187
+ slug = escape_docid(doc['_id'])
188
+ destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
189
+ "#{dest['_id']}?rev=#{dest['_rev']}"
190
+ else
191
+ dest
192
+ end
193
+ CouchRest.copy "#{@root}/#{slug}", destination
194
+ end
195
+
196
+ # MOVE an existing document to a new id. If the destination id currently exists, a rev must be provided.
197
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
198
+ # hash with a '_rev' key
199
+ def move doc, dest
200
+ raise ArgumentError, "_id and _rev are required for moving" unless doc['_id'] && doc['_rev']
201
+ slug = escape_docid(doc['_id'])
202
+ destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
203
+ "#{dest['_id']}?rev=#{dest['_rev']}"
204
+ else
205
+ dest
206
+ end
207
+ CouchRest.move "#{@root}/#{slug}?rev=#{doc['_rev']}", destination
208
+ end
209
+
210
+ # Compact the database, removing old document revisions and optimizing space use.
211
+ def compact!
212
+ CouchRest.post "#{@root}/_compact"
213
+ end
214
+
138
215
  # DELETE the database itself. This is not undoable and could be rather
139
216
  # catastrophic. Use with care!
140
217
  def delete!
@@ -143,6 +220,10 @@ module CouchRest
143
220
 
144
221
  private
145
222
 
223
+ def escape_docid id
224
+ /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
225
+ end
226
+
146
227
  def encode_attachments attachments
147
228
  attachments.each do |k,v|
148
229
  next if v['stub']
@@ -0,0 +1,89 @@
1
+ module CouchRest
2
+ class Design < Document
3
+ def view_by *keys
4
+ opts = keys.pop if keys.last.is_a?(Hash)
5
+ opts ||= {}
6
+ self['views'] ||= {}
7
+ method_name = "by_#{keys.join('_and_')}"
8
+
9
+ if opts[:map]
10
+ view = {}
11
+ view['map'] = opts.delete(:map)
12
+ if opts[:reduce]
13
+ view['reduce'] = opts.delete(:reduce)
14
+ opts[:reduce] = false
15
+ end
16
+ self['views'][method_name] = view
17
+ else
18
+ doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
19
+ key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
20
+ guards = opts.delete(:guards) || []
21
+ guards.concat doc_keys
22
+ map_function = <<-JAVASCRIPT
23
+ function(doc) {
24
+ if (#{guards.join(' && ')}) {
25
+ emit(#{key_emit}, null);
26
+ }
27
+ }
28
+ JAVASCRIPT
29
+ self['views'][method_name] = {
30
+ 'map' => map_function
31
+ }
32
+ end
33
+ self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
34
+ method_name
35
+ end
36
+
37
+ # Dispatches to any named view.
38
+ def view view_name, query={}, &block
39
+ view_name = view_name.to_s
40
+ view_slug = "#{name}/#{view_name}"
41
+ defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
42
+ fetch_view(view_slug, defaults.merge(query), &block)
43
+ end
44
+
45
+ def name
46
+ id.sub('_design/','') if id
47
+ end
48
+
49
+ def name= newname
50
+ self['_id'] = "_design/#{newname}"
51
+ end
52
+
53
+ def save
54
+ raise ArgumentError, "_design docs require a name" unless name && name.length > 0
55
+ super
56
+ end
57
+
58
+ private
59
+
60
+ # returns stored defaults if the there is a view named this in the design doc
61
+ def has_view?(view)
62
+ view = view.to_s
63
+ self['views'][view] &&
64
+ (self['views'][view]["couchrest-defaults"]||{})
65
+ end
66
+
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
+ def fetch_view view_name, opts, &block
84
+ database.view(view_name, opts, &block)
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,63 @@
1
+ module CouchRest
2
+ class Response < Hash
3
+ def initialize keys = {}
4
+ keys.each do |k,v|
5
+ self[k.to_s] = v
6
+ end
7
+ end
8
+ def []= key, value
9
+ super(key.to_s, value)
10
+ end
11
+ def [] key
12
+ super(key.to_s)
13
+ end
14
+ end
15
+
16
+ class Document < Response
17
+
18
+ attr_accessor :database
19
+
20
+ # alias for self['_id']
21
+ def id
22
+ self['_id']
23
+ end
24
+
25
+ # alias for self['_rev']
26
+ def rev
27
+ self['_rev']
28
+ end
29
+
30
+ # returns true if the document has never been saved
31
+ def new_document?
32
+ !rev
33
+ end
34
+
35
+ # Saves the document to the db using create or update. Also runs the :save
36
+ # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
37
+ # CouchDB's response.
38
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
39
+ def save(bulk = false)
40
+ raise ArgumentError, "doc.database required for saving" unless database
41
+ result = database.save self, bulk
42
+ result['ok']
43
+ end
44
+
45
+ # Deletes the document from the database. Runs the :delete callbacks.
46
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
47
+ # document to be saved to a new <tt>_id</tt>.
48
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
49
+ # actually be deleted from the db until bulk save.
50
+ def destroy(bulk = false)
51
+ raise ArgumentError, "doc.database required to destroy" unless database
52
+ result = database.delete(self, bulk)
53
+ if result['ok']
54
+ self['_rev'] = nil
55
+ self['_id'] = nil
56
+ end
57
+ result['ok']
58
+ end
59
+
60
+ end
61
+
62
+
63
+ end