shrine 2.2.0 → 2.3.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.

Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +143 -84
  3. data/doc/carrierwave.md +187 -47
  4. data/doc/direct_s3.md +57 -39
  5. data/doc/paperclip.md +183 -91
  6. data/doc/refile.md +148 -124
  7. data/doc/regenerating_versions.md +2 -3
  8. data/lib/shrine.rb +26 -28
  9. data/lib/shrine/plugins/activerecord.rb +22 -31
  10. data/lib/shrine/plugins/add_metadata.rb +1 -1
  11. data/lib/shrine/plugins/backgrounding.rb +19 -7
  12. data/lib/shrine/plugins/backup.rb +2 -2
  13. data/lib/shrine/plugins/cached_attachment_data.rb +1 -1
  14. data/lib/shrine/plugins/copy.rb +52 -0
  15. data/lib/shrine/plugins/data_uri.rb +1 -1
  16. data/lib/shrine/plugins/default_storage.rb +2 -2
  17. data/lib/shrine/plugins/default_url.rb +1 -1
  18. data/lib/shrine/plugins/default_url_options.rb +1 -1
  19. data/lib/shrine/plugins/delete_promoted.rb +1 -1
  20. data/lib/shrine/plugins/delete_raw.rb +1 -1
  21. data/lib/shrine/plugins/determine_mime_type.rb +3 -2
  22. data/lib/shrine/plugins/direct_upload.rb +36 -24
  23. data/lib/shrine/plugins/download_endpoint.rb +3 -3
  24. data/lib/shrine/plugins/dynamic_storage.rb +2 -2
  25. data/lib/shrine/plugins/hooks.rb +1 -1
  26. data/lib/shrine/plugins/included.rb +3 -4
  27. data/lib/shrine/plugins/keep_files.rb +1 -1
  28. data/lib/shrine/plugins/logging.rb +1 -1
  29. data/lib/shrine/plugins/module_include.rb +1 -1
  30. data/lib/shrine/plugins/moving.rb +10 -5
  31. data/lib/shrine/plugins/multi_delete.rb +2 -2
  32. data/lib/shrine/plugins/parallelize.rb +2 -2
  33. data/lib/shrine/plugins/parsed_json.rb +1 -1
  34. data/lib/shrine/plugins/pretty_location.rb +1 -1
  35. data/lib/shrine/plugins/processing.rb +11 -9
  36. data/lib/shrine/plugins/rack_file.rb +1 -1
  37. data/lib/shrine/plugins/recache.rb +14 -4
  38. data/lib/shrine/plugins/remote_url.rb +1 -1
  39. data/lib/shrine/plugins/remove_attachment.rb +3 -4
  40. data/lib/shrine/plugins/remove_invalid.rb +1 -1
  41. data/lib/shrine/plugins/restore_cached_data.rb +11 -4
  42. data/lib/shrine/plugins/sequel.rb +34 -45
  43. data/lib/shrine/plugins/store_dimensions.rb +1 -1
  44. data/lib/shrine/plugins/upload_options.rb +2 -2
  45. data/lib/shrine/plugins/validation_helpers.rb +7 -8
  46. data/lib/shrine/plugins/versions.rb +31 -30
  47. data/lib/shrine/storage/file_system.rb +16 -12
  48. data/lib/shrine/storage/s3.rb +36 -2
  49. data/lib/shrine/version.rb +1 -1
  50. data/shrine.gemspec +9 -8
  51. metadata +11 -9
@@ -3,14 +3,14 @@ require "down"
3
3
 
4
4
  class Shrine
5
5
  module Plugins
6
- # The download_endpoint plugin provides a [Roda] endpoint for downloading
6
+ # The `download_endpoint` plugin provides a Rack endpoint for downloading
7
7
  # uploaded files from specified storages. This can be useful when files
8
8
  # from your storages aren't accessible over URL (e.g. database storages) or
9
- # if you want to authenticate your downloads.
9
+ # if you want to authenticate your downloads. It requires the [Roda] gem.
10
10
  #
11
11
  # plugin :download_endpoint, storages: [:store], prefix: "attachments"
12
12
  #
13
- # After loading the plugin the endpoint should be mounted:
13
+ # After loading the plugin the endpoint can be mounted:
14
14
  #
15
15
  # Rails.appliations.routes.draw do
16
16
  # mount Shrine::DownloadEndpoint => "/attachments"
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The dynamic_storage plugin allows you to register a storage using a
3
+ # The `dynamic_storage` plugin allows you to register a storage using a
4
4
  # regex, and evaluate the storage class dynamically depending on the regex.
5
5
  #
6
6
  # Example:
@@ -16,7 +16,7 @@ class Shrine
16
16
  # saves files to the bucket "foo". The block is yielded an instance of
17
17
  # `MatchData`.
18
18
  #
19
- # This can be useful in combination with the default_storage plugin.
19
+ # This can be useful in combination with the `default_storage` plugin.
20
20
  module DynamicStorage
21
21
  module ClassMethods
22
22
  def dynamic_storages
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The hooks plugin allows you to trigger some code around
3
+ # The `hooks` plugin allows you to trigger some code around
4
4
  # processing/storing/deleting of each file.
5
5
  #
6
6
  # plugin :hooks
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The included plugin allows you to hook up to the `.included` hook of the
3
+ # The `included` plugin allows you to hook up to the `.included` hook of the
4
4
  # attachment module, and call additional methods on the model which includes
5
5
  # it.
6
6
  #
@@ -10,9 +10,8 @@ class Shrine
10
10
  # end
11
11
  # end
12
12
  #
13
- # The block is evaluated in the context of the model via `instance_exec`.
14
- # This means you cannot use keywords like `def`, instead you should use the
15
- # metaprogramming equivalents like `define_method`.
13
+ # If you want to define additional methods on the model, it's recommended
14
+ # to use the `module_include` plugin instead.
16
15
  module Included
17
16
  def self.configure(uploader, &block)
18
17
  uploader.opts[:included_block] = block
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The keep_files plugin gives you the ability to prevent files from being
3
+ # The `keep_files` plugin gives you the ability to prevent files from being
4
4
  # deleted. This functionality is useful when implementing soft deletes, or
5
5
  # when implementing some kind of [event store] where you need to track
6
6
  # history.
@@ -4,7 +4,7 @@ require "json"
4
4
 
5
5
  class Shrine
6
6
  module Plugins
7
- # The logging plugin logs any storing/processing/deleting that is performed.
7
+ # The `logging` plugin logs any storing/processing/deleting that is performed.
8
8
  #
9
9
  # plugin :logging
10
10
  #
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The module_include plugin allows you to extend Shrine's core classes for
3
+ # The `module_include` plugin allows you to extend Shrine's core classes for
4
4
  # the given uploader with modules/methods.
5
5
  #
6
6
  # plugin :module_include
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The moving plugin will *move* files to storages instead of copying them,
3
+ # The `moving` plugin will *move* files to storages instead of copying them,
4
4
  # when the storage supports it. For FileSystem this will issue a `mv`
5
5
  # command, which is instantaneous regardless of the filesize, so in that
6
6
  # case loading this plugin can significantly speed up the attachment
@@ -29,6 +29,7 @@ class Shrine
29
29
  end
30
30
  end
31
31
 
32
+ # Generates upload options and calls `#move` on the storage.
32
33
  def move(io, context)
33
34
  location = context[:location]
34
35
  metadata = context[:metadata]
@@ -37,14 +38,18 @@ class Shrine
37
38
  storage.move(io, location, shrine_metadata: metadata, **upload_options)
38
39
  end
39
40
 
40
- def movable?(io, context)
41
- storage.respond_to?(:move) && storage.movable?(io, context[:location])
42
- end
43
-
41
+ # Returns true if file should be moved and is movable.
44
42
  def move?(io, context)
43
+ return false if context[:move] == false
45
44
  moving_storage? && movable?(io, context)
46
45
  end
47
46
 
47
+ # Returns true if storage can move this file.
48
+ def movable?(io, context)
49
+ storage.respond_to?(:move) && storage.movable?(io, context[:location])
50
+ end
51
+
52
+ # Returns true if file should be moved.
48
53
  def moving_storage?
49
54
  opts[:moving_storages].nil? ||
50
55
  opts[:moving_storages].include?(storage_key)
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The multi_delete plugins allows you to leverage your storage's multi
3
+ # The `multi_delete` plugins allows you to leverage your storage's multi
4
4
  # delete capabilities.
5
5
  #
6
6
  # plugin :multi_delete
@@ -11,7 +11,7 @@ class Shrine
11
11
  #
12
12
  # Now if you're using Storage::S3, deleting an array of files will issue a
13
13
  # single HTTP request. Some other storages may support multi deletes as
14
- # well. The versions plugin uses this plugin for deleting multiple versions
14
+ # well. The `versions` plugin uses this plugin for deleting multiple versions
15
15
  # at once.
16
16
  module MultiDelete
17
17
  module InstanceMethods
@@ -2,8 +2,8 @@ require "thread"
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # The parallelize plugin parallelizes uploads and deletes when handling
6
- # versions, using threads.
5
+ # The `parallelize` plugin parallelizes uploads and deletes of multiple
6
+ # versions using threads.
7
7
  #
8
8
  # plugin :parallelize
9
9
  #
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The parsed_json plugin is suitable for the case when your framework is
3
+ # The `parsed_json` plugin is suitable for the case when your framework is
4
4
  # automatically parsing JSON query parameters, allowing you to assign
5
5
  # cached files with hashes/arrays.
6
6
  #
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The pretty_location plugin attempts to generate a nicer folder structure
3
+ # The `pretty_location` plugin attempts to generate a nicer folder structure
4
4
  # for uploaded files.
5
5
  #
6
6
  # plugin :pretty_location
@@ -1,25 +1,27 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The process plugin allows you to declaratively define
4
- # file processing for specified actions, allowing you to transform
3
+ # The `processing` plugin allows you to declaratively define file processing
4
+ # for specified actions.
5
5
  #
6
- # def process(io, context)
7
- # if context[:action] == :store
8
- # # ...
9
- # end
10
- # end
11
- #
12
- # into
6
+ # plugin :processing
13
7
  #
14
8
  # process(:store) do |io, context|
15
9
  # # ...
16
10
  # end
17
11
  #
12
+ # The `io` is the original file, while the `context` contains some
13
+ # additional information about the upload. The result of the processing
14
+ # block should be an IO-like object, which will continue being uploaded
15
+ # instead of the original.
16
+ #
18
17
  # The declarations are additive and inherited, so for the same action you
19
18
  # can declare multiple blocks, and they will be performed in the same order,
20
19
  # where output from previous will be input to next. You can return `nil`
21
20
  # in any block to signal that no processing was performed and that the
22
21
  # original file should be used.
22
+ #
23
+ # If you want the result of processing to be multiple files, use the
24
+ # `versions` plugin.
23
25
  module Processing
24
26
  def self.configure(uploader)
25
27
  uploader.opts[:processing] = {}
@@ -2,7 +2,7 @@ require "forwardable"
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # The rack_file plugin enables models to accept Rack file hashes as
5
+ # The `rack_file` plugin enables models to accept Rack file hashes as
6
6
  # attachments.
7
7
  #
8
8
  # rack_file #=>
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The recache plugin allows you to process your attachment after
3
+ # The `recache` plugin allows you to process your attachment after
4
4
  # validations succeed, but before the attachment is promoted. This is
5
5
  # useful for example when you want to generate some versions upfront (so
6
6
  # the user immediately sees them) and other versions you want to generate
@@ -16,14 +16,24 @@ class Shrine
16
16
  # process(:store) do |io, context|
17
17
  # # perform more expensive processing
18
18
  # end
19
+ #
20
+ # Recaching will be automatically triggered in a "before save" callback,
21
+ # but if you're using the attacher directly, you can call it manually:
22
+ #
23
+ # attacher.recache if attacher.attached?
19
24
  module Recache
20
25
  module AttacherMethods
21
26
  def save
22
- if get && cache.uploaded?(get)
23
- _set cache!(get, action: :recache)
24
- end
27
+ recache
25
28
  super
26
29
  end
30
+
31
+ def recache
32
+ if cached?
33
+ recached = cache!(get, action: :recache)
34
+ _set(recached)
35
+ end
36
+ end
27
37
  end
28
38
  end
29
39
 
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The remote_url plugin allows you to attach files from a remote location.
3
+ # The `remote_url` plugin allows you to attach files from a remote location.
4
4
  #
5
5
  # plugin :remote_url, max_size: 20*1024*1024
6
6
  #
@@ -1,13 +1,13 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The remove_attachment plugin allows you to delete attachments through
3
+ # The `remove_attachment` plugin allows you to delete attachments through
4
4
  # checkboxes on the web form.
5
5
  #
6
6
  # plugin :remove_attachment
7
7
  #
8
8
  # If for example your attachment is called "avatar", this plugin will add
9
9
  # `#remove_avatar` and `#remove_avatar=` methods to your model. This allows
10
- # you to easily enable deleting attached files through the form:
10
+ # you to easily delete attached files through the form:
11
11
  #
12
12
  # <%= form_for @user do |f| %>
13
13
  # <%= f.hidden_field :avatar, value: @user.avatar_data %>
@@ -16,8 +16,7 @@ class Shrine
16
16
  # <% end %>
17
17
  #
18
18
  # Now when the checkbox is ticked and the form is submitted, the attached
19
- # file will be removed. Note that the "remove_avatar" field needs to be
20
- # declared somewhere after the hidden field.
19
+ # file will be removed.
21
20
  module RemoveAttachment
22
21
  module AttachmentMethods
23
22
  def initialize(*)
@@ -1,6 +1,6 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The remove_invalid plugin automatically deletes a cached file if it was
3
+ # The `remove_invalid` plugin automatically deletes a cached file if it was
4
4
  # invalid and deassigns it from the record.
5
5
  #
6
6
  # plugin :remove_invalid
@@ -1,11 +1,18 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The restore_cached_data plugin ensures the cached file metadata hasn't
4
- # been tampered with, which can be done by modifying the hidden field
5
- # before submitting the form. The cached file's metadata will be
6
- # reextracted during assignment and replaced with potentially tampered one.
3
+ # The `restore_cached_data` plugin re-extracts cached file's metadata on
4
+ # assignment. This happens when an uploaded file is retained on validation
5
+ # errors, or when assigning direct uploaded files. In both cases you
6
+ # usually want to re-extract metadata on the server side, mainly to prevent
7
+ # tempering, but also in case of direct uploads to obtain metadata that
8
+ # couldn't be extracted on the client side.
7
9
  #
8
10
  # plugin :restore_cached_data
11
+ #
12
+ # This will give an opened `UploadedFile` for metadata extraction. For
13
+ # remote storages this will make an HTTP request, and since metadata is
14
+ # typically found in the beginning of the file, Shrine will download only
15
+ # the amount of bytes necessary for extracting the metadata.
9
16
  module RestoreCachedData
10
17
  module AttacherMethods
11
18
  private
@@ -2,7 +2,7 @@ require "sequel"
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
- # The sequel plugin extends the "attachment" interface with support for
5
+ # The `sequel` plugin extends the "attachment" interface with support for
6
6
  # Sequel.
7
7
  #
8
8
  # plugin :sequel
@@ -20,7 +20,7 @@ class Shrine
20
20
  # you should first disable transactions for those tests.
21
21
  #
22
22
  # If you want to put promoting/deleting into a background job, see the
23
- # backgrounding plugin.
23
+ # `backgrounding` plugin.
24
24
  #
25
25
  # Since attaching first saves the record with a cached attachment, then
26
26
  # saves again with a stored attachment, you can detect this in callbacks:
@@ -73,69 +73,58 @@ class Shrine
73
73
 
74
74
  return unless model < ::Sequel::Model
75
75
 
76
- if shrine_class.opts[:sequel_validations]
77
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
78
- def validate
79
- super
80
- #{@name}_attacher.errors.each do |message|
81
- errors.add(:#{@name}, message)
82
- end
83
- end
84
- RUBY
85
- end
76
+ opts = shrine_class.opts
86
77
 
87
- if shrine_class.opts[:sequel_callbacks]
88
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
89
- def before_save
90
- super
91
- #{@name}_attacher.save if #{@name}_attacher.attached?
78
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:sequel_validations]
79
+ def validate
80
+ super
81
+ #{@name}_attacher.errors.each do |message|
82
+ errors.add(:#{@name}, message)
92
83
  end
84
+ end
85
+ RUBY
93
86
 
94
- def after_commit
95
- super
96
- #{@name}_attacher.finalize if #{@name}_attacher.attached?
97
- end
87
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:sequel_callbacks]
88
+ def before_save
89
+ super
90
+ #{@name}_attacher.save if #{@name}_attacher.attached?
91
+ end
98
92
 
99
- def after_destroy_commit
100
- super
101
- #{@name}_attacher.destroy
102
- end
103
- RUBY
104
- end
93
+ def after_commit
94
+ super
95
+ #{@name}_attacher.finalize if #{@name}_attacher.attached?
96
+ end
97
+
98
+ def after_destroy_commit
99
+ super
100
+ #{@name}_attacher.destroy
101
+ end
102
+ RUBY
105
103
  end
106
104
  end
107
105
 
108
106
  module AttacherClassMethods
109
- # Needed by the backgrounding plugin.
107
+ # Needed by the `backgrounding` plugin.
110
108
  def find_record(record_class, record_id)
111
109
  record_class.with_pk(record_id)
112
110
  end
113
111
  end
114
112
 
115
113
  module AttacherMethods
116
- private
117
-
118
- # Proceeds with updating the record unless the attachment has changed.
119
- def swap(uploaded_file)
120
- return if record.send(:"#{name}_data") != record.reload.send(:"#{name}_data")
121
- super
122
- rescue ::Sequel::NoExistingObject
123
- rescue ::Sequel::Error => error
124
- raise unless error.message == "Record not found" # prior to version 4.28
125
- end
126
-
127
- # Saves the record after assignment, skipping validations.
128
- def update(uploaded_file)
129
- super
130
- record.save(validate: false)
131
- end
132
-
133
114
  # Support for Postgres JSON columns.
134
115
  def read
135
116
  value = super
136
117
  value = value.to_hash if value.respond_to?(:to_hash)
137
118
  value
138
119
  end
120
+
121
+ private
122
+
123
+ # Saves the record after assignment, skipping validations.
124
+ def update(uploaded_file)
125
+ super
126
+ record.save_changes(validate: false)
127
+ end
139
128
  end
140
129
  end
141
130