shrine 1.2.0 → 1.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/LICENSE.txt +1 -1
- data/README.md +12 -9
- data/doc/carrierwave.md +111 -19
- data/doc/changing_location.md +5 -19
- data/doc/creating_storages.md +22 -1
- data/doc/direct_s3.md +3 -5
- data/doc/paperclip.md +117 -4
- data/doc/refile.md +61 -0
- data/lib/shrine.rb +42 -34
- data/lib/shrine/plugins/activerecord.rb +4 -4
- data/lib/shrine/plugins/backup.rb +9 -4
- data/lib/shrine/plugins/data_uri.rb +1 -1
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/determine_mime_type.rb +5 -2
- data/lib/shrine/plugins/direct_upload.rb +3 -3
- data/lib/shrine/plugins/keep_location.rb +4 -1
- data/lib/shrine/plugins/migration_helpers.rb +32 -0
- data/lib/shrine/plugins/moving.rb +6 -6
- data/lib/shrine/plugins/multi_delete.rb +1 -1
- data/lib/shrine/plugins/pretty_location.rb +28 -2
- data/lib/shrine/plugins/remote_url.rb +38 -17
- data/lib/shrine/plugins/sequel.rb +1 -1
- data/lib/shrine/plugins/store_dimensions.rb +3 -1
- data/lib/shrine/plugins/versions.rb +0 -1
- data/lib/shrine/storage/s3.rb +7 -4
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +2 -3
- metadata +9 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d59731ea1e8a0376a6f8ad86e8d627250dec2f44
|
4
|
+
data.tar.gz: 14bd2696930a028e157cc1535995a5342958170b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0ca4170b242be2e01fa6abdeb9ff0e3380e1271c0783099e738182e4d6f995ac7b8042884c52147052a751b6a7143eb16766879165c87f221ed6e84b7deb53a
|
7
|
+
data.tar.gz: 6aee5bb9a3a44f162c9d2da791c498c2d592a05aa3efc7bf058d38def80040af8856db96431eb558ed93538cc327f718874f59a376e3b55e11778189c87401be
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -18,7 +18,7 @@ explains the motivation behind Shrine.
|
|
18
18
|
gem "shrine"
|
19
19
|
```
|
20
20
|
|
21
|
-
Shrine has been tested on MRI 2.1, MRI 2.2, JRuby and Rubinius.
|
21
|
+
Shrine has been tested on MRI 2.1, MRI 2.2, MRI 2.3, JRuby and Rubinius.
|
22
22
|
|
23
23
|
## Basics
|
24
24
|
|
@@ -47,8 +47,8 @@ First we add the storage we want to use to Shrine's registry. Storages are
|
|
47
47
|
simple Ruby classes which perform the actual uploads. We instantiate a `Shrine`
|
48
48
|
with the storage name, and when we call `#upload` Shrine does the following:
|
49
49
|
|
50
|
-
* generates a unique location for the file
|
51
50
|
* extracts metadata from the file
|
51
|
+
* generates a unique location for the file
|
52
52
|
* uploads the file using the underlying storage
|
53
53
|
* closes the file
|
54
54
|
* returns a `Shrine::UploadedFile` with relevant data
|
@@ -78,8 +78,8 @@ To read about the metadata that is stored with the uploaded file, see the
|
|
78
78
|
## Attachment
|
79
79
|
|
80
80
|
In web applications, instead of managing files directly, we rather want to
|
81
|
-
treat them as "attachments" to
|
82
|
-
do this by generating and including "attachment" modules.
|
81
|
+
treat them as "attachments" to records and tie them to their lifecycle. In
|
82
|
+
Shrine we do this by generating and including "attachment" modules.
|
83
83
|
|
84
84
|
Firstly we need to assign the special `:cache` and `:store` storages:
|
85
85
|
|
@@ -245,7 +245,8 @@ assigned and saved, an "upload" actually happens two times. First the file is
|
|
245
245
|
"uploaded" to cache on assignment, and then the cached file is reuploaded to
|
246
246
|
store on save. You could theoretically do processing in both phases, depending
|
247
247
|
on your preferences (although it's generally not recommended to process on
|
248
|
-
caching
|
248
|
+
caching, because it happens before file validations; use the `recache` plugin
|
249
|
+
instead).
|
249
250
|
|
250
251
|
Ok, now how do we do the actual processing? Well, Shrine actually doesn't ship
|
251
252
|
with any file processing functionality, because that is a generic problem that
|
@@ -477,9 +478,11 @@ end
|
|
477
478
|
|
478
479
|
Note that there should always be a random component in the location, otherwise
|
479
480
|
dirty tracking won't be detected properly (you can use `Shrine#generate_uid`).
|
481
|
+
Also note that you can access the extracted metadata here through
|
482
|
+
`context[:metadata]`.
|
480
483
|
|
481
|
-
When using
|
482
|
-
`:location
|
484
|
+
When using the uploader directly, it's possible to bypass `#generate_location`
|
485
|
+
by passing in `:location`:
|
483
486
|
|
484
487
|
```rb
|
485
488
|
file = File.open("avatar.jpg")
|
@@ -527,7 +530,7 @@ and for FileSystem you can put something like this in your Rake task:
|
|
527
530
|
|
528
531
|
```rb
|
529
532
|
file_system = Shrine.storages[:cache]
|
530
|
-
file_system.clear!(older_than:
|
533
|
+
file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
|
531
534
|
```
|
532
535
|
|
533
536
|
## Background jobs
|
@@ -569,7 +572,7 @@ libraries are:
|
|
569
572
|
* **User experience** – After starting the background job, Shrine will save the
|
570
573
|
record with the cached attachment so that it can be immediately shown to the
|
571
574
|
user. With other file upload libraries users cannot see the file until the
|
572
|
-
background job has finished
|
575
|
+
background job has finished.
|
573
576
|
* **Simplicity** – Instead of writing the workers for you, Shrine allows you
|
574
577
|
to use your own workers in a very simple way. Also, no extra columns are
|
575
578
|
required.
|
data/doc/carrierwave.md
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
# Shrine for CarrierWave Users
|
2
2
|
|
3
|
-
This guide is aimed at helping CarrierWave users transition to Shrine.
|
4
|
-
|
5
|
-
|
6
|
-
Shrine.
|
3
|
+
This guide is aimed at helping CarrierWave users transition to Shrine. First it
|
4
|
+
explains some key differences in the design between the two libraries.
|
5
|
+
Afterwards it explains how you can transition an existing app that uses
|
6
|
+
CarrierWave to Shrine. It then finishes off with an extensive reference of
|
7
|
+
CarrierWave's interface and what is the equivalent in Shrine.
|
7
8
|
|
8
9
|
## Uploaders
|
9
10
|
|
10
|
-
Shrine has a concept of uploaders similar to CarrierWave's,
|
11
|
-
|
12
|
-
directly:
|
11
|
+
Shrine has a concept of uploaders similar to CarrierWave's, which allows you to
|
12
|
+
have different uploading logic for different types of files:
|
13
13
|
|
14
14
|
```rb
|
15
15
|
class ImageUploader < Shrine
|
16
|
-
#
|
16
|
+
# uploading logic
|
17
17
|
end
|
18
18
|
```
|
19
19
|
|
20
20
|
While in CarrierWave you choose a storages for uploaders directly, in Shrine
|
21
|
-
you first register storages globally (under a symbol name), and then you
|
21
|
+
you first register storages globally (under a symbol name), and then you can
|
22
22
|
instantiate uploaders with a specific storage.
|
23
23
|
|
24
24
|
```rb
|
@@ -35,15 +35,15 @@ store_uploader = Shrine.new(:store)
|
|
35
35
|
```
|
36
36
|
|
37
37
|
CarrierWave uses symbols for referencing storages (`:file`, `:fog`, ...), but
|
38
|
-
in Shrine
|
39
|
-
flexible, because this way they can have their
|
40
|
-
them.
|
38
|
+
in Shrine storages are simple Ruby classes which you can instantiate directly.
|
39
|
+
This makes storages much more flexible, because this way they can have their
|
40
|
+
own options that are specific to them.
|
41
41
|
|
42
42
|
### Processing
|
43
43
|
|
44
|
-
In Shrine processing is done instance-level in the `#process` method
|
45
|
-
|
46
|
-
|
44
|
+
In Shrine processing is done instance-level in the `#process` method, and can
|
45
|
+
be specified for each phase. You can return a single processed file or a hash of
|
46
|
+
versions (with the `versions` plugin):
|
47
47
|
|
48
48
|
```rb
|
49
49
|
require "image_processing/mini_magick" # part of the "image_processing" gem
|
@@ -73,7 +73,7 @@ Shrine.plugin :activerecord # If you're using ActiveRecord
|
|
73
73
|
```
|
74
74
|
|
75
75
|
Instead of giving you class methods for "mounting" uploaders, in Shrine you
|
76
|
-
generate
|
76
|
+
generate attachment modules which you simply include in your models:
|
77
77
|
|
78
78
|
```rb
|
79
79
|
class User < Sequel::Model
|
@@ -82,8 +82,8 @@ end
|
|
82
82
|
```
|
83
83
|
|
84
84
|
You models are required to have the `<attachment>_data` column, in the above
|
85
|
-
case `avatar_data`.
|
86
|
-
|
85
|
+
case `avatar_data`. Shrine stores storage, location, and additional metadata of
|
86
|
+
the uploaded file to that column.
|
87
87
|
|
88
88
|
### Multiple uploads
|
89
89
|
|
@@ -94,6 +94,99 @@ you're using, and it's analogous to how you would implement adding items to any
|
|
94
94
|
dynamic one-to-many relationship. Take a look at the [example app] which
|
95
95
|
demonstrates how easy it is to implement multiple uploads.
|
96
96
|
|
97
|
+
## Migrating from CarrierWave
|
98
|
+
|
99
|
+
You have an existing app using CarrierWave and you want to transfer it to
|
100
|
+
Shrine. Let's assume we have a `Photo` model with the "image" attachment. First
|
101
|
+
we need to create the `image_data` column for Shrine:
|
102
|
+
|
103
|
+
```rb
|
104
|
+
add_column :photos, :image_data, :text
|
105
|
+
```
|
106
|
+
|
107
|
+
Afterwards we need to make new uploads write to the `image_data` column. This
|
108
|
+
can be done by including the below module to all models that have CarrierWave
|
109
|
+
attachments:
|
110
|
+
|
111
|
+
```rb
|
112
|
+
require "fastimage"
|
113
|
+
|
114
|
+
module CarrierwaveShrineSynchronization
|
115
|
+
def self.included(model)
|
116
|
+
model.before_save do
|
117
|
+
self.class.uploaders.each_key do |name|
|
118
|
+
write_shrine_data(name) if changes.key?(name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def write_shrine_data(name)
|
124
|
+
uploader = send(name)
|
125
|
+
|
126
|
+
if read_attribute(name).present?
|
127
|
+
data = uploader_to_shrine_data(uploader)
|
128
|
+
|
129
|
+
if uploader.versions.any?
|
130
|
+
data = {original: data}
|
131
|
+
uploader.versions.each do |name, version|
|
132
|
+
data[name] = uploader_to_shrine_data(version)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
write_attribute(:"#{name}_data", data.to_json)
|
137
|
+
else
|
138
|
+
write_attribute(:"#{name}_data", nil)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# If you'll be using `:prefix` on your Shrine storage, make sure to
|
145
|
+
# subtract it from the path assigned as `:id`.
|
146
|
+
def uploader_to_shrine_data(uploader)
|
147
|
+
path = uploader.store_path(read_attribute(uploader.mounted_as))
|
148
|
+
|
149
|
+
size = uploader.file.size if changes.key?(uploader.mounted_as)
|
150
|
+
size ||= FastImage.new(uploader.url).content_length
|
151
|
+
size ||= File.size(File.join(uploader.root, path))
|
152
|
+
filename = File.basename(path)
|
153
|
+
mime_type = MIME::Types.type_for(path).first.to_s.presence
|
154
|
+
|
155
|
+
{
|
156
|
+
storage: :store,
|
157
|
+
id: path,
|
158
|
+
metadata: {
|
159
|
+
size: size,
|
160
|
+
filename: filename,
|
161
|
+
mime_type: mime_type,
|
162
|
+
},
|
163
|
+
}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
```rb
|
168
|
+
class Photo < ActiveRecord::Base
|
169
|
+
mount_uploader :image, ImageUploader
|
170
|
+
include CarrierwaveShrineSynchronization # needs to be after `mount_uploader`
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
After you deploy this code, the `image_data` column should now be successfully
|
175
|
+
synchronized with new attachments. Next step is to run a script which writes
|
176
|
+
all existing CarrierWave attachments to `image_data`:
|
177
|
+
|
178
|
+
```rb
|
179
|
+
Photo.find_each do |photo|
|
180
|
+
Photo.uploaders.each_key { |name| photo.write_shrine_data(name) }
|
181
|
+
photo.save!
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
Now you should be able to rewrite your application so that it uses Shrine
|
186
|
+
instead of CarrierWave, using equivalent Shrine storages. For help with
|
187
|
+
translating the code from CarrierWave to Shrine, you can consult the reference
|
188
|
+
below.
|
189
|
+
|
97
190
|
## CarrierWave to Shrine direct mapping
|
98
191
|
|
99
192
|
### `CarrierWave::Uploader::Base`
|
@@ -107,7 +200,6 @@ plugin:
|
|
107
200
|
```rb
|
108
201
|
Shrine.storages[:foo] = Shrine::Storage::Foo.new(*args)
|
109
202
|
```
|
110
|
-
|
111
203
|
```rb
|
112
204
|
class ImageUploader
|
113
205
|
plugin :default_storage, store: :foo
|
data/doc/changing_location.md
CHANGED
@@ -4,26 +4,12 @@ You have a production app with already uploaded attachments. However, you've
|
|
4
4
|
realized that the existing store folder structure for attachments isn't working
|
5
5
|
for you.
|
6
6
|
|
7
|
-
The first step is to change the location
|
8
|
-
plugin
|
7
|
+
The first step is to change the location (by overriding `#generate_location` or
|
8
|
+
with the pretty_location plugin), and deploy that change. Attachments on old
|
9
|
+
locations will still continue to work properly.
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
```
|
13
|
-
|
14
|
-
Or by overriding `#generate_location`:
|
15
|
-
|
16
|
-
```rb
|
17
|
-
class MyUploader < Shrine
|
18
|
-
def generate_location(io, context)
|
19
|
-
"#{context[:record].class}/#{context[:record].id}/#{io.original_filename}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
```
|
23
|
-
|
24
|
-
After you've deployed this change, all existing attachments on old locations
|
25
|
-
will continue to work properly. The next step is to run a script that will
|
26
|
-
move those to new locations. The easiest way to do that is to reupload them:
|
11
|
+
The next step is to run a script that will move those to new locations. The
|
12
|
+
easiest way to do that is to reupload them, and afterwards delete them:
|
27
13
|
|
28
14
|
```rb
|
29
15
|
Shrine.plugin :migration_helpers # before the model is loaded
|
data/doc/creating_storages.md
CHANGED
@@ -69,10 +69,31 @@ def upload(io, id, metadata = {})
|
|
69
69
|
end
|
70
70
|
```
|
71
71
|
|
72
|
+
## Updating
|
73
|
+
|
74
|
+
If your storage supports updating data of existing files (e.g. some metadata),
|
75
|
+
the convention is to create an `#update` method:
|
76
|
+
|
77
|
+
```rb
|
78
|
+
class Shrine
|
79
|
+
module Storage
|
80
|
+
class MyStorage
|
81
|
+
# ...
|
82
|
+
|
83
|
+
def update(id, options = {})
|
84
|
+
# update data of the file
|
85
|
+
end
|
86
|
+
|
87
|
+
# ...
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
72
93
|
## Streaming
|
73
94
|
|
74
95
|
If your storage can stream files by yielding chunks, you can add an additional
|
75
|
-
`#stream` method:
|
96
|
+
`#stream` method (used by the `download_endpoint` plugin):
|
76
97
|
|
77
98
|
```rb
|
78
99
|
class Shrine
|
data/doc/direct_s3.md
CHANGED
@@ -39,7 +39,7 @@ Shrine's JSON representation of an uploaded file looks like this:
|
|
39
39
|
"id": "349234854924394", # requied
|
40
40
|
"storage": "cache", # required
|
41
41
|
"metadata": {
|
42
|
-
"size": 45461, #
|
42
|
+
"size": 45461, # optional
|
43
43
|
"filename": "foo.jpg", # optional
|
44
44
|
"mime_type": "image/jpeg", # optional
|
45
45
|
}
|
@@ -141,10 +141,8 @@ include the object key as a query param:
|
|
141
141
|
<%
|
142
142
|
cached_file = {
|
143
143
|
storage: "cache",
|
144
|
-
id: params[:key][/cache\/(.+)/, 1], # we have to remove the prefix part
|
145
|
-
metadata: {
|
146
|
-
size: Shrine.storages[:cache].bucket.object(params[:key]).size,
|
147
|
-
}
|
144
|
+
id: params[:key][/cache\/(.+)/, 1], # we have to remove the prefix part
|
145
|
+
metadata: {},
|
148
146
|
}
|
149
147
|
%>
|
150
148
|
|
data/doc/paperclip.md
CHANGED
@@ -103,10 +103,9 @@ class User < Sequel::Model
|
|
103
103
|
end
|
104
104
|
```
|
105
105
|
|
106
|
-
Unlike in Paperclip which requires you to have `<attachment>
|
107
|
-
|
108
|
-
|
109
|
-
`<attachment>_data` text column, and all information will be stored there.
|
106
|
+
Unlike in Paperclip which requires you to have 4 `<attachment>_*` columns, in
|
107
|
+
Shrine you only need to have an `<attachment>_data` text column, and all
|
108
|
+
information will be stored there (in the above case `avatar_data`).
|
110
109
|
|
111
110
|
The attachments use `:store` for storing the files, and `:cache` for caching.
|
112
111
|
The latter is something Paperclip doesn't do, but caching before storing is
|
@@ -179,6 +178,120 @@ class ImageUploader < Shrine
|
|
179
178
|
end
|
180
179
|
```
|
181
180
|
|
181
|
+
## Migrating from Paperclip
|
182
|
+
|
183
|
+
You have an existing app using Paperclip and you want to transfer it to Shrine.
|
184
|
+
First we need to make new uploads write to the `<attachment>_data` column.
|
185
|
+
Let's assume we have a `Photo` model with the "image" attachment:
|
186
|
+
|
187
|
+
```rb
|
188
|
+
add_column :photos, :image_data, :text
|
189
|
+
```
|
190
|
+
|
191
|
+
Afterwards we need to make new uploads write to the `image_data` column. This
|
192
|
+
can be done by including the below module to all models that have Paperclip
|
193
|
+
attachments:
|
194
|
+
|
195
|
+
```rb
|
196
|
+
require "fastimage"
|
197
|
+
|
198
|
+
module PaperclipShrineSynchronization
|
199
|
+
def self.included(model)
|
200
|
+
model.before_save do
|
201
|
+
Paperclip::AttachmentRegistry.each_definition do |klass, name, options|
|
202
|
+
write_shrine_data(name) if changes.key?(:"#{name}_file_name") && klass == self.class
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def write_shrine_data(name)
|
208
|
+
attachment = send(name)
|
209
|
+
|
210
|
+
if attachment.size.present?
|
211
|
+
data = attachment_to_shrine_data(attachment)
|
212
|
+
|
213
|
+
if attachment.styles.any?
|
214
|
+
data = {original: data}
|
215
|
+
attachment.styles.each do |name, style|
|
216
|
+
data[name] = style_to_shrine_data(style)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
write_attribute(:"#{name}_data", data.to_json)
|
221
|
+
else
|
222
|
+
write_attribute(:"#{name}_data", nil)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
# If you'll be using a `:prefix` on your Shrine storage, or you're storing
|
229
|
+
# files on the filesystem, make sure to subtract the appropriate part
|
230
|
+
# from the path assigned to `:id`.
|
231
|
+
def attachment_to_shrine_data(attachment)
|
232
|
+
{
|
233
|
+
storage: :store,
|
234
|
+
id: attachment.path,
|
235
|
+
metadata: {
|
236
|
+
size: attachment.size,
|
237
|
+
filename: attachment.original_filename,
|
238
|
+
content_type: attachment.content_type,
|
239
|
+
},
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
# If you'll be using a `:prefix` on your Shrine storage, or you're storing
|
244
|
+
# files on the filesystem, make sure to subtract the appropriate part
|
245
|
+
# from the path assigned to `:id`.
|
246
|
+
def style_to_shrine_data(style)
|
247
|
+
attachment = style.attachment
|
248
|
+
path = attachment.path(style.name)
|
249
|
+
url = attachment.url(style.name)
|
250
|
+
file = attachment.instance_variable_get("@queued_for_write")[style.name]
|
251
|
+
|
252
|
+
size = file.size if file
|
253
|
+
size ||= FastImage.new(url).content_length
|
254
|
+
size ||= File.size(path)
|
255
|
+
filename = File.basename(path)
|
256
|
+
mime_type = MIME::Types.type_for(path).first.to_s.presence
|
257
|
+
|
258
|
+
{
|
259
|
+
storage: :store,
|
260
|
+
id: path,
|
261
|
+
metadata: {
|
262
|
+
size: size,
|
263
|
+
filename: filename,
|
264
|
+
mime_type: mime_type,
|
265
|
+
}
|
266
|
+
}
|
267
|
+
end
|
268
|
+
end
|
269
|
+
```
|
270
|
+
```rb
|
271
|
+
class Photo < ActiveRecord::Base
|
272
|
+
has_attached_file :image
|
273
|
+
include PaperclipShrineSynchronization # needs to be after `has_attached_file`
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
277
|
+
After you deploy this code, the `image_data` column should now be successfully
|
278
|
+
synchronized with new attachments. Next step is to run a script which writes
|
279
|
+
all existing CarrierWave attachments to `image_data`:
|
280
|
+
|
281
|
+
```rb
|
282
|
+
Photo.find_each do |photo|
|
283
|
+
Paperclip::AttachmentRegistry.each_definition do |klass, name, options|
|
284
|
+
photo.write_shrine_data(name) if klass == Photo
|
285
|
+
end
|
286
|
+
photo.save!
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
Now you should be able to rewrite your application so that it uses Shrine
|
291
|
+
instead of Paperclip, using equivalent Shrine storages. For help with
|
292
|
+
translating the code from Paperclip to Shrine, you can consult the reference
|
293
|
+
below.
|
294
|
+
|
182
295
|
## Paperclip to Shrine direct mapping
|
183
296
|
|
184
297
|
### `has_attached_file`
|
data/doc/refile.md
CHANGED
@@ -188,6 +188,67 @@ Shrine doesn't have a built-in solution for accepting multiple uploads, but
|
|
188
188
|
it's actually very easy to do manually, see the [example app] on how you can do
|
189
189
|
multiple uploads directly to S3.
|
190
190
|
|
191
|
+
## Migrating from Refile
|
192
|
+
|
193
|
+
You have an existing app using Refile and you want to transfer it to
|
194
|
+
Shrine. Let's assume we have a `Photo` model with the "image" attachment. First
|
195
|
+
we need to create the `image_data` column for Shrine:
|
196
|
+
|
197
|
+
```rb
|
198
|
+
add_column :photos, :image_data, :text
|
199
|
+
```
|
200
|
+
|
201
|
+
Afterwards we need to make new uploads write to the `image_data` column. This
|
202
|
+
can be done by including the below module to all models that have Refile
|
203
|
+
attachments:
|
204
|
+
|
205
|
+
```rb
|
206
|
+
module RefileShrineSynchronization
|
207
|
+
def write_shrine_data(name)
|
208
|
+
if read_attribute("#{name}_id").present?
|
209
|
+
data = {
|
210
|
+
storage: :store,
|
211
|
+
id: send("#{name}_id"),
|
212
|
+
metadata: {
|
213
|
+
size: (send("#{name}_size") if respond_to?("#{name}_size")),
|
214
|
+
filename: (send("#{name}_filename") if respond_to?("#{name}_filename")),
|
215
|
+
mime_type: (send("#{name}_content_type") if respond_to?("#{name}_content_type")),
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
write_attribute(:"#{name}_data", data.to_json)
|
220
|
+
else
|
221
|
+
write_attribute(:"#{name}_data", nil)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
```
|
226
|
+
```rb
|
227
|
+
class Photo < ActiveRecord::Base
|
228
|
+
attachment :image
|
229
|
+
include RefileShrineSynchronization
|
230
|
+
|
231
|
+
before_save do
|
232
|
+
write_shrine_data(:image) if changes.key?(:image_id)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
After you deploy this code, the `image_data` column should now be successfully
|
238
|
+
synchronized with new attachments. Next step is to run a script which writes
|
239
|
+
all existing Refile attachments to `image_data`:
|
240
|
+
|
241
|
+
```rb
|
242
|
+
Photo.find_each do |photo|
|
243
|
+
photo.write_shrine_data(:image)
|
244
|
+
photo.save!
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
Now you should be able to rewrite your application so that it uses Shrine
|
249
|
+
instead of Refile, using equivalent Shrine storages. For help with translating
|
250
|
+
the code from Refile to Shrine, you can consult the reference below.
|
251
|
+
|
191
252
|
## Refile to Shrine direct mapping
|
192
253
|
|
193
254
|
### `Refile`
|
data/lib/shrine.rb
CHANGED
@@ -258,10 +258,11 @@ class Shrine
|
|
258
258
|
# Generates a unique location for the uploaded file, and preserves an
|
259
259
|
# optional extension.
|
260
260
|
def generate_location(io, context = {})
|
261
|
-
extension
|
261
|
+
extension = ".#{io.extension}" if io.is_a?(UploadedFile) && io.extension
|
262
|
+
extension ||= File.extname(extract_filename(io).to_s)
|
262
263
|
basename = generate_uid(io)
|
263
264
|
|
264
|
-
basename + extension
|
265
|
+
basename + extension.to_s
|
265
266
|
end
|
266
267
|
|
267
268
|
# Extracts filename, size and MIME type from the file, which is later
|
@@ -310,15 +311,15 @@ class Shrine
|
|
310
311
|
# the metadata, stores the file, and returns a Shrine::UploadedFile.
|
311
312
|
def _store(io, context)
|
312
313
|
_enforce_io(io)
|
313
|
-
|
314
|
-
|
314
|
+
metadata = get_metadata(io, context)
|
315
|
+
location = get_location(io, context.merge(metadata: metadata))
|
315
316
|
|
316
|
-
put(io, context)
|
317
|
+
put(io, context.merge(location: location, metadata: metadata))
|
317
318
|
|
318
319
|
self.class::UploadedFile.new(
|
319
|
-
"id" =>
|
320
|
+
"id" => location,
|
320
321
|
"storage" => storage_key.to_s,
|
321
|
-
"metadata" =>
|
322
|
+
"metadata" => metadata,
|
322
323
|
)
|
323
324
|
end
|
324
325
|
|
@@ -352,7 +353,7 @@ class Shrine
|
|
352
353
|
# Retrieves the location for the given io and context. First it looks
|
353
354
|
# for the `:location` option, otherwise it calls #generate_location.
|
354
355
|
def get_location(io, context)
|
355
|
-
generate_location(io, context)
|
356
|
+
context[:location] || generate_location(io, context)
|
356
357
|
end
|
357
358
|
|
358
359
|
# Copies the metadata over from an UploadedFile or calls
|
@@ -527,7 +528,7 @@ class Shrine
|
|
527
528
|
# stored file.
|
528
529
|
def promote(cached_file)
|
529
530
|
stored_file = store!(cached_file, phase: :store)
|
530
|
-
|
531
|
+
result = swap(stored_file) or delete!(stored_file, phase: :stored)
|
531
532
|
result
|
532
533
|
end
|
533
534
|
|
@@ -562,6 +563,7 @@ class Shrine
|
|
562
563
|
instance_exec(&validate_block) if validate_block && get
|
563
564
|
end
|
564
565
|
|
566
|
+
# Delegates to `Shrine.uploaded_file`.
|
565
567
|
def uploaded_file(*args, &block)
|
566
568
|
shrine_class.uploaded_file(*args, &block)
|
567
569
|
end
|
@@ -585,7 +587,7 @@ class Shrine
|
|
585
587
|
uploaded_file && cache.uploaded?(uploaded_file)
|
586
588
|
end
|
587
589
|
|
588
|
-
#
|
590
|
+
# Calls #update, overriden in ORM plugins.
|
589
591
|
def swap(uploaded_file)
|
590
592
|
update(uploaded_file)
|
591
593
|
uploaded_file
|
@@ -642,7 +644,7 @@ class Shrine
|
|
642
644
|
# The context that's sent to Shrine on upload and delete. It holds the
|
643
645
|
# record and the name of the attachment.
|
644
646
|
def context
|
645
|
-
|
647
|
+
{name: name, record: record}
|
646
648
|
end
|
647
649
|
end
|
648
650
|
|
@@ -659,46 +661,50 @@ class Shrine
|
|
659
661
|
end
|
660
662
|
|
661
663
|
module FileMethods
|
662
|
-
# The ID of the uploaded file, which holds the location of the actual
|
663
|
-
# file on the storage
|
664
|
-
attr_reader :id
|
665
|
-
|
666
|
-
# The storage key as a string.
|
667
|
-
attr_reader :storage_key
|
668
|
-
|
669
|
-
# A hash of metadata, returned from `Shrine#extract_metadata`.
|
670
|
-
attr_reader :metadata
|
671
|
-
|
672
664
|
# The entire data hash which identifies this uploaded file.
|
673
665
|
attr_reader :data
|
674
666
|
|
675
667
|
def initialize(data)
|
676
|
-
@data
|
677
|
-
@
|
678
|
-
@storage_key = data.fetch("storage")
|
679
|
-
@metadata = data.fetch("metadata")
|
680
|
-
|
668
|
+
@data = data
|
669
|
+
@data["metadata"] ||= {}
|
681
670
|
storage # ensure storage exists
|
682
671
|
end
|
683
672
|
|
673
|
+
# The ID of the uploaded file, which holds the location of the actual
|
674
|
+
# file on the storage
|
675
|
+
def id
|
676
|
+
@data.fetch("id")
|
677
|
+
end
|
678
|
+
|
679
|
+
# The storage key as a string.
|
680
|
+
def storage_key
|
681
|
+
@data.fetch("storage")
|
682
|
+
end
|
683
|
+
|
684
|
+
# A hash of metadata.
|
685
|
+
def metadata
|
686
|
+
@data.fetch("metadata")
|
687
|
+
end
|
688
|
+
|
684
689
|
# The filename that was extracted from the original file.
|
685
690
|
def original_filename
|
686
|
-
metadata
|
691
|
+
metadata["filename"]
|
687
692
|
end
|
688
693
|
|
689
|
-
# The extension derived from
|
694
|
+
# The extension derived from #id if present, otherwise from
|
695
|
+
# #original_filename.
|
690
696
|
def extension
|
691
697
|
File.extname(id)[1..-1] || File.extname(original_filename.to_s)[1..-1]
|
692
698
|
end
|
693
699
|
|
694
700
|
# The filesize of the original file.
|
695
701
|
def size
|
696
|
-
Integer(metadata
|
702
|
+
Integer(metadata["size"]) if metadata["size"]
|
697
703
|
end
|
698
704
|
|
699
705
|
# The MIME type of the original file.
|
700
706
|
def mime_type
|
701
|
-
metadata
|
707
|
+
metadata["mime_type"]
|
702
708
|
end
|
703
709
|
alias content_type mime_type
|
704
710
|
|
@@ -717,8 +723,10 @@ class Shrine
|
|
717
723
|
# Part of Shrine::UploadedFile's complying to the IO interface. It
|
718
724
|
# delegates to the internally downloaded file.
|
719
725
|
def close
|
720
|
-
io
|
721
|
-
|
726
|
+
if @io
|
727
|
+
io.close
|
728
|
+
io.delete if io.class.name == "Tempfile"
|
729
|
+
end
|
722
730
|
end
|
723
731
|
|
724
732
|
# Part of Shrine::UploadedFile's complying to the IO interface. It
|
@@ -783,12 +791,12 @@ class Shrine
|
|
783
791
|
|
784
792
|
# The instance of `Shrine` with the corresponding storage.
|
785
793
|
def uploader
|
786
|
-
|
794
|
+
shrine_class.new(storage_key)
|
787
795
|
end
|
788
796
|
|
789
797
|
# The storage class this file was uploaded to.
|
790
798
|
def storage
|
791
|
-
|
799
|
+
shrine_class.find_storage(storage_key)
|
792
800
|
end
|
793
801
|
|
794
802
|
# Returns the Shrine class related to this uploaded file.
|
@@ -28,9 +28,9 @@ class Shrine
|
|
28
28
|
# If you want to put some parts of this lifecycle into a background job, see
|
29
29
|
# the backgrounding plugin.
|
30
30
|
#
|
31
|
-
# Additionally, any Shrine validation errors will added to
|
32
|
-
# errors upon validation.
|
33
|
-
# attachment, you can do it directly on the model.
|
31
|
+
# Additionally, any Shrine validation errors will be added to
|
32
|
+
# ActiveRecord's errors upon validation. If you want to validate presence
|
33
|
+
# of the attachment, you can do it directly on the model.
|
34
34
|
#
|
35
35
|
# class User < ActiveRecord::Base
|
36
36
|
# include ImageUploader[:avatar]
|
@@ -80,7 +80,7 @@ class Shrine
|
|
80
80
|
break if record.send("#{name}_data") != record.reload.send("#{name}_data")
|
81
81
|
super
|
82
82
|
end
|
83
|
-
rescue ActiveRecord::RecordNotFound
|
83
|
+
rescue ::ActiveRecord::RecordNotFound
|
84
84
|
end
|
85
85
|
|
86
86
|
# We save the record after updating, raising any validation errors.
|
@@ -30,20 +30,25 @@ class Shrine
|
|
30
30
|
|
31
31
|
module AttacherMethods
|
32
32
|
def backup_file(uploaded_file)
|
33
|
-
uploaded_file(uploaded_file)
|
34
|
-
|
33
|
+
uploaded_file(uploaded_file.to_json) do |file|
|
34
|
+
file.data["storage"] = backup_storage.to_s
|
35
|
+
end
|
35
36
|
end
|
36
37
|
|
37
38
|
private
|
38
39
|
|
39
40
|
# Back up the stored file and return it.
|
40
41
|
def store!(io, phase:)
|
41
|
-
|
42
|
+
stored_file = super
|
43
|
+
store_backup!(stored_file)
|
44
|
+
stored_file
|
42
45
|
end
|
43
46
|
|
44
47
|
# Delete the backed up file unless `:delete` was set to false.
|
45
48
|
def delete!(uploaded_file, phase:)
|
46
|
-
|
49
|
+
deleted_file = super
|
50
|
+
delete_backup!(deleted_file) if backup_delete?
|
51
|
+
deleted_file
|
47
52
|
end
|
48
53
|
|
49
54
|
# Upload the stored file to the backup storage.
|
@@ -107,13 +107,16 @@ class Shrine
|
|
107
107
|
# Uses the ruby-filemagic gem to magically extract the MIME type.
|
108
108
|
def _extract_mime_type_with_filemagic(io)
|
109
109
|
filemagic = FileMagic.new(FileMagic::MAGIC_MIME_TYPE)
|
110
|
-
data = io.read(MAGIC_NUMBER)
|
110
|
+
data = io.read(MAGIC_NUMBER)
|
111
|
+
io.rewind
|
111
112
|
filemagic.buffer(data)
|
112
113
|
end
|
113
114
|
|
114
115
|
# Uses the mimemagic gem to extract the MIME type.
|
115
116
|
def _extract_mime_type_with_mimemagic(io)
|
116
|
-
MimeMagic.by_magic(io).type
|
117
|
+
result = MimeMagic.by_magic(io).type
|
118
|
+
io.rewind
|
119
|
+
result
|
117
120
|
end
|
118
121
|
|
119
122
|
# Uses the mime-types gem to determine MIME type from file extension.
|
@@ -55,9 +55,9 @@ class Shrine
|
|
55
55
|
#
|
56
56
|
# plugin :direct_upload, presign: true
|
57
57
|
#
|
58
|
-
# This will disable the default `POST
|
59
|
-
#
|
60
|
-
#
|
58
|
+
# This will add `GET /:storage/presign`, and disable the default `POST
|
59
|
+
# /:storage/:name` (for security reasons) The response for that request
|
60
|
+
# looks something like this:
|
61
61
|
#
|
62
62
|
# {
|
63
63
|
# "url" => "https://my-bucket.s3-eu-west-1.amazonaws.com",
|
@@ -1,3 +1,6 @@
|
|
1
|
+
warn "The keep_location Shrine plugin is deprecated and will be removed in Shrine 2. " \
|
2
|
+
"You can easily implement the same behaviour in Shrine#generate_location."
|
3
|
+
|
1
4
|
class Shrine
|
2
5
|
module Plugins
|
3
6
|
# The keep_location plugin allows you to preserve locations when
|
@@ -22,7 +25,7 @@ class Shrine
|
|
22
25
|
private
|
23
26
|
|
24
27
|
def get_location(io, context)
|
25
|
-
if io.is_a?(UploadedFile) && keep_location?(io)
|
28
|
+
if !context[:location] && io.is_a?(UploadedFile) && keep_location?(io)
|
26
29
|
io.id
|
27
30
|
else
|
28
31
|
super
|
@@ -5,6 +5,8 @@ class Shrine
|
|
5
5
|
#
|
6
6
|
# plugin :migration_helpers
|
7
7
|
#
|
8
|
+
# ## `<attachment>_cache` and `<attachment>_store`
|
9
|
+
#
|
8
10
|
# If your attachment's name is "avatar", the model will get `#avatar_cache`
|
9
11
|
# and `#avatar_store` methods.
|
10
12
|
#
|
@@ -12,6 +14,16 @@ class Shrine
|
|
12
14
|
# user.avatar_cache #=> #<Shrine @storage_key=:cache @storage=#<Shrine::Storage::FileSystem @directory=public/uploads>>
|
13
15
|
# user.avatar_store #=> #<Shrine @storage_key=:store @storage=#<Shrine::Storage::S3:0x007fb8343397c8 @bucket=#<Aws::S3::Bucket name="foo">>>
|
14
16
|
#
|
17
|
+
# ## `<attachment>_cached?` and `<attachment>_stored?`
|
18
|
+
#
|
19
|
+
# You can use these methods to check whether attachment exists and is
|
20
|
+
# cached/stored:
|
21
|
+
#
|
22
|
+
# user.avatar_cached? # user.avatar && user.avatar_cache.uploaded?(user.avatar)
|
23
|
+
# user.avatar_stored? # user.avatar && user.avatar_store.uploaded?(user.avatar)
|
24
|
+
#
|
25
|
+
# ## `update_<attachment>`
|
26
|
+
#
|
15
27
|
# The model will also get `#update_avatar` method, which can be used when
|
16
28
|
# doing attachment migrations. It will update the record's attachment with
|
17
29
|
# the result of the passed in block.
|
@@ -41,6 +53,14 @@ class Shrine
|
|
41
53
|
def #{name}_store
|
42
54
|
#{name}_attacher.store
|
43
55
|
end
|
56
|
+
|
57
|
+
def #{name}_cached?
|
58
|
+
#{name}_attacher.cached?
|
59
|
+
end
|
60
|
+
|
61
|
+
def #{name}_stored?
|
62
|
+
#{name}_attacher.stored?
|
63
|
+
end
|
44
64
|
RUBY
|
45
65
|
end
|
46
66
|
end
|
@@ -53,6 +73,18 @@ class Shrine
|
|
53
73
|
new_attachment = block.call(get)
|
54
74
|
swap(new_attachment)
|
55
75
|
end
|
76
|
+
|
77
|
+
# Returns true if the attachment is present and is uploaded by the
|
78
|
+
# temporary storage.
|
79
|
+
def cached?
|
80
|
+
get && cache.uploaded?(get)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns true if the attachment is present and is uploaded by the
|
84
|
+
# permanent storage.
|
85
|
+
def stored?
|
86
|
+
get && store.uploaded?(get)
|
87
|
+
end
|
56
88
|
end
|
57
89
|
end
|
58
90
|
|
@@ -17,12 +17,12 @@ class Shrine
|
|
17
17
|
#
|
18
18
|
# plugin :moving, storages: [:cache, :store]
|
19
19
|
#
|
20
|
-
# What exactly means "moving"?
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
20
|
+
# What exactly means "moving"? If both the file being uploaded and the
|
21
|
+
# destination are on the filesystem, a `mv` command will be executed
|
22
|
+
# (making the transfer instantaneous). Some other storages may implement
|
23
|
+
# moving as well, usually only for files which are on the same storage.
|
24
|
+
# If moving isn't implemented by the storage, the file will be simply
|
25
|
+
# deleted after upload.
|
26
26
|
module Moving
|
27
27
|
def self.configure(uploader, storages:)
|
28
28
|
uploader.opts[:move_files_to_storages] = storages
|
@@ -11,11 +11,28 @@ class Shrine
|
|
11
11
|
#
|
12
12
|
# "user/564/avatar/thumb-493g82jf23.jpg"
|
13
13
|
# # :model/:id/:attachment/:version-:uid.:extension
|
14
|
+
#
|
15
|
+
# By default if a record class is inside a namespace, only the "inner"
|
16
|
+
# class name is used in the location. If you want to include the namespace,
|
17
|
+
# you can pass in the `:namespace` option with the desired separator as the
|
18
|
+
# value:
|
19
|
+
#
|
20
|
+
# plugin :pretty_location, namespace: "_"
|
21
|
+
# # "blog_user/.../493g82jf23.jpg"
|
22
|
+
#
|
23
|
+
# plugin :pretty_location, namespace: "/"
|
24
|
+
# # "blog/user/.../493g82jf23.jpg"
|
14
25
|
module PrettyLocation
|
26
|
+
def self.configure(uploader, namespace: nil)
|
27
|
+
uploader.opts[:pretty_location_namespace] = namespace
|
28
|
+
end
|
29
|
+
|
15
30
|
module InstanceMethods
|
16
31
|
def generate_location(io, context)
|
17
|
-
|
18
|
-
|
32
|
+
if context[:record]
|
33
|
+
type = class_location(context[:record].class) if context[:record].class.name
|
34
|
+
id = context[:record].id if context[:record].respond_to?(:id)
|
35
|
+
end
|
19
36
|
name = context[:name]
|
20
37
|
|
21
38
|
dirname, slash, basename = super.rpartition("/")
|
@@ -30,6 +47,15 @@ class Shrine
|
|
30
47
|
def generate_uid(io)
|
31
48
|
SecureRandom.hex(5)
|
32
49
|
end
|
50
|
+
|
51
|
+
def class_location(klass)
|
52
|
+
parts = klass.name.downcase.split("::")
|
53
|
+
if separator = opts[:pretty_location_namespace]
|
54
|
+
parts.join(separator)
|
55
|
+
else
|
56
|
+
parts.last
|
57
|
+
end
|
58
|
+
end
|
33
59
|
end
|
34
60
|
end
|
35
61
|
|
@@ -29,34 +29,38 @@ class Shrine
|
|
29
29
|
#
|
30
30
|
# plugin :remote_url, max_size: nil
|
31
31
|
#
|
32
|
-
# If download fails, either because the remote file wasn't found, was too
|
33
|
-
# large, or the request redirected, an error will be added to the
|
34
|
-
# attachment. You can change the default error message:
|
35
|
-
#
|
36
|
-
# plugin :remote_url, error_message: "download failed"
|
37
|
-
# plugin :remote_url, error_message: ->(url) { I18n.t("errors.download_failed") }
|
38
|
-
#
|
39
32
|
# Finally, if for some reason the way the file is downloaded doesn't suit
|
40
33
|
# your needs, you can provide a custom downloader:
|
41
34
|
#
|
42
|
-
# plugin :remote_url, downloader: ->(url) do
|
35
|
+
# plugin :remote_url, downloader: ->(url, max_size:) do
|
43
36
|
# request = RestClient::Request.new(method: :get, url: url, raw_response: true)
|
44
37
|
# response = request.execute
|
45
38
|
# response.file
|
46
39
|
# end
|
40
|
+
#
|
41
|
+
# If download errors, the error is rescued and a validation error is added
|
42
|
+
# equal to the error message. You can change the default error message:
|
43
|
+
#
|
44
|
+
# plugin :remote_url, error_message: "download failed"
|
45
|
+
# plugin :remote_url, error_message: ->(url) { I18n.t("errors.download_failed") }
|
46
|
+
#
|
47
|
+
# If you need the error instance for generating the error message, passing
|
48
|
+
# the `:include_error` option will additionally yield the error to the
|
49
|
+
# block:
|
50
|
+
#
|
51
|
+
# plugin :remote_url, include_error: true, error_message: ->(url, error) { "..." }
|
47
52
|
module RemoteUrl
|
48
|
-
DEFAULT_ERROR_MESSAGE = "file was not found or was too large"
|
49
|
-
|
50
53
|
def self.load_dependencies(uploader, downloader: :open_uri, **)
|
51
54
|
case downloader
|
52
55
|
when :open_uri then require "down"
|
53
56
|
end
|
54
57
|
end
|
55
58
|
|
56
|
-
def self.configure(uploader, downloader: :open_uri, error_message: nil,
|
59
|
+
def self.configure(uploader, downloader: :open_uri, max_size:, error_message: nil, include_error: false)
|
57
60
|
uploader.opts[:remote_url_downloader] = downloader
|
58
|
-
uploader.opts[:remote_url_error_message] = error_message || DEFAULT_ERROR_MESSAGE
|
59
61
|
uploader.opts[:remote_url_max_size] = max_size
|
62
|
+
uploader.opts[:remote_url_error_message] = error_message
|
63
|
+
uploader.opts[:remote_url_include_error] = include_error
|
60
64
|
end
|
61
65
|
|
62
66
|
module AttachmentMethods
|
@@ -82,12 +86,17 @@ class Shrine
|
|
82
86
|
def remote_url=(url)
|
83
87
|
return if url == ""
|
84
88
|
|
85
|
-
|
89
|
+
begin
|
90
|
+
downloaded_file = download(url)
|
91
|
+
rescue => error
|
92
|
+
download_error = error
|
93
|
+
end
|
94
|
+
|
95
|
+
if downloaded_file
|
86
96
|
assign(downloaded_file)
|
87
97
|
else
|
88
|
-
message =
|
89
|
-
|
90
|
-
errors << message
|
98
|
+
message = download_error_message(url, download_error)
|
99
|
+
errors.replace [message]
|
91
100
|
@remote_url = url
|
92
101
|
end
|
93
102
|
end
|
@@ -117,7 +126,19 @@ class Shrine
|
|
117
126
|
# the download simply failed.
|
118
127
|
def download_with_open_uri(url, max_size:)
|
119
128
|
Down.download(url, max_size: max_size)
|
120
|
-
|
129
|
+
end
|
130
|
+
|
131
|
+
def download_error_message(url, error)
|
132
|
+
if message = shrine_class.opts[:remote_url_error_message]
|
133
|
+
args = [url]
|
134
|
+
args << error if shrine_class.opts[:remote_url_include_error]
|
135
|
+
message = message.call(*args) if message.respond_to?(:call)
|
136
|
+
else
|
137
|
+
message = "download failed"
|
138
|
+
message = "#{message}: #{error.message}" if error
|
139
|
+
end
|
140
|
+
|
141
|
+
message
|
121
142
|
end
|
122
143
|
end
|
123
144
|
end
|
data/lib/shrine/storage/s3.rb
CHANGED
@@ -40,8 +40,8 @@ class Shrine
|
|
40
40
|
# These options will be passed to aws-sdk's methods for [uploading],
|
41
41
|
# [copying] and [presigning].
|
42
42
|
#
|
43
|
-
# You can also
|
44
|
-
#
|
43
|
+
# You can also generate upload options per upload with the `upload_options`
|
44
|
+
# plugin:
|
45
45
|
#
|
46
46
|
# class MyUploader < Shrine
|
47
47
|
# plugin :upload_options, store: ->(io, context) do
|
@@ -53,6 +53,9 @@ class Shrine
|
|
53
53
|
# end
|
54
54
|
# end
|
55
55
|
#
|
56
|
+
# Note that these aren't applied to presigns, since presigns are generated
|
57
|
+
# using the storage directly.
|
58
|
+
#
|
56
59
|
# ## CDN
|
57
60
|
#
|
58
61
|
# If you're using a CDN with S3 like Amazon CloudFront, you can specify
|
@@ -217,7 +220,7 @@ class Shrine
|
|
217
220
|
|
218
221
|
# This is used to check whether an S3 file is copyable.
|
219
222
|
def access_key_id
|
220
|
-
@s3.client.config.credentials.access_key_id
|
223
|
+
@s3.client.config.credentials.credentials.access_key_id
|
221
224
|
end
|
222
225
|
|
223
226
|
private
|
@@ -242,7 +245,7 @@ class Shrine
|
|
242
245
|
|
243
246
|
# Amazon requires multipart copy from S3 objects larger than 5 GB.
|
244
247
|
def large?(io)
|
245
|
-
io.size >= 5*1024*1024*1024 # 5GB
|
248
|
+
io.size && io.size >= 5*1024*1024*1024 # 5GB
|
246
249
|
end
|
247
250
|
end
|
248
251
|
end
|
data/lib/shrine/version.rb
CHANGED
data/shrine.gemspec
CHANGED
@@ -16,13 +16,12 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "shrine.gemspec", "doc/*.md"]
|
17
17
|
gem.require_path = "lib"
|
18
18
|
|
19
|
-
gem.add_dependency "down", ">=
|
19
|
+
gem.add_dependency "down", ">= 2.0.1"
|
20
20
|
|
21
21
|
gem.add_development_dependency "rake"
|
22
22
|
gem.add_development_dependency "minitest", "~> 5.8"
|
23
23
|
gem.add_development_dependency "minitest-hooks", "~> 1.3.0"
|
24
24
|
gem.add_development_dependency "mocha"
|
25
|
-
gem.add_development_dependency "vcr", "~> 2.9"
|
26
25
|
gem.add_development_dependency "webmock"
|
27
26
|
gem.add_development_dependency "rack-test_app"
|
28
27
|
gem.add_development_dependency "dotenv"
|
@@ -39,7 +38,7 @@ Gem::Specification.new do |gem|
|
|
39
38
|
end
|
40
39
|
|
41
40
|
gem.add_development_dependency "sequel"
|
42
|
-
gem.add_development_dependency "activerecord"
|
41
|
+
gem.add_development_dependency "activerecord", "~> 4.2"
|
43
42
|
|
44
43
|
if RUBY_ENGINE == "jruby"
|
45
44
|
gem.add_development_dependency "activerecord-jdbcsqlite3-adapter"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shrine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: down
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.0.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.0.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,20 +80,6 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: vcr
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '2.9'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '2.9'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: webmock
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -252,16 +238,16 @@ dependencies:
|
|
252
238
|
name: activerecord
|
253
239
|
requirement: !ruby/object:Gem::Requirement
|
254
240
|
requirements:
|
255
|
-
- - "
|
241
|
+
- - "~>"
|
256
242
|
- !ruby/object:Gem::Version
|
257
|
-
version: '
|
243
|
+
version: '4.2'
|
258
244
|
type: :development
|
259
245
|
prerelease: false
|
260
246
|
version_requirements: !ruby/object:Gem::Requirement
|
261
247
|
requirements:
|
262
|
-
- - "
|
248
|
+
- - "~>"
|
263
249
|
- !ruby/object:Gem::Version
|
264
|
-
version: '
|
250
|
+
version: '4.2'
|
265
251
|
- !ruby/object:Gem::Dependency
|
266
252
|
name: sqlite3
|
267
253
|
requirement: !ruby/object:Gem::Requirement
|
@@ -358,9 +344,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
358
344
|
version: '0'
|
359
345
|
requirements: []
|
360
346
|
rubyforge_project:
|
361
|
-
rubygems_version: 2.
|
347
|
+
rubygems_version: 2.5.1
|
362
348
|
signing_key:
|
363
349
|
specification_version: 4
|
364
350
|
summary: Toolkit for file uploads in Ruby
|
365
351
|
test_files: []
|
366
|
-
has_rdoc:
|