samflores-couchrest 0.2.1 → 0.12.3

Sign up to get free protection for your applications and to get access to all the features.
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')