samflores-couchrest 0.2.1 → 0.12.3

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 (66) hide show
  1. data/README.md +10 -34
  2. data/Rakefile +5 -2
  3. data/bin/couchdir +20 -0
  4. data/examples/model/example.rb +13 -19
  5. data/examples/word_count/word_count.rb +24 -3
  6. data/examples/word_count/word_count_query.rb +6 -7
  7. data/lib/couchrest/core/database.rb +49 -126
  8. data/lib/couchrest/core/document.rb +25 -58
  9. data/lib/couchrest/core/model.rb +612 -0
  10. data/lib/couchrest/core/server.rb +10 -47
  11. data/lib/couchrest/core/validations.rb +328 -0
  12. data/lib/couchrest/monkeypatches.rb +0 -95
  13. data/lib/couchrest.rb +10 -57
  14. data/spec/couchrest/core/database_spec.rb +68 -183
  15. data/spec/couchrest/core/design_spec.rb +1 -1
  16. data/spec/couchrest/core/document_spec.rb +104 -285
  17. data/spec/couchrest/core/model_spec.rb +855 -0
  18. data/spec/couchrest/helpers/pager_spec.rb +1 -1
  19. data/spec/spec_helper.rb +7 -13
  20. metadata +17 -83
  21. data/examples/word_count/word_count_views.rb +0 -26
  22. data/lib/couchrest/core/response.rb +0 -16
  23. data/lib/couchrest/mixins/attachments.rb +0 -31
  24. data/lib/couchrest/mixins/callbacks.rb +0 -483
  25. data/lib/couchrest/mixins/design_doc.rb +0 -64
  26. data/lib/couchrest/mixins/document_queries.rb +0 -48
  27. data/lib/couchrest/mixins/extended_attachments.rb +0 -68
  28. data/lib/couchrest/mixins/extended_document_mixins.rb +0 -6
  29. data/lib/couchrest/mixins/properties.rb +0 -125
  30. data/lib/couchrest/mixins/validation.rb +0 -234
  31. data/lib/couchrest/mixins/views.rb +0 -168
  32. data/lib/couchrest/mixins.rb +0 -4
  33. data/lib/couchrest/more/casted_model.rb +0 -28
  34. data/lib/couchrest/more/extended_document.rb +0 -217
  35. data/lib/couchrest/more/property.rb +0 -40
  36. data/lib/couchrest/support/blank.rb +0 -42
  37. data/lib/couchrest/support/class.rb +0 -191
  38. data/lib/couchrest/validation/auto_validate.rb +0 -163
  39. data/lib/couchrest/validation/contextual_validators.rb +0 -78
  40. data/lib/couchrest/validation/validation_errors.rb +0 -118
  41. data/lib/couchrest/validation/validators/absent_field_validator.rb +0 -74
  42. data/lib/couchrest/validation/validators/confirmation_validator.rb +0 -99
  43. data/lib/couchrest/validation/validators/format_validator.rb +0 -117
  44. data/lib/couchrest/validation/validators/formats/email.rb +0 -66
  45. data/lib/couchrest/validation/validators/formats/url.rb +0 -43
  46. data/lib/couchrest/validation/validators/generic_validator.rb +0 -120
  47. data/lib/couchrest/validation/validators/length_validator.rb +0 -134
  48. data/lib/couchrest/validation/validators/method_validator.rb +0 -89
  49. data/lib/couchrest/validation/validators/numeric_validator.rb +0 -104
  50. data/lib/couchrest/validation/validators/required_field_validator.rb +0 -109
  51. data/spec/couchrest/core/server_spec.rb +0 -35
  52. data/spec/couchrest/more/casted_extended_doc_spec.rb +0 -40
  53. data/spec/couchrest/more/casted_model_spec.rb +0 -98
  54. data/spec/couchrest/more/extended_doc_attachment_spec.rb +0 -130
  55. data/spec/couchrest/more/extended_doc_spec.rb +0 -509
  56. data/spec/couchrest/more/extended_doc_view_spec.rb +0 -207
  57. data/spec/couchrest/more/property_spec.rb +0 -130
  58. data/spec/couchrest/support/class_spec.rb +0 -59
  59. data/spec/fixtures/more/article.rb +0 -34
  60. data/spec/fixtures/more/card.rb +0 -20
  61. data/spec/fixtures/more/course.rb +0 -14
  62. data/spec/fixtures/more/event.rb +0 -6
  63. data/spec/fixtures/more/invoice.rb +0 -17
  64. data/spec/fixtures/more/person.rb +0 -8
  65. data/spec/fixtures/more/question.rb +0 -6
  66. data/spec/fixtures/more/service.rb +0 -12
@@ -1,483 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '..', 'support', 'class')
2
-
3
- # Extracted from ActiveSupport::Callbacks written by Yehuda Katz
4
- # http://github.com/wycats/rails/raw/abstract_controller/activesupport/lib/active_support/new_callbacks.rb
5
- # http://github.com/wycats/rails/raw/18b405f154868204a8f332888871041a7bad95e1/activesupport/lib/active_support/callbacks.rb
6
-
7
- module CouchRest
8
- # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
9
- # before or after an alteration of the object state.
10
- #
11
- # Mixing in this module allows you to define callbacks in your class.
12
- #
13
- # Example:
14
- # class Storage
15
- # include ActiveSupport::Callbacks
16
- #
17
- # define_callbacks :save
18
- # end
19
- #
20
- # class ConfigStorage < Storage
21
- # save_callback :before, :saving_message
22
- # def saving_message
23
- # puts "saving..."
24
- # end
25
- #
26
- # save_callback :after do |object|
27
- # puts "saved"
28
- # end
29
- #
30
- # def save
31
- # _run_save_callbacks do
32
- # puts "- save"
33
- # end
34
- # end
35
- # end
36
- #
37
- # config = ConfigStorage.new
38
- # config.save
39
- #
40
- # Output:
41
- # saving...
42
- # - save
43
- # saved
44
- #
45
- # Callbacks from parent classes are inherited.
46
- #
47
- # Example:
48
- # class Storage
49
- # include ActiveSupport::Callbacks
50
- #
51
- # define_callbacks :save
52
- #
53
- # save_callback :before, :prepare
54
- # def prepare
55
- # puts "preparing save"
56
- # end
57
- # end
58
- #
59
- # class ConfigStorage < Storage
60
- # save_callback :before, :saving_message
61
- # def saving_message
62
- # puts "saving..."
63
- # end
64
- #
65
- # save_callback :after do |object|
66
- # puts "saved"
67
- # end
68
- #
69
- # def save
70
- # _run_save_callbacks do
71
- # puts "- save"
72
- # end
73
- # end
74
- # end
75
- #
76
- # config = ConfigStorage.new
77
- # config.save
78
- #
79
- # Output:
80
- # preparing save
81
- # saving...
82
- # - save
83
- # saved
84
- module Callbacks
85
- def self.included(klass)
86
- klass.extend ClassMethods
87
- end
88
-
89
- def run_callbacks(kind, options = {}, &blk)
90
- send("_run_#{kind}_callbacks", &blk)
91
- end
92
-
93
- class Callback
94
- @@_callback_sequence = 0
95
-
96
- attr_accessor :filter, :kind, :name, :options, :per_key, :klass
97
- def initialize(filter, kind, options, klass, name)
98
- @kind, @klass = kind, klass
99
- @name = name
100
-
101
- normalize_options!(options)
102
-
103
- @per_key = options.delete(:per_key)
104
- @raw_filter, @options = filter, options
105
- @filter = _compile_filter(filter)
106
- @compiled_options = _compile_options(options)
107
- @callback_id = next_id
108
-
109
- _compile_per_key_options
110
- end
111
-
112
- def clone(klass)
113
- obj = super()
114
- obj.klass = klass
115
- obj.per_key = @per_key.dup
116
- obj.options = @options.dup
117
- obj.per_key[:if] = @per_key[:if].dup
118
- obj.per_key[:unless] = @per_key[:unless].dup
119
- obj.options[:if] = @options[:if].dup
120
- obj.options[:unless] = @options[:unless].dup
121
- obj
122
- end
123
-
124
- def normalize_options!(options)
125
- options[:if] = Array(options[:if])
126
- options[:unless] = Array(options[:unless])
127
-
128
- options[:per_key] ||= {}
129
- options[:per_key][:if] = Array(options[:per_key][:if])
130
- options[:per_key][:unless] = Array(options[:per_key][:unless])
131
- end
132
-
133
- def next_id
134
- @@_callback_sequence += 1
135
- end
136
-
137
- def matches?(_kind, _name, _filter)
138
- @kind == _kind &&
139
- @name == _name &&
140
- @filter == _filter
141
- end
142
-
143
- def _update_filter(filter_options, new_options)
144
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
145
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
146
- end
147
-
148
- def recompile!(_options, _per_key)
149
- _update_filter(self.options, _options)
150
- _update_filter(self.per_key, _per_key)
151
-
152
- @callback_id = next_id
153
- @filter = _compile_filter(@raw_filter)
154
- @compiled_options = _compile_options(@options)
155
- _compile_per_key_options
156
- end
157
-
158
- def _compile_per_key_options
159
- key_options = _compile_options(@per_key)
160
-
161
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
162
- def _one_time_conditions_valid_#{@callback_id}?
163
- true #{key_options[0]}
164
- end
165
- RUBY_EVAL
166
- end
167
-
168
- # This will supply contents for before and around filters, and no
169
- # contents for after filters (for the forward pass).
170
- def start(key = nil, options = {})
171
- object, terminator = (options || {}).values_at(:object, :terminator)
172
-
173
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
174
-
175
- terminator ||= false
176
-
177
- # options[0] is the compiled form of supplied conditions
178
- # options[1] is the "end" for the conditional
179
-
180
- if @kind == :before || @kind == :around
181
- if @kind == :before
182
- # if condition # before_save :filter_name, :if => :condition
183
- # filter_name
184
- # end
185
- filter = <<-RUBY_EVAL
186
- unless halted
187
- result = #{@filter}
188
- halted ||= (#{terminator})
189
- end
190
- RUBY_EVAL
191
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
192
- else
193
- # Compile around filters with conditions into proxy methods
194
- # that contain the conditions.
195
- #
196
- # For `around_save :filter_name, :if => :condition':
197
- #
198
- # def _conditional_callback_save_17
199
- # if condition
200
- # filter_name do
201
- # yield self
202
- # end
203
- # else
204
- # yield self
205
- # end
206
- # end
207
-
208
- name = "_conditional_callback_#{@kind}_#{next_id}"
209
- txt = <<-RUBY_EVAL
210
- def #{name}(halted)
211
- #{@compiled_options[0] || "if true"} && !halted
212
- #{@filter} do
213
- yield self
214
- end
215
- else
216
- yield self
217
- end
218
- end
219
- RUBY_EVAL
220
- @klass.class_eval(txt)
221
- "#{name}(halted) do"
222
- end
223
- end
224
- end
225
-
226
- # This will supply contents for around and after filters, but not
227
- # before filters (for the backward pass).
228
- def end(key = nil, options = {})
229
- object = (options || {})[:object]
230
-
231
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
232
-
233
- if @kind == :around || @kind == :after
234
- # if condition # after_save :filter_name, :if => :condition
235
- # filter_name
236
- # end
237
- if @kind == :after
238
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
239
- else
240
- "end"
241
- end
242
- end
243
- end
244
-
245
- private
246
- # Options support the same options as filters themselves (and support
247
- # symbols, string, procs, and objects), so compile a conditional
248
- # expression based on the options
249
- def _compile_options(options)
250
- return [] if options[:if].empty? && options[:unless].empty?
251
-
252
- conditions = []
253
-
254
- unless options[:if].empty?
255
- conditions << Array(_compile_filter(options[:if]))
256
- end
257
-
258
- unless options[:unless].empty?
259
- conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
260
- end
261
-
262
- ["if #{conditions.flatten.join(" && ")}", "end"]
263
- end
264
-
265
- # Filters support:
266
- # Arrays:: Used in conditions. This is used to specify
267
- # multiple conditions. Used internally to
268
- # merge conditions from skip_* filters
269
- # Symbols:: A method to call
270
- # Strings:: Some content to evaluate
271
- # Procs:: A proc to call with the object
272
- # Objects:: An object with a before_foo method on it to call
273
- #
274
- # All of these objects are compiled into methods and handled
275
- # the same after this point:
276
- # Arrays:: Merged together into a single filter
277
- # Symbols:: Already methods
278
- # Strings:: class_eval'ed into methods
279
- # Procs:: define_method'ed into methods
280
- # Objects::
281
- # a method is created that calls the before_foo method
282
- # on the object.
283
- def _compile_filter(filter)
284
- method_name = "_callback_#{@kind}_#{next_id}"
285
- case filter
286
- when Array
287
- filter.map {|f| _compile_filter(f)}
288
- when Symbol
289
- filter
290
- when Proc
291
- @klass.send(:define_method, method_name, &filter)
292
- method_name << (filter.arity == 1 ? "(self)" : "")
293
- when String
294
- @klass.class_eval <<-RUBY_EVAL
295
- def #{method_name}
296
- #{filter}
297
- end
298
- RUBY_EVAL
299
- method_name
300
- else
301
- kind, name = @kind, @name
302
- @klass.send(:define_method, method_name) do
303
- filter.send("#{kind}_#{name}", self)
304
- end
305
- method_name
306
- end
307
- end
308
- end
309
-
310
- # This method_missing is supplied to catch callbacks with keys and create
311
- # the appropriate callback for future use.
312
- def method_missing(meth, *args, &blk)
313
- if meth.to_s =~ /_run__([\w:]+)__(\w+)__(\w+)__callbacks/
314
- return self.class._create_and_run_keyed_callback($1, $2.to_sym, $3.to_sym, self, &blk)
315
- end
316
- super
317
- end
318
-
319
- # An Array with a compile method
320
- class CallbackChain < Array
321
- def initialize(symbol)
322
- @symbol = symbol
323
- end
324
-
325
- def compile(key = nil, options = {})
326
- method = []
327
- method << "halted = false"
328
- each do |callback|
329
- method << callback.start(key, options)
330
- end
331
- method << "yield self if block_given?"
332
- reverse_each do |callback|
333
- method << callback.end(key, options)
334
- end
335
- method.compact.join("\n")
336
- end
337
-
338
- def clone(klass)
339
- chain = CallbackChain.new(@symbol)
340
- chain.push(*map {|c| c.clone(klass)})
341
- end
342
- end
343
-
344
- module ClassMethods
345
- CHAINS = {:before => :before, :around => :before, :after => :after} unless self.const_defined?("CHAINS")
346
-
347
- # Make the _run_save_callbacks method. The generated method takes
348
- # a block that it'll yield to. It'll call the before and around filters
349
- # in order, yield the block, and then run the after filters.
350
- #
351
- # _run_save_callbacks do
352
- # save
353
- # end
354
- #
355
- # The _run_save_callbacks method can optionally take a key, which
356
- # will be used to compile an optimized callback method for each
357
- # key. See #define_callbacks for more information.
358
- def _define_runner(symbol, str, options)
359
- str = <<-RUBY_EVAL
360
- def _run_#{symbol}_callbacks(key = nil)
361
- if key
362
- send("_run__\#{self.class.name.split("::").last}__#{symbol}__\#{key}__callbacks") { yield if block_given? }
363
- else
364
- #{str}
365
- end
366
- end
367
- RUBY_EVAL
368
-
369
- class_eval str, __FILE__, __LINE__ + 1
370
-
371
- before_name, around_name, after_name =
372
- options.values_at(:before, :after, :around)
373
- end
374
-
375
- # This is called the first time a callback is called with a particular
376
- # key. It creates a new callback method for the key, calculating
377
- # which callbacks can be omitted because of per_key conditions.
378
- def _create_and_run_keyed_callback(klass, kind, key, obj, &blk)
379
- @_keyed_callbacks ||= {}
380
- @_keyed_callbacks[[kind, key]] ||= begin
381
- str = self.send("_#{kind}_callbacks").compile(key, :object => obj, :terminator => self.send("_#{kind}_terminator"))
382
-
383
- self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
384
- def _run__#{klass.split("::").last}__#{kind}__#{key}__callbacks
385
- #{str}
386
- end
387
- RUBY_EVAL
388
-
389
- true
390
- end
391
-
392
- obj.send("_run__#{klass.split("::").last}__#{kind}__#{key}__callbacks", &blk)
393
- end
394
-
395
- # Define callbacks.
396
- #
397
- # Creates a <name>_callback method that you can use to add callbacks.
398
- #
399
- # Syntax:
400
- # save_callback :before, :before_meth
401
- # save_callback :after, :after_meth, :if => :condition
402
- # save_callback :around {|r| stuff; yield; stuff }
403
- #
404
- # The <name>_callback method also updates the _run_<name>_callbacks
405
- # method, which is the public API to run the callbacks.
406
- #
407
- # Also creates a skip_<name>_callback method that you can use to skip
408
- # callbacks.
409
- #
410
- # When creating or skipping callbacks, you can specify conditions that
411
- # are always the same for a given key. For instance, in ActionPack,
412
- # we convert :only and :except conditions into per-key conditions.
413
- #
414
- # before_filter :authenticate, :except => "index"
415
- # becomes
416
- # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
417
- #
418
- # Per-Key conditions are evaluated only once per use of a given key.
419
- # In the case of the above example, you would do:
420
- #
421
- # run_dispatch_callbacks(action_name) { ... dispatch stuff ... }
422
- #
423
- # In that case, each action_name would get its own compiled callback
424
- # method that took into consideration the per_key conditions. This
425
- # is a speed improvement for ActionPack.
426
- def define_callbacks(*symbols)
427
- terminator = symbols.pop if symbols.last.is_a?(String)
428
- symbols.each do |symbol|
429
- self.class_inheritable_accessor("_#{symbol}_terminator")
430
- self.send("_#{symbol}_terminator=", terminator)
431
- self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
432
- class_inheritable_accessor :_#{symbol}_callbacks
433
- self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
434
-
435
- def self.#{symbol}_callback(*filters, &blk)
436
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
437
- options = filters.last.is_a?(Hash) ? filters.pop : {}
438
- filters.unshift(blk) if block_given?
439
-
440
- filters.map! do |filter|
441
- # overrides parent class
442
- self._#{symbol}_callbacks.delete_if {|c| c.matches?(type, :#{symbol}, filter)}
443
- Callback.new(filter, type, options.dup, self, :#{symbol})
444
- end
445
- self._#{symbol}_callbacks.push(*filters)
446
- _define_runner(:#{symbol},
447
- self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator),
448
- options)
449
- end
450
-
451
- def self.skip_#{symbol}_callback(*filters, &blk)
452
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
453
- options = filters.last.is_a?(Hash) ? filters.pop : {}
454
- filters.unshift(blk) if block_given?
455
- filters.each do |filter|
456
- self._#{symbol}_callbacks = self._#{symbol}_callbacks.clone(self)
457
-
458
- filter = self._#{symbol}_callbacks.find {|c| c.matches?(type, :#{symbol}, filter) }
459
- per_key = options[:per_key] || {}
460
- if filter
461
- filter.recompile!(options, per_key)
462
- else
463
- self._#{symbol}_callbacks.delete(filter)
464
- end
465
- _define_runner(:#{symbol},
466
- self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator),
467
- options)
468
- end
469
-
470
- end
471
-
472
- def self.reset_#{symbol}_callbacks
473
- self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
474
- _define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, {})
475
- end
476
-
477
- self.#{symbol}_callback(:before)
478
- RUBY_EVAL
479
- end
480
- end
481
- end
482
- end
483
- end
@@ -1,64 +0,0 @@
1
- require 'digest/md5'
2
-
3
- module CouchRest
4
- module Mixins
5
- module DesignDoc
6
-
7
- def self.included(base)
8
- base.extend(ClassMethods)
9
- end
10
-
11
- module ClassMethods
12
- def design_doc_id
13
- "_design/#{design_doc_slug}"
14
- end
15
-
16
- def design_doc_slug
17
- return design_doc_slug_cache if (design_doc_slug_cache && design_doc_fresh)
18
- funcs = []
19
- design_doc ||= Design.new(default_design_doc)
20
- design_doc['views'].each do |name, view|
21
- funcs << "#{name}/#{view['map']}#{view['reduce']}"
22
- end
23
- md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
24
- self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
25
- end
26
-
27
- def default_design_doc
28
- {
29
- "language" => "javascript",
30
- "views" => {
31
- 'all' => {
32
- 'map' => "function(doc) {
33
- if (doc['couchrest-type'] == '#{self.to_s}') {
34
- emit(null,null);
35
- }
36
- }"
37
- }
38
- }
39
- }
40
- end
41
-
42
- def refresh_design_doc
43
- did = design_doc_id
44
- saved = database.get(did) rescue nil
45
- if saved
46
- design_doc['views'].each do |name, view|
47
- saved['views'][name] = view
48
- end
49
- database.save_doc(saved)
50
- self.design_doc = saved
51
- else
52
- design_doc['_id'] = did
53
- design_doc.delete('_rev')
54
- design_doc.database = database
55
- design_doc.save
56
- end
57
- self.design_doc_fresh = true
58
- end
59
-
60
- end # module ClassMethods
61
-
62
- end
63
- end
64
- end
@@ -1,48 +0,0 @@
1
- module CouchRest
2
- module Mixins
3
- module DocumentQueries
4
-
5
- def self.included(base)
6
- base.extend(ClassMethods)
7
- end
8
-
9
- module ClassMethods
10
-
11
- # Load all documents that have the "couchrest-type" field equal to the
12
- # name of the current class. Take the standard set of
13
- # CouchRest::Database#view options.
14
- def all(opts = {}, &block)
15
- self.design_doc ||= Design.new(default_design_doc)
16
- unless design_doc_fresh
17
- refresh_design_doc
18
- end
19
- view(:all, opts, &block)
20
- end
21
-
22
- # Load the first document that have the "couchrest-type" field equal to
23
- # the name of the current class.
24
- #
25
- # ==== Returns
26
- # Object:: The first object instance available
27
- # or
28
- # Nil:: if no instances available
29
- #
30
- # ==== Parameters
31
- # opts<Hash>::
32
- # View options, see <tt>CouchRest::Database#view</tt> options for more info.
33
- def first(opts = {})
34
- first_instance = self.all(opts.merge!(:limit => 1))
35
- first_instance.empty? ? nil : first_instance.first
36
- end
37
-
38
- # Load a document from the database by id
39
- def get(id)
40
- doc = database.get id
41
- new(doc)
42
- end
43
-
44
- end
45
-
46
- end
47
- end
48
- end
@@ -1,68 +0,0 @@
1
- module CouchRest
2
- module Mixins
3
- module ExtendedAttachments
4
-
5
- # creates a file attachment to the current doc
6
- def create_attachment(args={})
7
- raise ArgumentError unless args[:file] && args[:name]
8
- return if has_attachment?(args[:name])
9
- self['_attachments'] ||= {}
10
- set_attachment_attr(args)
11
- rescue ArgumentError => e
12
- raise ArgumentError, 'You must specify :file and :name'
13
- end
14
-
15
- # reads the data from an attachment
16
- def read_attachment(attachment_name)
17
- Base64.decode64(database.fetch_attachment(self, attachment_name))
18
- end
19
-
20
- # modifies a file attachment on the current doc
21
- def update_attachment(args={})
22
- raise ArgumentError unless args[:file] && args[:name]
23
- return unless has_attachment?(args[:name])
24
- delete_attachment(args[:name])
25
- set_attachment_attr(args)
26
- rescue ArgumentError => e
27
- raise ArgumentError, 'You must specify :file and :name'
28
- end
29
-
30
- # deletes a file attachment from the current doc
31
- def delete_attachment(attachment_name)
32
- return unless self['_attachments']
33
- self['_attachments'].delete attachment_name
34
- end
35
-
36
- # returns true if attachment_name exists
37
- def has_attachment?(attachment_name)
38
- !!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
39
- end
40
-
41
- # returns URL to fetch the attachment from
42
- def attachment_url(attachment_name)
43
- return unless has_attachment?(attachment_name)
44
- "#{database.root}/#{self.id}/#{attachment_name}"
45
- end
46
-
47
- private
48
-
49
- def encode_attachment(data)
50
- ::Base64.encode64(data).gsub(/\r|\n/,'')
51
- end
52
-
53
- def get_mime_type(file)
54
- ::MIME::Types.type_for(file.path).empty? ?
55
- 'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
56
- end
57
-
58
- def set_attachment_attr(args)
59
- content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
60
- self['_attachments'][args[:name]] = {
61
- 'content-type' => content_type,
62
- 'data' => encode_attachment(args[:file].read)
63
- }
64
- end
65
-
66
- end # module ExtendedAttachments
67
- end
68
- end
@@ -1,6 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'properties')
2
- require File.join(File.dirname(__FILE__), 'document_queries')
3
- require File.join(File.dirname(__FILE__), 'views')
4
- require File.join(File.dirname(__FILE__), 'design_doc')
5
- require File.join(File.dirname(__FILE__), 'validation')
6
- require File.join(File.dirname(__FILE__), 'extended_attachments')