shrine 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

@@ -4,9 +4,10 @@ require "securerandom"
4
4
  require "json"
5
5
 
6
6
  class Shrine
7
+ # A generic exception used by Shrine.
7
8
  class Error < StandardError; end
8
9
 
9
- # Raised when a file was not a valid IO.
10
+ # Raised when a file is not a valid IO.
10
11
  class InvalidFile < Error
11
12
  def initialize(io, missing_methods)
12
13
  @io, @missing_methods = io, missing_methods
@@ -24,7 +25,7 @@ class Shrine
24
25
  end
25
26
 
26
27
  # Methods which an object has to respond to in order to be considered
27
- # an IO object. Keys are method names, and values are arguments.
28
+ # an IO object, along with their arguments.
28
29
  IO_METHODS = {
29
30
  :read => [:length, :outbuf],
30
31
  :eof? => [],
@@ -33,24 +34,24 @@ class Shrine
33
34
  :close => [],
34
35
  }
35
36
 
36
- # Core class that represents a file uploaded to a storage. The instance
37
+ # Core class that represents a file uploaded to a storage. The instance
37
38
  # methods for this class are added by Shrine::Plugins::Base::FileMethods, the
38
39
  # class methods are added by Shrine::Plugins::Base::FileClassMethods.
39
40
  class UploadedFile
40
41
  @shrine_class = ::Shrine
41
42
  end
42
43
 
43
- # Core class which generates attachment-specific modules that are included in
44
- # model classes. The instance methods for this class are added by
45
- # Shrine::Plugins::Base::AttachmentMethods, the class methods are added by
46
- # Shrine::Plugins::Base::AttachmentClassMethods.
44
+ # Core class which creates attachment modules for specified attribute names
45
+ # that are included into model classes. The instance methods for this class
46
+ # are added by Shrine::Plugins::Base::AttachmentMethods, the class methods
47
+ # are added by Shrine::Plugins::Base::AttachmentClassMethods.
47
48
  class Attachment < Module
48
49
  @shrine_class = ::Shrine
49
50
  end
50
51
 
51
- # Core class which handles attaching files on records. The instance methods
52
- # for this class are added by Shrine::Plugins::Base::AttachmentMethods, the
53
- # class methods are added by Shrine::Plugins::Base::AttachmentClassMethods.
52
+ # Core class which handles attaching files to model instances. The instance
53
+ # methods for this class are added by Shrine::Plugins::Base::AttacherMethods,
54
+ # the class methods are added by Shrine::Plugins::Base::AttacherClassMethods.
54
55
  class Attacher
55
56
  @shrine_class = ::Shrine
56
57
  end
@@ -63,8 +64,8 @@ class Shrine
63
64
  module Plugins
64
65
  @plugins = {}
65
66
 
66
- # If the registered plugin already exists, use it. Otherwise, require it
67
- # and return it. This raises a LoadError if such a plugin doesn't exist,
67
+ # If the registered plugin already exists, use it. Otherwise, require it
68
+ # and return it. This raises a LoadError if such a plugin doesn't exist,
68
69
  # or a Shrine::Error if it exists but it does not register itself
69
70
  # correctly.
70
71
  def self.load_plugin(name)
@@ -76,7 +77,7 @@ class Shrine
76
77
  end
77
78
 
78
79
  # Register the given plugin with Shrine, so that it can be loaded using
79
- # `Shrine.plugin` with a symbol. Should be used by plugin files. Example:
80
+ # `Shrine.plugin` with a symbol. Should be used by plugin files. Example:
80
81
  #
81
82
  # Shrine::Plugins.register_plugin(:plugin_name, PluginModule)
82
83
  def self.register_plugin(name, mod)
@@ -91,11 +92,11 @@ class Shrine
91
92
  # Generic options for this class, plugins store their options here.
92
93
  attr_reader :opts
93
94
 
94
- # A hash of storages and their symbol identifiers.
95
+ # A hash of storages with their symbol identifiers.
95
96
  attr_accessor :storages
96
97
 
97
98
  # When inheriting Shrine, copy the instance variables into the subclass,
98
- # and setup the subclasses for core classes.
99
+ # and create subclasses of core classes.
99
100
  def inherited(subclass)
100
101
  subclass.instance_variable_set(:@opts, opts.dup)
101
102
  subclass.opts.each do |key, value|
@@ -118,12 +119,12 @@ class Shrine
118
119
  subclass.const_set(:Attacher, attacher_class)
119
120
  end
120
121
 
121
- # Load a new plugin into the current class. A plugin can be a module
122
- # which is used directly, or a symbol represented a registered plugin
123
- # which will be required and then used. Returns nil.
122
+ # Load a new plugin into the current class. A plugin can be a module
123
+ # which is used directly, or a symbol representing a registered plugin
124
+ # which will be required and then loaded.
124
125
  #
125
- # Shrine.plugin PluginModule
126
- # Shrine.plugin :basic_authentication
126
+ # Shrine.plugin MyPlugin
127
+ # Shrine.plugin :my_plugin
127
128
  def plugin(plugin, *args, &block)
128
129
  plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
129
130
  plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
@@ -139,35 +140,33 @@ class Shrine
139
140
  nil
140
141
  end
141
142
 
142
- # Retrieves the storage specifies by the symbol/string, and raises an
143
- # appropriate error if the storage is missing
143
+ # Retrieves the storage under the given identifier (can be a Symbol or
144
+ # a String), and raises Shrine::Error if the storage is missing.
144
145
  def find_storage(name)
145
146
  storages.each { |key, value| return value if key.to_s == name.to_s }
146
147
  raise Error, "storage #{name.inspect} isn't registered on #{self}"
147
148
  end
148
149
 
149
150
  # Generates an instance of Shrine::Attachment to be included in the
150
- # model class. Example:
151
+ # model class. Example:
151
152
  #
152
- # class User
153
- # include Shrine[:avatar] # alias for `Shrine.attachment(:avatar)`
153
+ # class Photo
154
+ # include Shrine[:image] # creates a Shrine::Attachment object
154
155
  # end
155
156
  def attachment(name)
156
157
  self::Attachment.new(name)
157
158
  end
158
159
  alias [] attachment
159
160
 
160
- # Instantiates a Shrine::UploadedFile from a JSON string or a hash, and
161
- # optionally yields the returned objects (useful with versions). This
162
- # is used internally by Shrine::Attacher, but it's also useful when you
163
- # need to deserialize the uploaded file in background jobs.
161
+ # Instantiates a Shrine::UploadedFile from a hash, and optionally
162
+ # yields the returned object.
164
163
  #
165
- # uploaded_file #=> #<Shrine::UploadedFile>
166
- # json = uploaded_file.to_json #=> '{"storage":"cache","id":"...","metadata":{...}}'
167
- # Shrine.uploaded_file(json) #=> #<Shrine::UploadedFile>
164
+ # data = {"storage" => "cache", "id" => "abc123.jpg", "metadata" => {}}
165
+ # Shrine.uploaded_file(data) #=> #<Shrine::UploadedFile>
168
166
  def uploaded_file(object, &block)
169
167
  case object
170
168
  when String
169
+ warn "Giving a string to Shrine.uploaded_file is deprecated and won't be possible in Shrine 3. Use Attacher#uploaded_file instead."
171
170
  uploaded_file(JSON.parse(object), &block)
172
171
  when Hash
173
172
  uploaded_file(self::UploadedFile.new(object), &block)
@@ -180,10 +179,10 @@ class Shrine
180
179
  end
181
180
 
182
181
  module InstanceMethods
183
- # The symbol that identifies the storage.
182
+ # The symbol identifier for the storage used by the uploader.
184
183
  attr_reader :storage_key
185
184
 
186
- # The storage object identified by #storage_key.
185
+ # The storage object used by the uploader.
187
186
  attr_reader :storage
188
187
 
189
188
  # Accepts a storage symbol registered in `Shrine.storages`.
@@ -192,33 +191,28 @@ class Shrine
192
191
  @storage_key = storage_key.to_sym
193
192
  end
194
193
 
195
- # The class-level options hash. This should probably not be modified
196
- # at the instance level.
194
+ # The class-level options hash. This should probably not be modified at
195
+ # the instance level.
197
196
  def opts
198
197
  self.class.opts
199
198
  end
200
199
 
201
- # The main method for uploading files. Takes in an IO object and an
202
- # optional context (used internally by Shrine::Attacher). It calls
203
- # user-defined #process, and aferwards it calls #store. The `io` is
200
+ # The main method for uploading files. Takes an IO-like object and an
201
+ # optional context hash (used internally by Shrine::Attacher). It calls
202
+ # user-defined #process, and aferwards it calls #store. The `io` is
204
203
  # closed after upload.
205
204
  def upload(io, context = {})
206
205
  io = processed(io, context) || io
207
206
  store(io, context)
208
207
  end
209
208
 
210
- # User is expected to perform processing inside of this method, and
209
+ # User is expected to perform processing inside this method, and
211
210
  # return the processed files. Returning nil signals that no proccessing
212
211
  # has been done and that the original file should be used.
213
212
  #
214
213
  # class ImageUploader < Shrine
215
214
  # def process(io, context)
216
- # case context[:action]
217
- # when :cache
218
- # # do processing
219
- # when :store
220
- # # do processing
221
- # end
215
+ # # do processing and return processed files
222
216
  # end
223
217
  # end
224
218
  def process(io, context = {})
@@ -229,25 +223,26 @@ class Shrine
229
223
  # \#generate_location, but you can pass in `:location` to upload to
230
224
  # a specific location.
231
225
  #
232
- # uploader.store(io, location: "custom/location.jpg")
226
+ # uploader.store(io)
233
227
  def store(io, context = {})
234
228
  _store(io, context)
235
229
  end
236
230
 
237
- # Checks if the storage identified with this instance uploaded the
238
- # given file.
231
+ # Returns true if the storage of the given uploaded file matches the
232
+ # storage of this uploader.
239
233
  def uploaded?(uploaded_file)
240
234
  uploaded_file.storage_key == storage_key.to_s
241
235
  end
242
236
 
243
- # Deletes the given uploaded file.
237
+ # Deletes the given uploaded file and returns it.
244
238
  def delete(uploaded_file, context = {})
245
239
  _delete(uploaded_file, context)
246
240
  uploaded_file
247
241
  end
248
242
 
249
- # Generates a unique location for the uploaded file, and preserves an
250
- # optional extension.
243
+ # Generates a unique location for the uploaded file, preserving the
244
+ # file extension. Can be overriden in uploaders for generating custom
245
+ # location.
251
246
  def generate_location(io, context = {})
252
247
  extension = ".#{io.extension}" if io.is_a?(UploadedFile) && io.extension
253
248
  extension ||= File.extname(extract_filename(io).to_s)
@@ -257,8 +252,7 @@ class Shrine
257
252
  end
258
253
 
259
254
  # Extracts filename, size and MIME type from the file, which is later
260
- # accessible through `UploadedFile#metadata`. When the uploaded file
261
- # is later promoted, this metadata is simply copied over.
255
+ # accessible through `UploadedFile#metadata`.
262
256
  def extract_metadata(io, context = {})
263
257
  {
264
258
  "filename" => extract_filename(io),
@@ -269,7 +263,7 @@ class Shrine
269
263
 
270
264
  private
271
265
 
272
- # Extracts the filename from the IO using some basic heuristics.
266
+ # Attempts to extract the appropriate filename from the IO object.
273
267
  def extract_filename(io)
274
268
  if io.respond_to?(:original_filename)
275
269
  io.original_filename
@@ -278,7 +272,7 @@ class Shrine
278
272
  end
279
273
  end
280
274
 
281
- # Extracts the MIME type from the IO using some basic heuristics.
275
+ # Attempts to extract the MIME type from the IO object.
282
276
  def extract_mime_type(io)
283
277
  if io.respond_to?(:content_type)
284
278
  warn "The \"mime_type\" Shrine metadata field will be set from the \"Content-Type\" request header, which might not hold the actual MIME type of the file. It is recommended to load the determine_mime_type plugin which determines MIME type from file content." unless opts.key?(:mime_type_analyzer)
@@ -286,14 +280,16 @@ class Shrine
286
280
  end
287
281
  end
288
282
 
289
- # Extracts the filesize from the IO.
283
+ # Extracts the filesize from the IO object.
290
284
  def extract_size(io)
291
285
  io.size
292
286
  end
293
287
 
294
- # Called by #store. It first generates the location if it wasn't
295
- # already provided with the `:location` option. Afterwards it extracts
296
- # the metadata, stores the file, and returns a Shrine::UploadedFile.
288
+ # It first asserts that `io` is a valid IO object. It then extracts
289
+ # metadata and generates the location, before calling the storage to
290
+ # upload the IO object, passing the extracted metadata and location.
291
+ # Finally it returns a Shrine::UploadedFile object which represents the
292
+ # file that was uploaded.
297
293
  def _store(io, context)
298
294
  _enforce_io(io)
299
295
  metadata = get_metadata(io, context)
@@ -301,24 +297,26 @@ class Shrine
301
297
 
302
298
  put(io, context.merge(location: location, metadata: metadata))
303
299
 
304
- self.class::UploadedFile.new(
300
+ self.class.uploaded_file(
305
301
  "id" => location,
306
302
  "storage" => storage_key.to_s,
307
303
  "metadata" => metadata,
308
304
  )
309
305
  end
310
306
 
311
- # Removes the file. Called by #delete.
307
+ # Delegates to #remove.
312
308
  def _delete(uploaded_file, context)
313
309
  remove(uploaded_file, context)
314
310
  end
315
311
 
316
- # Copies the file to the storage.
312
+ # Delegates to #copy.
317
313
  def put(io, context)
318
314
  copy(io, context)
319
315
  end
320
316
 
321
- # Does the actual uploading, calling `#upload` on the storage.
317
+ # Calls `#upload` on the storage, passing to it the location, metadata
318
+ # and any upload options. The storage might modify the location or
319
+ # metadata that were passed in. The uploaded IO is then closed.
322
320
  def copy(io, context)
323
321
  location = context[:location]
324
322
  metadata = context[:metadata]
@@ -329,24 +327,24 @@ class Shrine
329
327
  io.close rescue nil
330
328
  end
331
329
 
332
- # Does the actual deletion, calls `UploadedFile#delete`.
330
+ # Delegates to `UploadedFile#delete`.
333
331
  def remove(uploaded_file, context)
334
332
  uploaded_file.delete
335
333
  end
336
334
 
337
- # Calls #process and returns the processed files.
335
+ # Delegates to #process.
338
336
  def processed(io, context)
339
337
  process(io, context)
340
338
  end
341
339
 
342
- # Retrieves the location for the given io and context. First it looks
340
+ # Retrieves the location for the given IO and context. First it looks
343
341
  # for the `:location` option, otherwise it calls #generate_location.
344
342
  def get_location(io, context)
345
343
  context[:location] || generate_location(io, context)
346
344
  end
347
345
 
348
- # Copies the metadata over from an UploadedFile or calls
349
- # #extract_metadata.
346
+ # If the IO object is a Shrine::UploadedFile, it simply copies over its
347
+ # metadata, otherwise it calls #extract_metadata.
350
348
  def get_metadata(io, context)
351
349
  if io.is_a?(UploadedFile)
352
350
  io.metadata.dup
@@ -355,22 +353,24 @@ class Shrine
355
353
  end
356
354
  end
357
355
 
358
- # Checks if the object is a valid IO by checking that it responds to
359
- # `#read`, `#eof?`, `#rewind`, `#size` and `#close`, otherwise raises
360
- # Shrine::InvalidFile.
356
+ # Asserts that the object is a valid IO object, specifically that it
357
+ # responds to `#read`, `#eof?`, `#rewind`, `#size` and `#close`. If the
358
+ # object doesn't respond to one of these methods, a Shrine::InvalidFile
359
+ # error is raised.
361
360
  def _enforce_io(io)
362
361
  missing_methods = IO_METHODS.select { |m, a| !io.respond_to?(m) }
363
362
  raise InvalidFile.new(io, missing_methods) if missing_methods.any?
364
363
  end
365
364
 
366
- # Generates a UID to use in location for uploaded files.
365
+ # Generates a unique identifier that can be used for a location.
367
366
  def generate_uid(io)
368
367
  SecureRandom.hex
369
368
  end
370
369
  end
371
370
 
372
371
  module AttachmentClassMethods
373
- # Reference to the Shrine class related to this attachment class.
372
+ # Returns the Shrine class that this attachment class is
373
+ # namespaced under.
374
374
  attr_accessor :shrine_class
375
375
 
376
376
  # Since Attachment is anonymously subclassed when Shrine is subclassed,
@@ -382,16 +382,13 @@ class Shrine
382
382
  end
383
383
 
384
384
  module AttachmentMethods
385
- # Since Shrine::Attachment is a subclass of `Module`, this method
386
- # generates a module, which should be included in a model class.
385
+ # Instantiates an attachment module for a given attribute name, which
386
+ # can then be included to a model class.
387
387
  def initialize(name)
388
388
  @name = name
389
389
 
390
- # We store the attacher class so that it can be retrieved by the model
391
- # at the instance level when instantiating the attacher. We use a
392
- # class variable because (a) it can be accessed from the instance
393
- # level without needing to create a class-level reader, and (b) we
394
- # want it to be inherited when subclassing the model
390
+ # We store the attacher class so that the model can instantiate the
391
+ # correct attacher instance.
395
392
  class_variable_set(:"@@#{name}_attacher_class", shrine_class::Attacher)
396
393
 
397
394
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -413,21 +410,30 @@ class Shrine
413
410
  RUBY
414
411
  end
415
412
 
416
- # Displays the attachment name.
413
+ # Includes the attachment name in the output.
417
414
  #
418
- # Shrine[:avatar] #=> #<Shrine::Attachment(avatar)>
415
+ # Shrine[:image].to_s #=> "#<Shrine::Attachment(image)>"
416
+ def to_s
417
+ "#<#{self.class.inspect}(#{@name})>"
418
+ end
419
+
420
+ # Includes the attachment name in the output.
421
+ #
422
+ # Shrine[:image].inspect #=> "#<Shrine::Attachment(image)>"
419
423
  def inspect
420
424
  "#<#{self.class.inspect}(#{@name})>"
421
425
  end
422
426
 
423
- # Returns the Shrine class related to this attachment.
427
+ # Returns the Shrine class that this attachment's class is namespaced
428
+ # under.
424
429
  def shrine_class
425
430
  self.class.shrine_class
426
431
  end
427
432
  end
428
433
 
429
434
  module AttacherClassMethods
430
- # Reference to the Shrine class related to this attacher class.
435
+ # Returns the Shrine class that this attacher class is namespaced
436
+ # under.
431
437
  attr_accessor :shrine_class
432
438
 
433
439
  # Since Attacher is anonymously subclassed when Shrine is subclassed,
@@ -438,7 +444,7 @@ class Shrine
438
444
  end
439
445
 
440
446
  # Block that is executed in context of Shrine::Attacher during
441
- # validation. Example:
447
+ # validation. Example:
442
448
  #
443
449
  # Shrine::Attacher.validate do
444
450
  # if get.size > 5*1024*1024
@@ -451,8 +457,22 @@ class Shrine
451
457
  end
452
458
 
453
459
  module AttacherMethods
454
- attr_reader :cache, :store, :context, :errors
460
+ # Returns the uploader that is used for the temporary storage.
461
+ attr_reader :cache
462
+
463
+ # Returns the uploader that is used for the permanent storage.
464
+ attr_reader :store
465
+
466
+ # Returns the context that will be sent to the uploader when uploading
467
+ # and deleting. Can be modified with additional data to be sent to the
468
+ # uploader.
469
+ attr_reader :context
455
470
 
471
+ # Returns an array of validation errors created on file assignment in
472
+ # the `Attacher.validate` block.
473
+ attr_reader :errors
474
+
475
+ # Initializes the necessary attributes.
456
476
  def initialize(record, name, cache: :cache, store: :store)
457
477
  @cache = shrine_class.new(cache)
458
478
  @store = shrine_class.new(store)
@@ -462,13 +482,14 @@ class Shrine
462
482
 
463
483
  # Returns the model instance associated with the attacher.
464
484
  def record; context[:record]; end
485
+
465
486
  # Returns the attachment name associated with the attacher.
466
487
  def name; context[:name]; end
467
488
 
468
- # Receives the attachment value from the form. If it receives a JSON
469
- # string or a hash, it will assume this refrences an already cached
470
- # file (e.g. when it persisted after validation errors).
471
- # Otherwise it assumes that it's an IO object and caches it.
489
+ # Receives the attachment value from the form. It can receive an
490
+ # already cached file as a JSON string, otherwise it assumes that it's
491
+ # an IO object and uploads it to the temporary storage. The cached file
492
+ # is then written to the attachment attribute in the JSON format.
472
493
  def assign(value)
473
494
  if value.is_a?(String)
474
495
  return if value == "" || value == read || !cache.uploaded?(uploaded_file(value))
@@ -479,8 +500,9 @@ class Shrine
479
500
  end
480
501
  end
481
502
 
482
- # Assigns a Shrine::UploadedFile, runs validation and schedules the
483
- # old file for deletion.
503
+ # Accepts a Shrine::UploadedFile object and writes is to the attachment
504
+ # attribute. It then runs file validations, and records that the
505
+ # attachment has changed.
484
506
  def set(uploaded_file)
485
507
  @old = get
486
508
  _set(uploaded_file)
@@ -498,57 +520,58 @@ class Shrine
498
520
  instance_variable_defined?(:@old)
499
521
  end
500
522
 
501
- # Plugins can override this if they want something to be done on save.
523
+ # Plugins can override this if they want something to be done before
524
+ # save.
502
525
  def save
503
526
  end
504
527
 
505
528
  # Deletes the old file and promotes the new one. Typically this should
506
- # be called after saving.
529
+ # be called after saving the model instance.
507
530
  def finalize
508
531
  replace
509
532
  remove_instance_variable(:@old)
510
533
  _promote(action: :store) if cached?
511
534
  end
512
535
 
513
- # Promotes the file.
536
+ # Delegates to #promote, overriden for backgrounding.
514
537
  def _promote(uploaded_file = get, **options)
515
538
  promote(uploaded_file, **options)
516
539
  end
517
540
 
518
- # Uploads the cached file to store, and updates the record with the
519
- # stored file.
541
+ # Uploads the cached file to store, and writes the stored file to the
542
+ # attachment attribute.
520
543
  def promote(uploaded_file = get, **options)
521
544
  stored_file = store!(uploaded_file, **options)
522
545
  result = swap(stored_file) or _delete(stored_file, action: :abort)
523
546
  result
524
547
  end
525
548
 
526
- # Calls #update, overriden in ORM plugins.
549
+ # Calls #update, overriden in ORM plugins, and returns true if the
550
+ # attachment was successfully updated.
527
551
  def swap(uploaded_file)
528
552
  update(uploaded_file)
529
553
  uploaded_file if uploaded_file == get
530
554
  end
531
555
 
532
- # Deletes the attachment that was replaced, and is called after saving
533
- # by ORM integrations. If also removes `@old` so that #save and #finalize
534
- # don't get called for the current attachment anymore.
556
+ # Deletes the previous attachment that was replaced, typically called
557
+ # after the model instance is saved with the new attachment.
535
558
  def replace
536
559
  _delete(@old, action: :replace) if @old && !cache.uploaded?(@old)
537
560
  end
538
561
 
539
- # Deletes the attachment. Typically this should be called after
540
- # destroying a record.
562
+ # Deletes the current attachment, typically called after destroying the
563
+ # record.
541
564
  def destroy
542
565
  _delete(get, action: :destroy) if get && !cache.uploaded?(get)
543
566
  end
544
567
 
545
- # Deletes the uploaded file.
568
+ # Delegates to #delete!, overriden for backgrounding.
546
569
  def _delete(uploaded_file, **options)
547
570
  delete!(uploaded_file, **options)
548
571
  end
549
572
 
550
- # Returns the URL to the attached file (internally calls `#url` on the
551
- # storage), forwarding any URL options to the storage.
573
+ # Returns the URL to the attached file if it's present. It forwards any
574
+ # given URL options to the storage.
552
575
  def url(**options)
553
576
  get.url(**options) if read
554
577
  end
@@ -563,41 +586,55 @@ class Shrine
563
586
  get && store.uploaded?(get)
564
587
  end
565
588
 
566
- # Retrieves the uploaded file from the record column.
589
+ # Returns a Shrine::UploadedFile instantiated from the data written to
590
+ # the attachment attribute.
567
591
  def get
568
592
  uploaded_file(read) if read
569
593
  end
570
594
 
571
- # It reads from the record's `<attachment>_data` column.
595
+ # Reads from the `<attachment>_data` attribute on the model instance.
596
+ # It returns nil if the value is blank.
572
597
  def read
573
- value = record.send(:"#{name}_data")
574
- value unless value.nil? || value.empty?
598
+ value = record.send(data_attribute)
599
+ convert_after_read(value) unless value.nil? || value.empty?
575
600
  end
576
601
 
577
- # Uploads the file to cache passing context.
602
+ # Uploads the file using the #cache uploader, passing the #context.
578
603
  def cache!(io, **options)
579
604
  warn "Sending :phase to Shrine::Attacher#cache! is deprecated and will not be supported in Shrine 3. Use :action instead." if options[:phase]
580
605
  cache.upload(io, context.merge(_equalize_phase_and_action(options)))
581
606
  end
582
607
 
583
- # Uploads the file to store passing context.
608
+ # Uploads the file using the #store uploader, passing the #context.
584
609
  def store!(io, **options)
585
610
  warn "Sending :phase to Shrine::Attacher#store! is deprecated and will not be supported in Shrine 3. Use :action instead." if options[:phase]
586
611
  store.upload(io, context.merge(_equalize_phase_and_action(options)))
587
612
  end
588
613
 
589
- # Deletes the file passing context.
614
+ # Deletes the file using the uploader, passing the #context.
590
615
  def delete!(uploaded_file, **options)
591
616
  warn "Sending :phase to Shrine::Attacher#delete! is deprecated and will not be supported in Shrine 3. Use :action instead." if options[:phase]
592
617
  store.delete(uploaded_file, context.merge(_equalize_phase_and_action(options)))
593
618
  end
594
619
 
595
- # Delegates to `Shrine.uploaded_file`.
596
- def uploaded_file(*args, &block)
597
- shrine_class.uploaded_file(*args, &block)
620
+ # Enhances `Shrine.uploaded_file` with the ability to recognize uploaded
621
+ # files as JSON strings.
622
+ def uploaded_file(object, &block)
623
+ if object.is_a?(String)
624
+ uploaded_file(JSON.parse(object), &block)
625
+ else
626
+ shrine_class.uploaded_file(object, &block)
627
+ end
628
+ end
629
+
630
+ # The name of the attribute on the model instance that is used to store
631
+ # the attachment data. Defaults to `<attachment>_data`.
632
+ def data_attribute
633
+ :"#{name}_data"
598
634
  end
599
635
 
600
- # Returns the Shrine class related to this attacher.
636
+ # Returns the Shrine class that this attacher's class is namespaced
637
+ # under.
601
638
  def shrine_class
602
639
  self.class.shrine_class
603
640
  end
@@ -609,24 +646,42 @@ class Shrine
609
646
  set(cached_file)
610
647
  end
611
648
 
612
- # Sets and saves the uploaded file.
649
+ # Writes the uploaded file the attachment attribute. Overriden in ORM
650
+ # plugins to additionally save the model instance.
613
651
  def update(uploaded_file)
614
652
  _set(uploaded_file)
615
653
  end
616
654
 
617
- # The validation block provided by `Shrine.validate`.
655
+ # The validation block registered with `Attacher.validate`.
618
656
  def validate_block
619
657
  shrine_class.opts[:validate]
620
658
  end
621
659
 
622
- # It dumps the UploadedFile to JSON and writes the result to the column.
660
+ # Converts the UploadedFile to a data hash and writes it to the
661
+ # attribute.
623
662
  def _set(uploaded_file)
624
- write(uploaded_file ? uploaded_file.to_json : nil)
663
+ write(uploaded_file ? convert_to_data(uploaded_file) : nil)
625
664
  end
626
665
 
627
- # It writes to record's `<attachment>_data` column.
666
+ # Writes to the `<attachment>_data` attribute on the model instance.
628
667
  def write(value)
629
- record.send(:"#{name}_data=", value)
668
+ value = convert_before_write(value) unless value.nil?
669
+ record.send(:"#{data_attribute}=", value)
670
+ end
671
+
672
+ # Returns the data hash of the given UploadedFile.
673
+ def convert_to_data(uploaded_file)
674
+ uploaded_file.data
675
+ end
676
+
677
+ # Returns the hash value dumped to JSON.
678
+ def convert_before_write(value)
679
+ value.to_json
680
+ end
681
+
682
+ # Returns the read value unchanged.
683
+ def convert_after_read(value)
684
+ value
630
685
  end
631
686
 
632
687
  # Temporary method used for transitioning from :phase to :action.
@@ -638,7 +693,7 @@ class Shrine
638
693
  end
639
694
 
640
695
  module FileClassMethods
641
- # Reference to the Shrine class related to this uploaded file class.
696
+ # Returns the Shrine class that this file class is namespaced under.
642
697
  attr_accessor :shrine_class
643
698
 
644
699
  # Since UploadedFile is anonymously subclassed when Shrine is subclassed,
@@ -650,32 +705,32 @@ class Shrine
650
705
  end
651
706
 
652
707
  module FileMethods
653
- # The entire data hash which identifies this uploaded file.
708
+ # The hash of information which defines this uploaded file.
654
709
  attr_reader :data
655
710
 
711
+ # Initializes the uploaded file with the given data hash.
656
712
  def initialize(data)
657
713
  @data = data
658
714
  @data["metadata"] ||= {}
659
715
  storage # ensure storage exists
660
716
  end
661
717
 
662
- # The ID of the uploaded file, which holds the location of the actual
663
- # file on the storage
718
+ # The location where the file was uploaded to the storage.
664
719
  def id
665
720
  @data.fetch("id")
666
721
  end
667
722
 
668
- # The storage key as a string.
723
+ # The string identifier of the storage the file is uploaded to.
669
724
  def storage_key
670
725
  @data.fetch("storage")
671
726
  end
672
727
 
673
- # A hash of metadata.
728
+ # A hash of file metadata that was extracted during upload.
674
729
  def metadata
675
730
  @data.fetch("metadata")
676
731
  end
677
732
 
678
- # The filename that was extracted from the original file.
733
+ # The filename that was extracted from the uploaded file.
679
734
  def original_filename
680
735
  metadata["filename"]
681
736
  end
@@ -686,34 +741,34 @@ class Shrine
686
741
  File.extname(id)[1..-1] || File.extname(original_filename.to_s)[1..-1]
687
742
  end
688
743
 
689
- # The filesize of the original file.
744
+ # The filesize of the uploaded file.
690
745
  def size
691
746
  (@io && @io.size) || (metadata["size"] && Integer(metadata["size"]))
692
747
  end
693
748
 
694
- # The MIME type of the original file.
749
+ # The MIME type of the uploaded file.
695
750
  def mime_type
696
751
  metadata["mime_type"]
697
752
  end
698
753
  alias content_type mime_type
699
754
 
700
- # Opens the underlying IO for reading and yields it to the block,
701
- # closing it after the block finishes. Use #to_io for opening without a
702
- # block.
755
+ # Opens an IO object of the uploaded file for reading and yields it to
756
+ # the block, closing it after the block finishes. For opening without
757
+ # a block #to_io can be used.
703
758
  #
704
759
  # uploaded_file.open do |io|
705
- # # ...
760
+ # puts io.read # prints the content of the file
706
761
  # end
707
762
  def open
708
763
  @io = storage.open(id)
709
764
  yield @io
710
765
  ensure
711
- @io.close
766
+ @io.close if @io
712
767
  @io = nil
713
768
  end
714
769
 
715
- # Calls `#download` on the storage if it is implemented, otherwise
716
- # streams the underlying IO to a Tempfile.
770
+ # Calls `#download` on the storage if the storage implements it,
771
+ # otherwise uses #open to stream the underlying IO to a Tempfile.
717
772
  def download
718
773
  if storage.respond_to?(:download)
719
774
  storage.download(id)
@@ -724,39 +779,37 @@ class Shrine
724
779
  end
725
780
  end
726
781
 
727
- # Part of Shrine::UploadedFile's complying to the IO interface. It
728
- # delegates to the internally downloaded file.
782
+ # Part of complying to the IO interface. It delegates to the internally
783
+ # opened IO object.
729
784
  def read(*args)
730
785
  io.read(*args)
731
786
  end
732
787
 
733
- # Part of Shrine::UploadedFile's complying to the IO interface. It
734
- # delegates to the internally downloaded file.
788
+ # Part of complying to the IO interface. It delegates to the internally
789
+ # opened IO object.
735
790
  def eof?
736
791
  io.eof?
737
792
  end
738
793
 
739
- # Part of Shrine::UploadedFile's complying to the IO interface. It
740
- # delegates to the internally downloaded file.
794
+ # Part of complying to the IO interface. It delegates to the internally
795
+ # opened IO object.
741
796
  def close
742
- if @io
743
- io.close
744
- io.delete if io.class.name == "Tempfile"
745
- end
797
+ io.close if @io
746
798
  end
747
799
 
748
- # Part of Shrine::UploadedFile's complying to the IO interface. It
749
- # delegates to the internally downloaded file.
800
+ # Part of complying to the IO interface. It delegates to the internally
801
+ # opened IO object.
750
802
  def rewind
751
803
  io.rewind
752
804
  end
753
805
 
754
- # Calls `#url` on the storage, forwarding any options.
806
+ # Calls `#url` on the storage, forwarding any given URL options.
755
807
  def url(**options)
756
808
  storage.url(id, **options)
757
809
  end
758
810
 
759
- # Calls `#exists?` on the storage, which checks that the file exists.
811
+ # Calls `#exists?` on the storage, which checks whether the file exists
812
+ # on the storage.
760
813
  def exists?
761
814
  storage.exists?(id)
762
815
  end
@@ -766,18 +819,19 @@ class Shrine
766
819
  uploader.upload(io, context.merge(location: id))
767
820
  end
768
821
 
769
- # Calls `#delete` on the storage, which deletes the remote file.
822
+ # Calls `#delete` on the storage, which deletes the file from the
823
+ # storage.
770
824
  def delete
771
825
  storage.delete(id)
772
826
  end
773
827
 
774
- # Returns the underlying IO.
828
+ # Returns an opened IO object for the uploaded file.
775
829
  def to_io
776
830
  io
777
831
  end
778
832
 
779
- # Serializes the uploaded file to JSON, suitable for storing in the
780
- # column or passing to a background job.
833
+ # Returns the data hash in the JSON format. Suitable for storing in a
834
+ # database column or passing to a background job.
781
835
  def to_json(*args)
782
836
  data.to_json(*args)
783
837
  end
@@ -787,8 +841,8 @@ class Shrine
787
841
  data
788
842
  end
789
843
 
790
- # Two uploaded files are equal if they're uploaded to the same storage
791
- # and they have the same #id.
844
+ # Returns true if the other UploadedFile is uploaded to the same
845
+ # storage and it has the same #id.
792
846
  def ==(other)
793
847
  other.is_a?(self.class) &&
794
848
  self.id == other.id &&
@@ -796,32 +850,30 @@ class Shrine
796
850
  end
797
851
  alias eql? ==
798
852
 
853
+ # Enables using UploadedFile objects as hash keys.
799
854
  def hash
800
855
  [id, storage_key].hash
801
856
  end
802
857
 
803
- # The instance of `Shrine` with the corresponding storage.
858
+ # Returns an uploader object for the corresponding storage.
804
859
  def uploader
805
860
  shrine_class.new(storage_key)
806
861
  end
807
862
 
808
- # The storage class this file was uploaded to.
863
+ # Returns the storage that this file was uploaded to.
809
864
  def storage
810
865
  shrine_class.find_storage(storage_key)
811
866
  end
812
867
 
813
- # Returns the Shrine class related to this uploaded file.
868
+ # Returns the Shrine class that this file's class is namespaced under.
814
869
  def shrine_class
815
870
  self.class.shrine_class
816
871
  end
817
872
 
818
- # Show only the data hash in inspect output.
819
- def inspect
820
- "#{to_s.chomp(">")} @data=#{data.inspect}>"
821
- end
822
-
823
873
  private
824
874
 
875
+ # Returns an opened IO object for the uploaded file by calling `#open`
876
+ # on the storage.
825
877
  def io
826
878
  @io ||= storage.open(id)
827
879
  end