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.
- checksums.yaml +4 -4
- data/README.md +143 -84
- data/doc/carrierwave.md +187 -47
- data/doc/direct_s3.md +57 -39
- data/doc/paperclip.md +183 -91
- data/doc/refile.md +148 -124
- data/doc/regenerating_versions.md +2 -3
- data/lib/shrine.rb +26 -28
- data/lib/shrine/plugins/activerecord.rb +22 -31
- data/lib/shrine/plugins/add_metadata.rb +1 -1
- data/lib/shrine/plugins/backgrounding.rb +19 -7
- data/lib/shrine/plugins/backup.rb +2 -2
- data/lib/shrine/plugins/cached_attachment_data.rb +1 -1
- data/lib/shrine/plugins/copy.rb +52 -0
- data/lib/shrine/plugins/data_uri.rb +1 -1
- data/lib/shrine/plugins/default_storage.rb +2 -2
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/default_url_options.rb +1 -1
- data/lib/shrine/plugins/delete_promoted.rb +1 -1
- data/lib/shrine/plugins/delete_raw.rb +1 -1
- data/lib/shrine/plugins/determine_mime_type.rb +3 -2
- data/lib/shrine/plugins/direct_upload.rb +36 -24
- data/lib/shrine/plugins/download_endpoint.rb +3 -3
- data/lib/shrine/plugins/dynamic_storage.rb +2 -2
- data/lib/shrine/plugins/hooks.rb +1 -1
- data/lib/shrine/plugins/included.rb +3 -4
- data/lib/shrine/plugins/keep_files.rb +1 -1
- data/lib/shrine/plugins/logging.rb +1 -1
- data/lib/shrine/plugins/module_include.rb +1 -1
- data/lib/shrine/plugins/moving.rb +10 -5
- data/lib/shrine/plugins/multi_delete.rb +2 -2
- data/lib/shrine/plugins/parallelize.rb +2 -2
- data/lib/shrine/plugins/parsed_json.rb +1 -1
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +11 -9
- data/lib/shrine/plugins/rack_file.rb +1 -1
- data/lib/shrine/plugins/recache.rb +14 -4
- data/lib/shrine/plugins/remote_url.rb +1 -1
- data/lib/shrine/plugins/remove_attachment.rb +3 -4
- data/lib/shrine/plugins/remove_invalid.rb +1 -1
- data/lib/shrine/plugins/restore_cached_data.rb +11 -4
- data/lib/shrine/plugins/sequel.rb +34 -45
- data/lib/shrine/plugins/store_dimensions.rb +1 -1
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/validation_helpers.rb +7 -8
- data/lib/shrine/plugins/versions.rb +31 -30
- data/lib/shrine/storage/file_system.rb +16 -12
- data/lib/shrine/storage/s3.rb +36 -2
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +9 -8
- metadata +11 -9
@@ -2,7 +2,7 @@ require "active_record"
|
|
2
2
|
|
3
3
|
class Shrine
|
4
4
|
module Plugins
|
5
|
-
# The activerecord plugin extends the "attachment" interface with support
|
5
|
+
# The `activerecord` plugin extends the "attachment" interface with support
|
6
6
|
# for ActiveRecord.
|
7
7
|
#
|
8
8
|
# plugin :activerecord
|
@@ -27,7 +27,7 @@ class Shrine
|
|
27
27
|
# you should first disable transactions for those tests.
|
28
28
|
#
|
29
29
|
# If you want to put promoting/deleting into a background job, see the
|
30
|
-
# backgrounding plugin.
|
30
|
+
# `backgrounding` plugin.
|
31
31
|
#
|
32
32
|
# Since attaching first saves the record with a cached attachment, then
|
33
33
|
# saves again with a stored attachment, you can detect this in callbacks:
|
@@ -78,36 +78,34 @@ class Shrine
|
|
78
78
|
|
79
79
|
return unless model < ::ActiveRecord::Base
|
80
80
|
|
81
|
-
|
82
|
-
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
83
|
-
validate do
|
84
|
-
#{@name}_attacher.errors.each do |message|
|
85
|
-
errors.add(:#{@name}, message)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
RUBY
|
89
|
-
end
|
81
|
+
opts = shrine_class.opts
|
90
82
|
|
91
|
-
if
|
92
|
-
|
93
|
-
|
94
|
-
|
83
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:activerecord_validations]
|
84
|
+
validate do
|
85
|
+
#{@name}_attacher.errors.each do |message|
|
86
|
+
errors.add(:#{@name}, message)
|
95
87
|
end
|
88
|
+
end
|
89
|
+
RUBY
|
96
90
|
|
97
|
-
|
98
|
-
|
99
|
-
|
91
|
+
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:activerecord_callbacks]
|
92
|
+
before_save do
|
93
|
+
#{@name}_attacher.save if #{@name}_attacher.attached?
|
94
|
+
end
|
100
95
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
96
|
+
after_commit on: [:create, :update] do
|
97
|
+
#{@name}_attacher.finalize if #{@name}_attacher.attached?
|
98
|
+
end
|
99
|
+
|
100
|
+
after_commit on: [:destroy] do
|
101
|
+
#{@name}_attacher.destroy
|
102
|
+
end
|
103
|
+
RUBY
|
106
104
|
end
|
107
105
|
end
|
108
106
|
|
109
107
|
module AttacherClassMethods
|
110
|
-
# Needed by the backgrounding plugin.
|
108
|
+
# Needed by the `backgrounding` plugin.
|
111
109
|
def find_record(record_class, record_id)
|
112
110
|
record_class.where(id: record_id).first
|
113
111
|
end
|
@@ -116,13 +114,6 @@ class Shrine
|
|
116
114
|
module AttacherMethods
|
117
115
|
private
|
118
116
|
|
119
|
-
# Proceeds with updating the record unless the attachment has changed.
|
120
|
-
def swap(uploaded_file)
|
121
|
-
return if record.send(:"#{name}_data") != record.reload.send(:"#{name}_data")
|
122
|
-
super
|
123
|
-
rescue ::ActiveRecord::RecordNotFound
|
124
|
-
end
|
125
|
-
|
126
117
|
# Saves the record after assignment, skipping validations.
|
127
118
|
def update(uploaded_file)
|
128
119
|
super
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The backgrounding plugin enables you to remove processing/storing/deleting
|
3
|
+
# The `backgrounding` plugin enables you to remove processing/storing/deleting
|
4
4
|
# of files from record's lifecycle, and put them into background jobs.
|
5
5
|
# This is generally useful if you're doing processing and/or your store is
|
6
6
|
# something other than Storage::FileSystem.
|
@@ -38,7 +38,7 @@ class Shrine
|
|
38
38
|
# any other backgrounding library. This setup will work globally for all
|
39
39
|
# uploaders.
|
40
40
|
#
|
41
|
-
# The backgrounding plugin affects the `Shrine::Attacher` in a way that
|
41
|
+
# The `backgrounding` plugin affects the `Shrine::Attacher` in a way that
|
42
42
|
# `#_promote` and `#_delete` spawn background jobs, while `#promote` and
|
43
43
|
# `#delete!` are always synchronous:
|
44
44
|
#
|
@@ -81,7 +81,7 @@ class Shrine
|
|
81
81
|
#
|
82
82
|
# If you're generating versions, and you want to process some versions in
|
83
83
|
# the foreground before kicking off a background job, you can use the
|
84
|
-
# recache plugin.
|
84
|
+
# `recache` plugin.
|
85
85
|
module Backgrounding
|
86
86
|
module AttacherClassMethods
|
87
87
|
# If block is passed in, stores it to be called on promotion. Otherwise
|
@@ -127,8 +127,12 @@ class Shrine
|
|
127
127
|
def load(data)
|
128
128
|
record_class, record_id = data["record"]
|
129
129
|
record_class = Object.const_get(record_class)
|
130
|
-
|
131
|
-
|
130
|
+
|
131
|
+
record = find_record(record_class, record_id)
|
132
|
+
record ||= record_class.new.tap do |instance|
|
133
|
+
# so that the id is always included in file deletion logs
|
134
|
+
instance.singleton_class.send(:define_method, :id) { record_id }
|
135
|
+
end
|
132
136
|
|
133
137
|
name = data["name"].to_sym
|
134
138
|
|
@@ -136,6 +140,7 @@ class Shrine
|
|
136
140
|
shrine_class = Object.const_get(data["shrine_class"])
|
137
141
|
attacher = shrine_class::Attacher.new(record, name)
|
138
142
|
else
|
143
|
+
# anonymous uploader class, try to retrieve attacher from record
|
139
144
|
attacher = record.send("#{name}_attacher")
|
140
145
|
end
|
141
146
|
|
@@ -150,8 +155,8 @@ class Shrine
|
|
150
155
|
if background_promote = shrine_class.opts[:backgrounding_promote]
|
151
156
|
data = self.class.dump(self).merge(
|
152
157
|
"attachment" => uploaded_file.to_json,
|
153
|
-
"phase" => (action.to_s if action),
|
154
158
|
"action" => (action.to_s if action),
|
159
|
+
"phase" => (action.to_s if action), # legacy
|
155
160
|
)
|
156
161
|
instance_exec(data, &background_promote)
|
157
162
|
else
|
@@ -165,8 +170,8 @@ class Shrine
|
|
165
170
|
if background_delete = shrine_class.opts[:backgrounding_delete]
|
166
171
|
data = self.class.dump(self).merge(
|
167
172
|
"attachment" => uploaded_file.to_json,
|
168
|
-
"phase" => (action.to_s if action),
|
169
173
|
"action" => (action.to_s if action),
|
174
|
+
"phase" => (action.to_s if action), # legacy
|
170
175
|
)
|
171
176
|
instance_exec(data, &background_delete)
|
172
177
|
uploaded_file
|
@@ -185,6 +190,13 @@ class Shrine
|
|
185
190
|
"shrine_class" => shrine_class.name,
|
186
191
|
}
|
187
192
|
end
|
193
|
+
|
194
|
+
# Updates with the new file only if the attachment hasn't changed.
|
195
|
+
def swap(new_file)
|
196
|
+
reloaded = self.class.find_record(record.class, record.id)
|
197
|
+
return if reloaded.nil? || self.class.new(reloaded, name).read != read
|
198
|
+
super
|
199
|
+
end
|
188
200
|
end
|
189
201
|
end
|
190
202
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The backup plugin allows you to automatically back up stored files to
|
3
|
+
# The `backup` plugin allows you to automatically back up stored files to
|
4
4
|
# an additional storage.
|
5
5
|
#
|
6
6
|
# storages[:backup_store] = Shrine::Storage::S3.new(options)
|
@@ -64,7 +64,7 @@ class Shrine
|
|
64
64
|
|
65
65
|
# Upload the stored file to the backup storage.
|
66
66
|
def store_backup!(stored_file)
|
67
|
-
options = _equalize_phase_and_action(action: :backup)
|
67
|
+
options = _equalize_phase_and_action(action: :backup, move: false)
|
68
68
|
backup_store.upload(stored_file, context.merge(options))
|
69
69
|
end
|
70
70
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The cached_attachment_data plugin adds the ability to retain the cached
|
3
|
+
# The `cached_attachment_data` plugin adds the ability to retain the cached
|
4
4
|
# file across form redisplays, which means the file doesn't have to be
|
5
5
|
# reuploaded in case of validation errors.
|
6
6
|
#
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Shrine
|
2
|
+
module Plugins
|
3
|
+
# The `copy` plugin allows copying attachment from one record to another.
|
4
|
+
#
|
5
|
+
# plugin :copy
|
6
|
+
#
|
7
|
+
# It adds a `Attacher#copy` method, which accepts another attacher, and
|
8
|
+
# copies the attachment from it:
|
9
|
+
#
|
10
|
+
# photo.image_attacher.copy(other_photo.image_attacher)
|
11
|
+
#
|
12
|
+
# This method will automatically be called when the record is duplicated:
|
13
|
+
#
|
14
|
+
# duplicated_photo = photo.dup
|
15
|
+
# duplicated_photo.image #=> #<Shrine::UploadedFile>
|
16
|
+
# duplicated_photo.image != photo.image
|
17
|
+
module Copy
|
18
|
+
module AttachmentMethods
|
19
|
+
def initialize(*)
|
20
|
+
super
|
21
|
+
|
22
|
+
module_eval <<-RUBY
|
23
|
+
def initialize_copy(record)
|
24
|
+
super
|
25
|
+
@#{@name}_attacher = nil # reload the attacher
|
26
|
+
self.#{@name}_data = nil # remove original attachment
|
27
|
+
#{@name}_attacher.copy(record.#{@name}_attacher)
|
28
|
+
end
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module AttacherMethods
|
34
|
+
def copy(attacher)
|
35
|
+
options = {action: :copy, move: false}
|
36
|
+
|
37
|
+
if attacher.cached?
|
38
|
+
copied_attachment = cache!(attacher.get, **options)
|
39
|
+
elsif attacher.stored?
|
40
|
+
copied_attachment = store!(attacher.get, **options)
|
41
|
+
else
|
42
|
+
copied_attachment = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
set(copied_attachment)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
register_plugin(:copy, Copy)
|
51
|
+
end
|
52
|
+
end
|
@@ -3,7 +3,7 @@ require "stringio"
|
|
3
3
|
|
4
4
|
class Shrine
|
5
5
|
module Plugins
|
6
|
-
# The data_uri plugin enables you to upload files as [data URIs].
|
6
|
+
# The `data_uri` plugin enables you to upload files as [data URIs].
|
7
7
|
# This plugin is useful for example when using [HTML5 Canvas].
|
8
8
|
#
|
9
9
|
# plugin :data_uri
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The default_storage plugin enables you to change which storages are going
|
3
|
+
# The `default_storage` plugin enables you to change which storages are going
|
4
4
|
# to be used for this uploader's attacher (the default is `:cache` and
|
5
5
|
# `:store`).
|
6
6
|
#
|
@@ -8,7 +8,7 @@ class Shrine
|
|
8
8
|
#
|
9
9
|
# You can also pass a block and choose the values depending on the record
|
10
10
|
# values and the name of the attachment. This is useful if you're using the
|
11
|
-
# dynamic_storage plugin. Example:
|
11
|
+
# `dynamic_storage` plugin. Example:
|
12
12
|
#
|
13
13
|
# plugin :default_storage, store: ->(record, name) { :"store_#{record.username}" }
|
14
14
|
module DefaultStorage
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The default_url_options plugin allows you to specify URL options that
|
3
|
+
# The `default_url_options` plugin allows you to specify URL options that
|
4
4
|
# will be applied by default for uploaded files of specified storages.
|
5
5
|
#
|
6
6
|
# plugin :default_url_options, store: {download: true}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The delete_promoted plugin deletes files that have been promoted, after
|
3
|
+
# The `delete_promoted` plugin deletes files that have been promoted, after
|
4
4
|
# the record is saved. This means that cached files handled by the attacher
|
5
5
|
# will automatically get deleted once they're uploaded to store. This also
|
6
6
|
# applies to any other uploaded file passed to `Attacher#promote`.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The delete_raw plugin will automatically delete raw files that have been
|
3
|
+
# The `delete_raw` plugin will automatically delete raw files that have been
|
4
4
|
# uploaded. This is especially useful when doing processing, to ensure that
|
5
5
|
# temporary files have been deleted after upload.
|
6
6
|
#
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Shrine
|
2
2
|
module Plugins
|
3
|
-
# The determine_mime_type plugin stores the actual MIME type of the
|
3
|
+
# The `determine_mime_type` plugin stores the actual MIME type of the
|
4
4
|
# uploaded file.
|
5
5
|
#
|
6
6
|
# plugin :determine_mime_type
|
@@ -37,7 +37,8 @@ class Shrine
|
|
37
37
|
# "Content-Type" request header, which might not hold the actual MIME type
|
38
38
|
# of the file.
|
39
39
|
#
|
40
|
-
#
|
40
|
+
# Not all analyzers can recognize all types of files. For those cases you
|
41
|
+
# can build your own analyzer, where you can reuse built-in analyzers:
|
41
42
|
#
|
42
43
|
# plugin :determine_mime_type, analyzer: ->(io, analyzers) do
|
43
44
|
# analyzers[:mimemagic].call(io) || analyzers[:file].call(io)
|
@@ -3,8 +3,8 @@ require "json"
|
|
3
3
|
|
4
4
|
class Shrine
|
5
5
|
module Plugins
|
6
|
-
# The direct_upload plugin provides a
|
7
|
-
# uploading individual files asynchronously.
|
6
|
+
# The `direct_upload` plugin provides a Rack endpoint which can be used for
|
7
|
+
# uploading individual files asynchronously. It requires the [Roda] gem.
|
8
8
|
#
|
9
9
|
# plugin :direct_upload
|
10
10
|
#
|
@@ -27,7 +27,7 @@ class Shrine
|
|
27
27
|
# Now your application will get `POST /images/cache/upload` and `GET
|
28
28
|
# /images/cache/presign` routes. Whether you upload files to your app or to
|
29
29
|
# to a 3rd-party service, you'll probably want to use a JavaScript file
|
30
|
-
# upload library like [jQuery-File-Upload] or [
|
30
|
+
# upload library like [jQuery-File-Upload], [Dropzone] or [FineUploader].
|
31
31
|
#
|
32
32
|
# ## Uploads
|
33
33
|
#
|
@@ -89,21 +89,24 @@ class Shrine
|
|
89
89
|
#
|
90
90
|
# plugin :direct_upload, presign_location: ->(request) { "${filename}" }
|
91
91
|
#
|
92
|
-
# This presign route internally calls `#presign` on the storage,
|
93
|
-
#
|
94
|
-
# options per-request
|
92
|
+
# This presign route internally calls `#presign` on the storage, and many
|
93
|
+
# storages accept additional service-specific options. You can generate
|
94
|
+
# these additional options per-request through `:presign_options`:
|
95
95
|
#
|
96
96
|
# plugin :direct_upload, presign_options: {acl: "public-read"}
|
97
97
|
#
|
98
98
|
# plugin :direct_upload, presign_options: ->(request) do
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
99
|
+
# filename = request.params["filename"].inspect
|
100
|
+
#
|
101
|
+
# {
|
102
|
+
# content_length_range: 0..(10*1024*1024), # limit filesize to 10MB
|
103
|
+
# content_disposition: "attachment; filename=#{filename}", # download with original filename
|
104
|
+
# }
|
103
105
|
# end
|
104
106
|
#
|
105
107
|
# Both `:presign_location` and `:presign_options` in their block versions
|
106
|
-
# are yielded an instance of [
|
108
|
+
# are yielded an instance of [Roda request], which is a subclass of
|
109
|
+
# `Rack::Request`.
|
107
110
|
#
|
108
111
|
# See the [Direct Uploads to S3] guide for further instructions on how to
|
109
112
|
# hook the presigned uploads to a form.
|
@@ -117,27 +120,36 @@ class Shrine
|
|
117
120
|
#
|
118
121
|
# ## Allowed storages
|
119
122
|
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
+
# By default only uploads to `:cache` are allowed, to prevent the
|
124
|
+
# possibility of having orphan files in your main storage. But you can
|
125
|
+
# allow more storages:
|
123
126
|
#
|
124
127
|
# plugin :direct_upload, allowed_storages: [:cache, :store]
|
125
128
|
#
|
126
129
|
# ## Customizing endpoint
|
127
130
|
#
|
128
|
-
# Since the endpoint is a [Roda] app, it
|
129
|
-
#
|
131
|
+
# Since the endpoint is a [Roda] app, it is very customizable. For example,
|
132
|
+
# you can add a Rack middleware to change the response status and headers:
|
133
|
+
#
|
134
|
+
# class ShrineUploadMiddleware
|
135
|
+
# def initialize(app)
|
136
|
+
# @app = app
|
137
|
+
# end
|
130
138
|
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
# plugin :hooks
|
139
|
+
# def call(env)
|
140
|
+
# result = @app.call(env)
|
134
141
|
#
|
135
|
-
#
|
136
|
-
#
|
142
|
+
# if result[0] == 200 && env["PATH_INFO"].end_with?("upload")
|
143
|
+
# result[0] = 201
|
144
|
+
# result[1]["Location"] = Shrine.uploaded_file(result[2].first).url
|
137
145
|
# end
|
146
|
+
#
|
147
|
+
# result
|
138
148
|
# end
|
139
149
|
# end
|
140
150
|
#
|
151
|
+
# Shrine::UploadEndpoint.use ShrineUploadMiddleware
|
152
|
+
#
|
141
153
|
# Upon subclassing uploader the upload endpoint is also subclassed. You can
|
142
154
|
# also call the plugin again in an uploader subclass to change its
|
143
155
|
# configuration.
|
@@ -145,9 +157,8 @@ class Shrine
|
|
145
157
|
# [Roda]: https://github.com/jeremyevans/roda
|
146
158
|
# [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
|
147
159
|
# [Dropzone]: https://github.com/enyo/dropzone
|
148
|
-
# [
|
149
|
-
# [
|
150
|
-
# [`Roda::RodaRequest`]: http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Base/RequestMethods.html
|
160
|
+
# [FineUploader]: https://github.com/FineUploader/fine-uploader
|
161
|
+
# [Roda request]: http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Base/RequestMethods.html
|
151
162
|
# [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
|
152
163
|
module DirectUpload
|
153
164
|
def self.load_dependencies(uploader, *)
|
@@ -202,6 +213,7 @@ class Shrine
|
|
202
213
|
options = get_presign_options
|
203
214
|
|
204
215
|
presign_data = generate_presign(location, options)
|
216
|
+
response.headers["Cache-Control"] = "no-store"
|
205
217
|
|
206
218
|
json presign_data
|
207
219
|
end
|