juozasg-couchrest 0.10.1 → 0.10.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. metadata +6 -101
  2. data/Rakefile +0 -86
  3. data/bin/couchapp +0 -58
  4. data/bin/couchdir +0 -20
  5. data/bin/couchview +0 -48
  6. data/examples/model/example.rb +0 -138
  7. data/examples/word_count/markov +0 -38
  8. data/examples/word_count/views/books/chunked-map.js +0 -3
  9. data/examples/word_count/views/books/united-map.js +0 -1
  10. data/examples/word_count/views/markov/chain-map.js +0 -6
  11. data/examples/word_count/views/markov/chain-reduce.js +0 -7
  12. data/examples/word_count/views/word_count/count-map.js +0 -6
  13. data/examples/word_count/views/word_count/count-reduce.js +0 -3
  14. data/examples/word_count/word_count.rb +0 -67
  15. data/examples/word_count/word_count_query.rb +0 -39
  16. data/lib/couchrest/commands/generate.rb +0 -71
  17. data/lib/couchrest/commands/push.rb +0 -103
  18. data/lib/couchrest/core/database.rb +0 -173
  19. data/lib/couchrest/core/design.rb +0 -89
  20. data/lib/couchrest/core/document.rb +0 -60
  21. data/lib/couchrest/core/model.rb +0 -557
  22. data/lib/couchrest/core/server.rb +0 -51
  23. data/lib/couchrest/core/view.rb +0 -4
  24. data/lib/couchrest/helper/file_manager.rb +0 -317
  25. data/lib/couchrest/helper/pager.rb +0 -103
  26. data/lib/couchrest/helper/streamer.rb +0 -44
  27. data/lib/couchrest/helper/templates/bar.txt +0 -11
  28. data/lib/couchrest/helper/templates/example-map.js +0 -8
  29. data/lib/couchrest/helper/templates/example-reduce.js +0 -10
  30. data/lib/couchrest/helper/templates/index.html +0 -26
  31. data/lib/couchrest/monkeypatches.rb +0 -24
  32. data/lib/couchrest.rb +0 -125
  33. data/spec/couchapp_spec.rb +0 -87
  34. data/spec/couchrest/core/couchrest_spec.rb +0 -191
  35. data/spec/couchrest/core/database_spec.rb +0 -478
  36. data/spec/couchrest/core/design_spec.rb +0 -131
  37. data/spec/couchrest/core/document_spec.rb +0 -96
  38. data/spec/couchrest/core/model_spec.rb +0 -660
  39. data/spec/couchrest/helpers/file_manager_spec.rb +0 -203
  40. data/spec/couchrest/helpers/pager_spec.rb +0 -122
  41. data/spec/couchrest/helpers/streamer_spec.rb +0 -23
  42. data/spec/fixtures/attachments/couchdb.png +0 -0
  43. data/spec/fixtures/attachments/test.html +0 -11
  44. data/spec/fixtures/views/lib.js +0 -3
  45. data/spec/fixtures/views/test_view/lib.js +0 -3
  46. data/spec/fixtures/views/test_view/only-map.js +0 -4
  47. data/spec/fixtures/views/test_view/test-map.js +0 -3
  48. data/spec/fixtures/views/test_view/test-reduce.js +0 -3
  49. data/spec/spec.opts +0 -6
  50. data/spec/spec_helper.rb +0 -14
  51. data/utils/remap.rb +0 -27
@@ -1,557 +0,0 @@
1
- require 'rubygems'
2
- require 'extlib'
3
- require 'digest/md5'
4
- require File.dirname(__FILE__) + '/document'
5
- # = CouchRest::Model - ORM, the CouchDB way
6
- module CouchRest
7
- # = CouchRest::Model - ORM, the CouchDB way
8
- #
9
- # CouchRest::Model provides an ORM-like interface for CouchDB documents. It
10
- # avoids all usage of <tt>method_missing</tt>, and tries to strike a balance
11
- # between usability and magic. See CouchRest::Model#view_by for
12
- # documentation about the view-generation system.
13
- #
14
- # ==== Example
15
- #
16
- # This is an example class using CouchRest::Model. It is taken from the
17
- # spec/couchrest/core/model_spec.rb file, which may be even more up to date
18
- # than this example.
19
- #
20
- # class Article < CouchRest::Model
21
- # use_database CouchRest.database!('http://localhost:5984/couchrest-model-test')
22
- # unique_id :slug
23
- #
24
- # view_by :date, :descending => true
25
- # view_by :user_id, :date
26
- #
27
- # view_by :tags,
28
- # :map =>
29
- # "function(doc) {
30
- # if (doc['couchrest-type'] == 'Article' && doc.tags) {
31
- # doc.tags.forEach(function(tag){
32
- # emit(tag, 1);
33
- # });
34
- # }
35
- # }",
36
- # :reduce =>
37
- # "function(keys, values, rereduce) {
38
- # return sum(values);
39
- # }"
40
- #
41
- # key_writer :date
42
- # key_reader :slug, :created_at, :updated_at
43
- # key_accessor :title, :tags
44
- #
45
- # timestamps!
46
- #
47
- # before(:create, :generate_slug_from_title)
48
- # def generate_slug_from_title
49
- # self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
50
- # end
51
- # end
52
- #
53
- # ==== Examples of finding articles with these views:
54
- #
55
- # * All the articles by Barney published in the last 24 hours. Note that we
56
- # use <tt>{}</tt> as a special value that sorts after all strings,
57
- # numbers, and arrays.
58
- #
59
- # Article.by_user_id_and_date :startkey => ["barney", Time.now - 24 * 3600], :endkey => ["barney", {}]
60
- #
61
- # * The most recent 20 articles. Remember that the <tt>view_by :date</tt>
62
- # has the default option <tt>:descending => true</tt>.
63
- #
64
- # Article.by_date :count => 20
65
- #
66
- # * The raw CouchDB view reduce result for the custom <tt>:tags</tt> view.
67
- # In this case we'll get a count of the number of articles tagged "ruby".
68
- #
69
- # Article.by_tags :key => "ruby", :reduce => true
70
- #
71
- class Model < Document
72
-
73
- # instantiates the hash by converting all the keys to strings.
74
- def initialize keys = {}
75
- super(keys)
76
- apply_defaults
77
- cast_keys
78
- unless self['_id'] && self['_rev']
79
- self['couchrest-type'] = self.class.to_s
80
- end
81
- end
82
-
83
- # this is the CouchRest::Database that model classes will use unless
84
- # they override it with <tt>use_database</tt>
85
- cattr_accessor :default_database
86
-
87
- class_inheritable_accessor :casts
88
- class_inheritable_accessor :default_obj
89
- class_inheritable_accessor :class_database
90
- class_inheritable_accessor :design_doc
91
- class_inheritable_accessor :design_doc_slug_cache
92
- class_inheritable_accessor :design_doc_fresh
93
-
94
- class << self
95
- # override the CouchRest::Model-wide default_database
96
- def use_database db
97
- self.class_database = db
98
- end
99
-
100
- # returns the CouchRest::Database instance that this class uses
101
- def database
102
- self.class_database || CouchRest::Model.default_database
103
- end
104
-
105
- # Load a document from the database by id
106
- def get id
107
- doc = database.get id
108
- new(doc)
109
- end
110
-
111
- # Load all documents that have the "couchrest-type" field equal to the
112
- # name of the current class. Take the standard set of
113
- # CouchRest::Database#view options.
114
- def all opts = {}, &block
115
- self.design_doc ||= Design.new(default_design_doc)
116
- unless design_doc_fresh
117
- refresh_design_doc
118
- end
119
- view :all, opts, &block
120
- end
121
-
122
- # Load the first document that have the "couchrest-type" field equal to
123
- # the name of the current class.
124
- #
125
- # ==== Returns
126
- # Object:: The first object instance available
127
- # or
128
- # Nil:: if no instances available
129
- #
130
- # ==== Parameters
131
- # opts<Hash>::
132
- # View options, see <tt>CouchRest::Database#view</tt> options for more info.
133
- def first opts = {}
134
- first_instance = self.all(opts.merge!(:count => 1))
135
- first_instance.empty? ? nil : first_instance.first
136
- end
137
-
138
- # Cast a field as another class. The class must be happy to have the
139
- # field's primitive type as the argument to it's constuctur. Classes
140
- # which inherit from CouchRest::Model are happy to act as sub-objects
141
- # for any fields that are stored in JSON as object (and therefore are
142
- # parsed from the JSON as Ruby Hashes).
143
- #
144
- # Example:
145
- #
146
- # class Post < CouchRest::Model
147
- #
148
- # key_accessor :title, :body, :author
149
- #
150
- # cast :author, :as => 'Author'
151
- #
152
- # end
153
- #
154
- # post.author.class #=> Author
155
- #
156
- # Using the same example, if a Post should have many Comments, we
157
- # would declare it like this:
158
- #
159
- # class Post < CouchRest::Model
160
- #
161
- # key_accessor :title, :body, :author, comments
162
- #
163
- # cast :author, :as => 'Author'
164
- # cast :comments, :as => ['Comment']
165
- #
166
- # end
167
- #
168
- # post.author.class #=> Author
169
- # post.comments.class #=> Array
170
- # post.comments.first #=> Comment
171
- #
172
- def cast field, opts = {}
173
- self.casts ||= {}
174
- self.casts[field.to_s] = opts
175
- end
176
-
177
- # Defines methods for reading and writing from fields in the document.
178
- # Uses key_writer and key_reader internally.
179
- def key_accessor *keys
180
- key_writer *keys
181
- key_reader *keys
182
- end
183
-
184
- # For each argument key, define a method <tt>key=</tt> that sets the
185
- # corresponding field on the CouchDB document.
186
- def key_writer *keys
187
- keys.each do |method|
188
- key = method.to_s
189
- define_method "#{method}=" do |value|
190
- self[key] = value
191
- end
192
- end
193
- end
194
-
195
- # For each argument key, define a method <tt>key</tt> that reads the
196
- # corresponding field on the CouchDB document.
197
- def key_reader *keys
198
- keys.each do |method|
199
- key = method.to_s
200
- define_method method do
201
- self[key]
202
- end
203
- end
204
- end
205
-
206
- def default
207
- self.default_obj
208
- end
209
-
210
- def set_default hash
211
- self.default_obj = hash
212
- end
213
-
214
- # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
215
- # on the document whenever saving occurs. CouchRest uses a pretty
216
- # decent time format by default. See Time#to_json
217
- def timestamps!
218
- before(:create) do
219
- self['updated_at'] = self['created_at'] = Time.now
220
- end
221
- before(:update) do
222
- self['updated_at'] = Time.now
223
- end
224
- end
225
-
226
- # Name a method that will be called before the document is first saved,
227
- # which returns a string to be used for the document's <tt>_id</tt>.
228
- # Because CouchDB enforces a constraint that each id must be unique,
229
- # this can be used to enforce eg: uniq usernames. Note that this id
230
- # must be globally unique across all document types which share a
231
- # database, so if you'd like to scope uniqueness to this class, you
232
- # should use the class name as part of the unique id.
233
- def unique_id method = nil, &block
234
- if method
235
- define_method :set_unique_id do
236
- self['_id'] ||= self.send(method)
237
- end
238
- elsif block
239
- define_method :set_unique_id do
240
- uniqid = block.call(self)
241
- raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
242
- self['_id'] ||= uniqid
243
- end
244
- end
245
- end
246
-
247
- # Define a CouchDB view. The name of the view will be the concatenation
248
- # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
249
- #
250
- # ==== Example views:
251
- #
252
- # class Post
253
- # # view with default options
254
- # # query with Post.by_date
255
- # view_by :date, :descending => true
256
- #
257
- # # view with compound sort-keys
258
- # # query with Post.by_user_id_and_date
259
- # view_by :user_id, :date
260
- #
261
- # # view with custom map/reduce functions
262
- # # query with Post.by_tags :reduce => true
263
- # view_by :tags,
264
- # :map =>
265
- # "function(doc) {
266
- # if (doc['couchrest-type'] == 'Post' && doc.tags) {
267
- # doc.tags.forEach(function(tag){
268
- # emit(doc.tag, 1);
269
- # });
270
- # }
271
- # }",
272
- # :reduce =>
273
- # "function(keys, values, rereduce) {
274
- # return sum(values);
275
- # }"
276
- # end
277
- #
278
- # <tt>view_by :date</tt> will create a view defined by this Javascript
279
- # function:
280
- #
281
- # function(doc) {
282
- # if (doc['couchrest-type'] == 'Post' && doc.date) {
283
- # emit(doc.date, null);
284
- # }
285
- # }
286
- #
287
- # It can be queried by calling <tt>Post.by_date</tt> which accepts all
288
- # valid options for CouchRest::Database#view. In addition, calling with
289
- # the <tt>:raw => true</tt> option will return the view rows
290
- # themselves. By default <tt>Post.by_date</tt> will return the
291
- # documents included in the generated view.
292
- #
293
- # CouchRest::Database#view options can be applied at view definition
294
- # time as defaults, and they will be curried and used at view query
295
- # time. Or they can be overridden at query time.
296
- #
297
- # Custom views can be queried with <tt>:reduce => true</tt> to return
298
- # reduce results. The default for custom views is to query with
299
- # <tt>:reduce => false</tt>.
300
- #
301
- # Views are generated (on a per-model basis) lazily on first-access.
302
- # This means that if you are deploying changes to a view, the views for
303
- # that model won't be available until generation is complete. This can
304
- # take some time with large databases. Strategies are in the works.
305
- #
306
- # To understand the capabilities of this view system more compeletly,
307
- # it is recommended that you read the RSpec file at
308
- # <tt>spec/core/model_spec.rb</tt>.
309
-
310
- def view_by *keys
311
- self.design_doc ||= Design.new(default_design_doc)
312
- opts = keys.pop if keys.last.is_a?(Hash)
313
- opts ||= {}
314
- ducktype = opts.delete(:ducktype)
315
- unless ducktype || opts[:map]
316
- opts[:guards] ||= []
317
- opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
318
- end
319
- keys.push opts
320
- self.design_doc.view_by(*keys)
321
- self.design_doc_fresh = false
322
- end
323
-
324
- def method_missing m, *args
325
- if has_view?(m)
326
- query = args.shift || {}
327
- view(m, query, *args)
328
- else
329
- super
330
- end
331
- end
332
-
333
- # returns stored defaults if the there is a view named this in the design doc
334
- def has_view?(view)
335
- view = view.to_s
336
- design_doc && design_doc['views'] && design_doc['views'][view]
337
- end
338
-
339
- # Dispatches to any named view.
340
- def view name, query={}, &block
341
- unless design_doc_fresh
342
- refresh_design_doc
343
- end
344
- query[:raw] = true if query[:reduce]
345
- raw = query.delete(:raw)
346
- fetch_view_with_docs(name, query, raw, &block)
347
- end
348
-
349
- def all_design_doc_versions
350
- database.documents :startkey => "_design/#{self.to_s}-",
351
- :endkey => "_design/#{self.to_s}-\u9999"
352
- end
353
-
354
- # Deletes any non-current design docs that were created by this class.
355
- # Running this when you're deployed version of your application is steadily
356
- # and consistently using the latest code, is the way to clear out old design
357
- # docs. Running it to early could mean that live code has to regenerate
358
- # potentially large indexes.
359
- def cleanup_design_docs!
360
- ddocs = all_design_doc_versions
361
- ddocs["rows"].each do |row|
362
- if (row['id'] != design_doc_id)
363
- database.delete({
364
- "_id" => row['id'],
365
- "_rev" => row['value']['rev']
366
- })
367
- end
368
- end
369
- end
370
-
371
- private
372
-
373
- def fetch_view_with_docs name, opts, raw=false, &block
374
- if raw
375
- fetch_view name, opts, &block
376
- else
377
- begin
378
- view = fetch_view name, opts.merge({:include_docs => true}), &block
379
- view['rows'].collect{|r|new(r['doc'])} if view['rows']
380
- rescue
381
- # fallback for old versions of couchdb that don't
382
- # have include_docs support
383
- view = fetch_view name, opts, &block
384
- view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
385
- end
386
- end
387
- end
388
-
389
- def fetch_view view_name, opts, &block
390
- retryable = true
391
- begin
392
- design_doc.view(view_name, opts, &block)
393
- # the design doc could have been deleted by a rouge process
394
- rescue RestClient::ResourceNotFound => e
395
- if retryable
396
- refresh_design_doc
397
- retryable = false
398
- retry
399
- else
400
- raise e
401
- end
402
- end
403
- end
404
-
405
- def design_doc_id
406
- "_design/#{design_doc_slug}"
407
- end
408
-
409
- def design_doc_slug
410
- return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
411
- funcs = []
412
- design_doc['views'].each do |name, view|
413
- funcs << "#{name}/#{view['map']}#{view['reduce']}"
414
- end
415
- md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
416
- self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
417
- end
418
-
419
- def default_design_doc
420
- {
421
- "language" => "javascript",
422
- "views" => {
423
- 'all' => {
424
- 'map' => "function(doc) {
425
- if (doc['couchrest-type'] == '#{self.to_s}') {
426
- emit(null,null);
427
- }
428
- }"
429
- }
430
- }
431
- }
432
- end
433
-
434
- def refresh_design_doc
435
- did = design_doc_id
436
- saved = database.get(did) rescue nil
437
- if saved
438
- design_doc['views'].each do |name, view|
439
- saved['views'][name] = view
440
- end
441
- database.save(saved)
442
- else
443
- design_doc['_id'] = did
444
- design_doc.delete('_rev')
445
- design_doc.database = database
446
- design_doc.save
447
- end
448
- self.design_doc_fresh = true
449
- end
450
-
451
- end # class << self
452
-
453
- # returns the database used by this model's class
454
- def database
455
- self.class.database
456
- end
457
-
458
- # Takes a hash as argument, and applies the values by using writer methods
459
- # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
460
- # missing. In case of error, no attributes are changed.
461
- def update_attributes_without_saving hash
462
- hash.each do |k, v|
463
- raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{k}" unless self.respond_to?("#{k}=")
464
- end
465
- hash.each do |k, v|
466
- self.send("#{k}=",v)
467
- end
468
- end
469
-
470
- # Takes a hash as argument, and applies the values by using writer methods
471
- # for each key. Raises a NoMethodError if the corresponding methods are
472
- # missing. In case of error, no attributes are changed.
473
- def update_attributes hash
474
- update_attributes_without_saving hash
475
- save
476
- end
477
-
478
- # for compatibility with old-school frameworks
479
- alias :new_record? :new_document?
480
-
481
- # We override this to create the create and update callback opportunities.
482
- # I think we should drop those and just have save. If you care, in your callback,
483
- # check new_document?
484
- def save actually=false
485
- if actually
486
- super()
487
- else
488
- if new_document?
489
- create
490
- else
491
- update
492
- end
493
- end
494
- end
495
-
496
- # Saves the document to the db using create or update. Raises an exception
497
- # if the document is not saved properly.
498
- def save!
499
- raise "#{self.inspect} failed to save" unless self.save
500
- end
501
-
502
- def update
503
- save :actually
504
- end
505
-
506
- # Deletes the document from the database. Runs the :delete callbacks.
507
- # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
508
- # document to be saved to a new <tt>_id</tt>.
509
- def destroy
510
- result = database.delete self
511
- if result['ok']
512
- self['_rev'] = nil
513
- self['_id'] = nil
514
- end
515
- result['ok']
516
- end
517
-
518
- def create
519
- # can we use the callbacks for this?
520
- set_unique_id if self.respond_to?(:set_unique_id)
521
- save :actually
522
- end
523
-
524
- private
525
-
526
- def apply_defaults
527
- if self.class.default
528
- self.class.default.each do |k,v|
529
- self[k] = v
530
- end
531
- end
532
- end
533
-
534
- def cast_keys
535
- return unless self.class.casts
536
- # TODO move the argument checking to the cast method for early crashes
537
- self.class.casts.each do |k,v|
538
- next unless self[k]
539
- target = v[:as]
540
- if target.is_a?(Array)
541
- klass = ::Extlib::Inflection.constantize(target[0])
542
- self[k] = self[k].collect do |value|
543
- klass.new(value)
544
- end
545
- else
546
- self[k] = ::Extlib::Inflection.constantize(target).new(self[k])
547
- end
548
- end
549
- end
550
-
551
- include ::Extlib::Hook
552
- # todo: drop create and update hooks...
553
- # (use new_record? in callbacks if you care)
554
- register_instance_hooks :save, :create, :update, :destroy
555
-
556
- end # class Model
557
- end # module CouchRest
@@ -1,51 +0,0 @@
1
- module CouchRest
2
- class Server
3
- attr_accessor :uri, :uuid_batch_count
4
- def initialize server = 'http://localhost:5984', uuid_batch_count = 1000
5
- @uri = server
6
- @uuid_batch_count = uuid_batch_count
7
- end
8
-
9
- # List all databases on the server
10
- def databases
11
- CouchRest.get "#{@uri}/_all_dbs"
12
- end
13
-
14
- # Returns a CouchRest::Database for the given name
15
- def database name
16
- CouchRest::Database.new(self, name)
17
- end
18
-
19
- # Creates the database if it doesn't exist
20
- def database! name
21
- create_db(name) rescue nil
22
- database name
23
- end
24
-
25
- # GET the welcome message
26
- def info
27
- CouchRest.get "#{@uri}/"
28
- end
29
-
30
- # Create a database
31
- def create_db name
32
- CouchRest.put "#{@uri}/#{name}"
33
- database name
34
- end
35
-
36
- # Restart the CouchDB instance
37
- def restart!
38
- CouchRest.post "#{@uri}/_restart"
39
- end
40
-
41
- # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
42
- def next_uuid count = @uuid_batch_count
43
- @uuids ||= []
44
- if @uuids.empty?
45
- @uuids = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
46
- end
47
- @uuids.pop
48
- end
49
-
50
- end
51
- end
@@ -1,4 +0,0 @@
1
- module CouchRest
2
- class View
3
- end
4
- end