jrun-couchrest 0.12.6 → 0.17.1

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.
Files changed (60) hide show
  1. data/README.md +31 -6
  2. data/Rakefile +4 -1
  3. data/examples/model/example.rb +19 -13
  4. data/lib/couchrest.rb +55 -12
  5. data/lib/couchrest/core/database.rb +15 -31
  6. data/lib/couchrest/core/document.rb +40 -45
  7. data/lib/couchrest/core/response.rb +16 -0
  8. data/lib/couchrest/core/server.rb +1 -1
  9. data/lib/couchrest/helper/upgrade.rb +51 -0
  10. data/lib/couchrest/mixins.rb +4 -0
  11. data/lib/couchrest/mixins/attachments.rb +31 -0
  12. data/lib/couchrest/mixins/callbacks.rb +483 -0
  13. data/lib/couchrest/mixins/design_doc.rb +64 -0
  14. data/lib/couchrest/mixins/document_queries.rb +48 -0
  15. data/lib/couchrest/mixins/extended_attachments.rb +68 -0
  16. data/lib/couchrest/mixins/extended_document_mixins.rb +6 -0
  17. data/lib/couchrest/mixins/properties.rb +125 -0
  18. data/lib/couchrest/mixins/validation.rb +234 -0
  19. data/lib/couchrest/mixins/views.rb +168 -0
  20. data/lib/couchrest/monkeypatches.rb +68 -48
  21. data/lib/couchrest/more/casted_model.rb +28 -0
  22. data/lib/couchrest/more/extended_document.rb +217 -0
  23. data/lib/couchrest/more/property.rb +40 -0
  24. data/lib/couchrest/support/blank.rb +42 -0
  25. data/lib/couchrest/support/class.rb +191 -0
  26. data/lib/couchrest/validation/auto_validate.rb +163 -0
  27. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  28. data/lib/couchrest/validation/validation_errors.rb +118 -0
  29. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  30. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  31. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  32. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  33. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  34. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  35. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  36. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  37. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  38. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  39. data/spec/couchrest/core/database_spec.rb +35 -89
  40. data/spec/couchrest/core/document_spec.rb +1 -45
  41. data/spec/couchrest/core/server_spec.rb +35 -0
  42. data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
  43. data/spec/couchrest/more/casted_model_spec.rb +98 -0
  44. data/spec/couchrest/more/extended_doc_attachment_spec.rb +130 -0
  45. data/spec/couchrest/more/extended_doc_spec.rb +509 -0
  46. data/spec/couchrest/more/extended_doc_view_spec.rb +207 -0
  47. data/spec/couchrest/more/property_spec.rb +130 -0
  48. data/spec/couchrest/support/class_spec.rb +59 -0
  49. data/spec/fixtures/more/article.rb +34 -0
  50. data/spec/fixtures/more/card.rb +20 -0
  51. data/spec/fixtures/more/course.rb +14 -0
  52. data/spec/fixtures/more/event.rb +6 -0
  53. data/spec/fixtures/more/invoice.rb +17 -0
  54. data/spec/fixtures/more/person.rb +8 -0
  55. data/spec/fixtures/more/question.rb +6 -0
  56. data/spec/fixtures/more/service.rb +12 -0
  57. data/spec/spec_helper.rb +6 -1
  58. metadata +57 -3
  59. data/lib/couchrest/core/model.rb +0 -615
  60. data/spec/couchrest/core/model_spec.rb +0 -855
data/README.md CHANGED
@@ -59,10 +59,35 @@ Creating and Querying Views:
59
59
 
60
60
  ## CouchRest::Model
61
61
 
62
- CouchRest::Model is a module designed along the lines of DataMapper::Resource.
63
- By subclassing, suddenly you get all sorts of powerful sugar, so that working
64
- with CouchDB in your Rails or Merb app is no harder than working with the
65
- standard SQL alternatives. See the CouchRest::Model documentation for an
66
- example article class that illustrates usage.
62
+ CouchRest::Model has been deprecated and replaced by CouchRest::ExtendedDocument
63
+
64
+
65
+ ## CouchRest::ExtendedDocument
66
+
67
+ ### Callbacks
68
+
69
+ `CouchRest::ExtendedDocuments` instances have 2 callbacks already defined for you:
70
+ `create_callback`, `save_callback`, `update_callback` and `destroy_callback`
71
+
72
+ In your document inherits from `CouchRest::ExtendedDocument`, define your callback as follows:
73
+
74
+ save_callback :before, :generate_slug_from_name
75
+
76
+ CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted from Rails 3, here are some simple usage examples:
77
+
78
+ save_callback :before, :before_method
79
+ save_callback :after, :after_method, :if => :condition
80
+ save_callback :around {|r| stuff; yield; stuff }
81
+
82
+ Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
83
+
84
+ ### Casting
85
+
86
+ Often, you will want to store multiple objects within a document, to be able to retrieve your objects when you load the document,
87
+ you can define some casting rules.
88
+
89
+ property :casted_attribute, :cast_as => 'WithCastedModelMixin'
90
+ property :keywords, :cast_as => ["String"]
91
+
92
+ If you want to cast an array of instances from a specific Class, use the trick shown above ["ClassName"]
67
93
 
68
- CouchRest::Model will be removed from this package.
data/Rakefile CHANGED
@@ -23,7 +23,7 @@ spec = Gem::Specification.new do |s|
23
23
  s.homepage = "http://github.com/jchris/couchrest"
24
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."
25
25
  s.has_rdoc = true
26
- s.authors = ["J. Chris Anderson"]
26
+ s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
27
27
  s.files = %w( LICENSE README.md Rakefile THANKS.md ) +
28
28
  Dir["{examples,lib,spec,utils}/**/*"] -
29
29
  Dir["spec/tmp"]
@@ -64,3 +64,6 @@ end
64
64
 
65
65
  desc "Run the rspec"
66
66
  task :default => :spec
67
+
68
+
69
+ ::Rake::GemPackageTask.new(spec) { |p| p.gem_spec = spec }
@@ -1,31 +1,38 @@
1
- require 'rubygems'
2
- require 'couchrest'
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'couchrest')
3
2
 
4
3
  def show obj
5
4
  puts obj.inspect
6
5
  puts
7
6
  end
8
7
 
9
- CouchRest::Model.default_database = CouchRest.database!('couchrest-model-example')
8
+ SERVER = CouchRest.new
9
+ SERVER.default_database = 'couchrest-extendeddoc-example'
10
10
 
11
- class Author < CouchRest::Model
12
- key_accessor :name
11
+ class Author < CouchRest::ExtendedDocument
12
+ use_database SERVER.default_database
13
+ property :name
14
+
13
15
  def drink_scotch
14
16
  puts "... glug type glug ... I'm #{name} ... type glug glug ..."
15
17
  end
16
18
  end
17
19
 
18
- class Post < CouchRest::Model
19
- key_accessor :title, :body, :author
20
-
21
- cast :author, :as => 'Author'
20
+ class Post < CouchRest::ExtendedDocument
21
+ use_database SERVER.default_database
22
+
23
+ property :title
24
+ property :body
25
+ property :author, :cast_as => 'Author'
22
26
 
23
27
  timestamps!
24
28
  end
25
29
 
26
- class Comment < CouchRest::Model
27
- cast :commenter, :as => 'Author'
28
-
30
+ class Comment < CouchRest::ExtendedDocument
31
+ use_database SERVER.default_database
32
+
33
+ property :commenter, :cast_as => 'Author'
34
+ timestamps!
35
+
29
36
  def post= post
30
37
  self["post_id"] = post.id
31
38
  end
@@ -33,7 +40,6 @@ class Comment < CouchRest::Model
33
40
  Post.get(self['post_id']) if self['post_id']
34
41
  end
35
42
 
36
- timestamps!
37
43
  end
38
44
 
39
45
  puts "Act I: CRUD"
data/lib/couchrest.rb CHANGED
@@ -13,31 +13,37 @@
13
13
  # limitations under the License.
14
14
 
15
15
  require "rubygems"
16
+ gem 'json'
16
17
  require 'json'
18
+ gem 'rest-client'
17
19
  require 'rest_client'
18
20
 
19
21
  $:.unshift File.dirname(__FILE__) unless
20
22
  $:.include?(File.dirname(__FILE__)) ||
21
23
  $:.include?(File.expand_path(File.dirname(__FILE__)))
22
24
 
25
+ $COUCHREST_DEBUG ||= false
23
26
 
24
27
  require 'couchrest/monkeypatches'
25
28
 
26
29
  # = CouchDB, close to the metal
27
30
  module CouchRest
28
- VERSION = '0.12.6'
31
+ VERSION = '0.17.1' unless self.const_defined?("VERSION")
29
32
 
30
33
  autoload :Server, 'couchrest/core/server'
31
34
  autoload :Database, 'couchrest/core/database'
35
+ autoload :Response, 'couchrest/core/response'
32
36
  autoload :Document, 'couchrest/core/document'
33
- autoload :Design, 'couchrest/core/design'
37
+ autoload :Design, 'couchrest/core/design'
34
38
  autoload :View, 'couchrest/core/view'
35
39
  autoload :Model, 'couchrest/core/model'
36
40
  autoload :Pager, 'couchrest/helper/pager'
37
41
  autoload :FileManager, 'couchrest/helper/file_manager'
38
42
  autoload :Streamer, 'couchrest/helper/streamer'
43
+ autoload :Upgrade, 'couchrest/helper/upgrade'
39
44
 
40
45
  autoload :ExtendedDocument, 'couchrest/more/extended_document'
46
+ autoload :CastedModel, 'couchrest/more/casted_model'
41
47
 
42
48
  require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
43
49
 
@@ -46,6 +52,23 @@ module CouchRest
46
52
  # some helpers for tasks like instantiating a new Database or Server instance.
47
53
  class << self
48
54
 
55
+ # extracted from Extlib
56
+ #
57
+ # Constantize tries to find a declared constant with the name specified
58
+ # in the string. It raises a NameError when the name is not in CamelCase
59
+ # or is not initialized.
60
+ #
61
+ # @example
62
+ # "Module".constantize #=> Module
63
+ # "Class".constantize #=> Class
64
+ def constantize(camel_cased_word)
65
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
66
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
67
+ end
68
+
69
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
70
+ end
71
+
49
72
  # todo, make this parse the url and instantiate a Server or Database instance
50
73
  # depending on the specificity.
51
74
  def new(*opts)
@@ -103,18 +126,42 @@ module CouchRest
103
126
  cr.database(parsed[:database])
104
127
  end
105
128
 
106
- def put uri, doc = nil
129
+ def put(uri, doc = nil)
107
130
  payload = doc.to_json if doc
108
- JSON.parse(RestClient.put(uri, payload))
131
+ begin
132
+ JSON.parse(RestClient.put(uri, payload))
133
+ rescue Exception => e
134
+ if $COUCHREST_DEBUG == true
135
+ raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
136
+ else
137
+ raise e
138
+ end
139
+ end
109
140
  end
110
141
 
111
- def get uri
112
- JSON.parse(RestClient.get(uri), :max_nesting => false)
142
+ def get(uri)
143
+ begin
144
+ JSON.parse(RestClient.get(uri), :max_nesting => false)
145
+ rescue => e
146
+ if $COUCHREST_DEBUG == true
147
+ raise "Error while sending a GET request #{uri}\n: #{e}"
148
+ else
149
+ raise e
150
+ end
151
+ end
113
152
  end
114
153
 
115
154
  def post uri, doc = nil
116
155
  payload = doc.to_json if doc
117
- JSON.parse(RestClient.post(uri, payload))
156
+ begin
157
+ JSON.parse(RestClient.post(uri, payload))
158
+ rescue Exception => e
159
+ if $COUCHREST_DEBUG == true
160
+ raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
161
+ else
162
+ raise e
163
+ end
164
+ end
118
165
  end
119
166
 
120
167
  def delete uri
@@ -124,10 +171,6 @@ module CouchRest
124
171
  def copy uri, destination
125
172
  JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
126
173
  end
127
-
128
- def move uri, destination
129
- JSON.parse(RestClient.move(uri, {'Destination' => destination}))
130
- end
131
174
 
132
175
  def paramify_url url, params = {}
133
176
  if params && !params.empty?
@@ -140,4 +183,4 @@ module CouchRest
140
183
  url
141
184
  end
142
185
  end # class << self
143
- end
186
+ end
@@ -20,7 +20,7 @@ module CouchRest
20
20
  @uri = @root = "#{host}/#{CGI.escape(name)}"
21
21
  @streamer = Streamer.new(self)
22
22
  @bulk_save_cache = []
23
- @bulk_save_cache_limit = 50
23
+ @bulk_save_cache_limit = 500 # must be smaller than the uuid count
24
24
  end
25
25
 
26
26
  # returns the database's uri
@@ -61,12 +61,15 @@ module CouchRest
61
61
  # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
62
62
  def view(name, params = {}, &block)
63
63
  keys = params.delete(:keys)
64
- url = CouchRest.paramify_url "#{@uri}/_view/#{name}", params
64
+ name = name.split('/') # I think this will always be length == 2, but maybe not...
65
+ dname = name.shift
66
+ vname = name.join('/')
67
+ url = CouchRest.paramify_url "#{@uri}/_design/#{dname}/_view/#{vname}", params
65
68
  if keys
66
69
  CouchRest.post(url, {:keys => keys})
67
70
  else
68
71
  if block_given?
69
- @streamer.view(name, params, &block)
72
+ @streamer.view("_design/#{dname}/_view/#{vname}", params, &block)
70
73
  else
71
74
  CouchRest.get url
72
75
  end
@@ -74,13 +77,15 @@ module CouchRest
74
77
  end
75
78
 
76
79
  # GET a document from CouchDB, by id. Returns a Ruby Hash.
77
- def get(id)
80
+ def get(id, params = {})
78
81
  slug = escape_docid(id)
79
- hash = CouchRest.get("#{@uri}/#{slug}")
80
- doc = if /^_design/ =~ hash["_id"]
81
- Design.new(hash)
82
+ url = CouchRest.paramify_url("#{@uri}/#{slug}", params)
83
+ result = CouchRest.get(url)
84
+ return result unless result.is_a?(Hash)
85
+ doc = if /^_design/ =~ result["_id"]
86
+ Design.new(result)
82
87
  else
83
- Document.new(hash)
88
+ Document.new(result)
84
89
  end
85
90
  doc.database = self
86
91
  doc
@@ -90,9 +95,7 @@ module CouchRest
90
95
  def fetch_attachment(doc, name)
91
96
  # slug = escape_docid(docid)
92
97
  # name = CGI.escape(name)
93
-
94
98
  uri = uri_for_attachment(doc, name)
95
-
96
99
  RestClient.get uri
97
100
  # "#{@uri}/#{slug}/#{name}"
98
101
  end
@@ -177,6 +180,7 @@ module CouchRest
177
180
  end
178
181
  CouchRest.post "#{@uri}/_bulk_docs", {:docs => docs}
179
182
  end
183
+ alias :bulk_delete :bulk_save
180
184
 
181
185
  # DELETE the document from CouchDB that has the given <tt>_id</tt> and
182
186
  # <tt>_rev</tt>.
@@ -220,26 +224,6 @@ module CouchRest
220
224
  copy_doc(doc, dest)
221
225
  end
222
226
 
223
- # MOVE an existing document to a new id. If the destination id currently exists, a rev must be provided.
224
- # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
225
- # hash with a '_rev' key
226
- def move_doc(doc, dest)
227
- raise ArgumentError, "_id and _rev are required for moving" unless doc['_id'] && doc['_rev']
228
- slug = escape_docid(doc['_id'])
229
- destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
230
- "#{dest['_id']}?rev=#{dest['_rev']}"
231
- else
232
- dest
233
- end
234
- CouchRest.move "#{@uri}/#{slug}?rev=#{doc['_rev']}", destination
235
- end
236
-
237
- ### DEPRECATION NOTICE
238
- def move(doc, dest)
239
- puts "CouchRest::Database's move method is being deprecated, please use move_doc instead"
240
- move_doc(doc, dest)
241
- end
242
-
243
227
  # Compact the database, removing old document revisions and optimizing space use.
244
228
  def compact!
245
229
  CouchRest.post "#{@uri}/_compact"
@@ -280,7 +264,7 @@ module CouchRest
280
264
 
281
265
  private
282
266
 
283
- def uri_for_attachment doc, name
267
+ def uri_for_attachment(doc, name)
284
268
  if doc.is_a?(String)
285
269
  puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
286
270
  docid = doc
@@ -1,33 +1,21 @@
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
-
1
+ require 'delegate'
2
+
3
+ module CouchRest
16
4
  class Document < Response
17
- include CouchRest::Mixins::Views
5
+ include CouchRest::Mixins::Attachments
18
6
 
7
+ # def self.inherited(subklass)
8
+ # subklass.send(:class_inheritable_accessor, :database)
9
+ # end
10
+
11
+ class_inheritable_accessor :database
19
12
  attr_accessor :database
20
- @@database = nil
21
13
 
22
14
  # override the CouchRest::Model-wide default_database
23
15
  # This is not a thread safe operation, do not change the model
24
16
  # database at runtime.
25
17
  def self.use_database(db)
26
- @@database = db
27
- end
28
-
29
- def self.database
30
- @@database
18
+ self.database = db
31
19
  end
32
20
 
33
21
  def id
@@ -38,6 +26,36 @@ module CouchRest
38
26
  self['_rev']
39
27
  end
40
28
 
29
+ # returns true if the document has never been saved
30
+ def new_document?
31
+ !rev
32
+ end
33
+
34
+ # Saves the document to the db using create or update. Also runs the :save
35
+ # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
36
+ # CouchDB's response.
37
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
38
+ def save(bulk = false)
39
+ raise ArgumentError, "doc.database required for saving" unless database
40
+ result = database.save_doc self, bulk
41
+ result['ok']
42
+ end
43
+
44
+ # Deletes the document from the database. Runs the :delete callbacks.
45
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
46
+ # document to be saved to a new <tt>_id</tt>.
47
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
48
+ # actually be deleted from the db until bulk save.
49
+ def destroy(bulk = false)
50
+ raise ArgumentError, "doc.database required to destroy" unless database
51
+ result = database.delete_doc(self, bulk)
52
+ if result['ok']
53
+ self['_rev'] = nil
54
+ self['_id'] = nil
55
+ end
56
+ result['ok']
57
+ end
58
+
41
59
  # copies the document to a new id. If the destination id currently exists, a rev must be provided.
42
60
  # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
43
61
  # hash with a '_rev' key
@@ -73,29 +91,6 @@ module CouchRest
73
91
  @database || self.class.database
74
92
  end
75
93
 
76
- # saves an attachment directly to couchdb
77
- def put_attachment(name, file, options={})
78
- raise ArgumentError, "doc must be saved" unless self.rev
79
- raise ArgumentError, "doc.database required to put_attachment" unless database
80
- result = database.put_attachment(self, name, file, options)
81
- self['_rev'] = result['rev']
82
- result['ok']
83
- end
84
-
85
- # returns an attachment's data
86
- def fetch_attachment(name)
87
- raise ArgumentError, "doc must be saved" unless self.rev
88
- raise ArgumentError, "doc.database required to put_attachment" unless database
89
- database.fetch_attachment(self, name)
90
- end
91
-
92
- # deletes an attachment directly from couchdb
93
- def delete_attachment(name)
94
- raise ArgumentError, "doc.database required to delete_attachment" unless database
95
- result = database.delete_attachment(self, name)
96
- self['_rev'] = result['rev']
97
- result['ok']
98
- end
99
94
  end
100
95
 
101
96
  end