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
data/doc/refile.md
CHANGED
@@ -1,191 +1,213 @@
|
|
1
1
|
# Shrine for Refile Users
|
2
2
|
|
3
|
-
This guide is aimed at helping Refile users transition to Shrine
|
4
|
-
|
5
|
-
|
6
|
-
Shrine
|
3
|
+
This guide is aimed at helping Refile users transition to Shrine, and it consists
|
4
|
+
of three parts:
|
5
|
+
|
6
|
+
1. Explanation of the key differences in design between Refile and Shrine
|
7
|
+
2. Instructions how to migrate and existing app that uses Refile to Shrine
|
8
|
+
3. Extensive reference of Refile's interface with Shrine equivalents
|
7
9
|
|
8
10
|
## Uploaders
|
9
11
|
|
10
|
-
Shrine
|
11
|
-
|
12
|
-
|
13
|
-
`Shrine`:
|
12
|
+
Shrine borrows many great concepts from Refile: Refile's "backends" are here
|
13
|
+
named "storages", it uses the same IO abstraction for uploading and representing
|
14
|
+
uploaded files, similar attachment logic, and direct uploads are also supported.
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
require "shrine/storage/s3"
|
16
|
+
While in Refile you work with storages directly, Shrine uses *uploaders* which
|
17
|
+
act as wrappers around storages:
|
18
18
|
|
19
|
-
Shrine.storages = {
|
20
|
-
cache: Shrine::Storage::FileSystem.new(*args),
|
21
|
-
store: Shrine::Storage::S3.new(*args),
|
22
|
-
}
|
23
|
-
```
|
24
19
|
```rb
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
storage = Shrine.storages[:store]
|
21
|
+
storage #=> #<Shrine::Storage::S3 ...>
|
22
|
+
|
23
|
+
uploader = Shrine.new(:store)
|
24
|
+
uploader #=> #<Shrine @storage_key=:store @storage=#<Shrine::Storage::S3>>
|
25
|
+
uploader.storage #=> #<Shrine::Storage::S3 ...>
|
26
|
+
|
27
|
+
uploaded_file = uploader.upload(image)
|
28
|
+
uploaded_file #=> #<Shrine::UploadedFile>
|
28
29
|
```
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
This way Shrine can perform tasks like generating location, extracting
|
32
|
+
metadata, processing, and logging, which are all storage-agnostic, and leave
|
33
|
+
storages to deal only with actual file storage. And these tasks can be
|
34
|
+
configured differently depending on the types of files you're uploading:
|
34
35
|
|
35
36
|
```rb
|
36
37
|
class ImageUploader < Shrine
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
add_metadata :exif do |io, context|
|
39
|
+
MiniMagick::Image.new(io).exif
|
40
|
+
end
|
40
41
|
end
|
41
42
|
```
|
42
43
|
```rb
|
43
|
-
class
|
44
|
-
|
44
|
+
class VideoUploader < Shrine
|
45
|
+
add_metadata :duration do |io, context|
|
46
|
+
FFMPEG::Movie.new(io.path).duration
|
47
|
+
end
|
45
48
|
end
|
46
49
|
```
|
47
50
|
|
48
|
-
|
49
|
-
has a very rich arsenal of features via plugins, and allows you to share your
|
50
|
-
uploading logic between uploaders through inheritance.
|
51
|
+
### Processing
|
51
52
|
|
52
|
-
|
53
|
+
Refile implements on-the-fly processing, serving all files through the Rack
|
54
|
+
endpoint. However, it doesn't offer any abilities for processing on upload.
|
55
|
+
Shrine, on the other hand, generates URLs to specific storages and offers
|
56
|
+
processing on upload (like CarrierWave and Paperclip), but doesn't support
|
57
|
+
on-the-fly processing.
|
53
58
|
|
54
|
-
|
55
|
-
|
59
|
+
The reason for this decision is that an image server is a completely separate
|
60
|
+
responsibility, and it's better to use any of the generic services for
|
61
|
+
on-the-fly processing. Shrine already has integrations for many such services:
|
62
|
+
[shrine-cloudinary], [shrine-imgix], and [shrine-uploadcare]. There is even
|
63
|
+
an open-source solution, [Attache], which you can also use with Shrine.
|
64
|
+
|
65
|
+
This is how you would process multiple versions in Shrine:
|
56
66
|
|
57
67
|
```rb
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
include ImageUploader[:image]
|
63
|
-
end
|
64
|
-
```
|
68
|
+
class ImageUploader < Shrine
|
69
|
+
include ImageProcessing::MiniMagick
|
70
|
+
plugin :processing
|
71
|
+
plugin :versions
|
65
72
|
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
process(:store) do |io, context|
|
74
|
+
size_800 = resize_to_limit(io.download, 800, 800)
|
75
|
+
size_500 = resize_to_limit(size_800, 500, 500)
|
76
|
+
size_300 = resize_to_limit(size_500, 300, 300)
|
69
77
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
<%= f.file_field :image %>
|
74
|
-
<% end %>
|
78
|
+
{original: size_800, medium: size_500, small: size_300}
|
79
|
+
end
|
80
|
+
end
|
75
81
|
```
|
76
82
|
|
77
|
-
###
|
83
|
+
### URL
|
78
84
|
|
79
|
-
|
85
|
+
While Refile serves all files through the Rack endpoint mounted in your app,
|
86
|
+
Shrine serves files directly from storage services:
|
80
87
|
|
81
88
|
```rb
|
82
|
-
@photo
|
83
|
-
@photo.image_url # returns nil if attachment is missing
|
89
|
+
Refile.attachment_url(@photo, :image) #=> "/attachments/cache/50dfl833lfs0gfh.jpg"
|
84
90
|
```
|
85
91
|
|
86
|
-
If you're using storages which don't expose files over URL, or you want to
|
87
|
-
secure your downloads, you can use the `download_endpoint` plugin.
|
88
|
-
|
89
|
-
### Metadata
|
90
|
-
|
91
|
-
While in Refile you're required to have a separate column for each metadata you
|
92
|
-
want to save (filename, size, content type), in Shrine all of the metadata are
|
93
|
-
stored in a single column (for "avatar" it's `avatar_data` column) as JSON.
|
94
|
-
|
95
92
|
```rb
|
96
|
-
|
93
|
+
@photo.image.url #=> "https://my-bucket.s3.amazonaws.com/cache/50dfl833lfs0gfh.jpg"
|
97
94
|
```
|
98
95
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
### Processing
|
96
|
+
If you're using storage which don't expose files over URL (e.g. a database
|
97
|
+
storage), or you want to secure your downloads, you can also serve files
|
98
|
+
through your app using the download_endpoint plugin.
|
103
99
|
|
104
|
-
|
105
|
-
processing on-the-fly (like Dragonfly), while in Shrine you do your processing
|
106
|
-
on upload (like CarrierWave and Paperclip). However, there are storages which
|
107
|
-
you can use which support on-the-fly processing, like [shrine-cloudinary] or
|
108
|
-
[shrine-imgix].
|
100
|
+
## Attachments
|
109
101
|
|
110
|
-
|
111
|
-
|
102
|
+
While in Refile you configure attachments by passing options to `.attachment`,
|
103
|
+
in Shrine you define all your uploading logic inside uploaders, and then
|
104
|
+
generate an attachment module with that uploader which is included into the
|
105
|
+
model:
|
112
106
|
|
113
107
|
```rb
|
114
|
-
|
108
|
+
class Photo < Sequel::Model
|
109
|
+
extend Shrine::Sequel::Attachment
|
110
|
+
attachment :image, destroy: false
|
111
|
+
end
|
112
|
+
```
|
115
113
|
|
114
|
+
```rb
|
116
115
|
class ImageUploader < Shrine
|
117
|
-
|
118
|
-
plugin :
|
116
|
+
plugin :sequel
|
117
|
+
plugin :keep_files, destroyed: true
|
118
|
+
end
|
119
119
|
|
120
|
-
|
121
|
-
|
122
|
-
end
|
120
|
+
class Photo < Sequel::Model
|
121
|
+
include ImageUploader[:image]
|
123
122
|
end
|
124
123
|
```
|
125
124
|
|
126
|
-
|
125
|
+
This way we can encapsulate all attachment logic inside a class and share it
|
126
|
+
between different models.
|
127
127
|
|
128
|
-
|
129
|
-
passing options to `.attachment`, in Shrine you do this logic instance level,
|
130
|
-
with the help of the `validation_helpers` plugin:
|
128
|
+
### Metadata
|
131
129
|
|
132
|
-
|
133
|
-
|
134
|
-
|
130
|
+
Refile allows you to save additional metadata about uploaded files in additional
|
131
|
+
columns, so you can define `<attachment>_filename`, `<attachment>_content_type`,
|
132
|
+
or `<attachment>_size`.
|
135
133
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
134
|
+
Shrine, on the other hand, saves all metadata into a single `<attachment>_data`
|
135
|
+
column:
|
136
|
+
|
137
|
+
```rb
|
138
|
+
photo.image_data #=>
|
139
|
+
# {
|
140
|
+
# "storage" => "store",
|
141
|
+
# "id" => "photo/1/image/0d9o8dk42.png",
|
142
|
+
# "metadata" => {
|
143
|
+
# "filename" => "nature.png",
|
144
|
+
# "size" => 49349138,
|
145
|
+
# "mime_type" => "image/png"
|
146
|
+
# }
|
147
|
+
# }
|
148
|
+
|
149
|
+
photo.image.original_filename #=> "nature.png"
|
150
|
+
photo.image.size #=> 49349138
|
151
|
+
photo.image.mime_type #=> "image/png"
|
140
152
|
```
|
141
153
|
|
142
|
-
|
154
|
+
By default "filename", "size" and "mime_type" is stored, but you can also store
|
155
|
+
image dimensions, or define any other custom metadata. This also allow storages
|
156
|
+
to add their own metadata.
|
143
157
|
|
144
|
-
|
145
|
-
|
158
|
+
### Validations
|
159
|
+
|
160
|
+
In Refile you define validations by passing options to `.attachment`, while
|
161
|
+
in Shrine you define validations on the instance-level, which allows them to
|
162
|
+
be dynamic:
|
146
163
|
|
147
164
|
```rb
|
148
|
-
class
|
149
|
-
|
165
|
+
class Photo < Sequel::Model
|
166
|
+
attachment :image,
|
167
|
+
extension: %w[jpg jpeg png gif],
|
168
|
+
content_type: %w[image/jpeg image/png image/gif]
|
150
169
|
end
|
151
170
|
```
|
171
|
+
|
152
172
|
```rb
|
153
|
-
|
154
|
-
|
155
|
-
|
173
|
+
class ImageUploader < Shrine
|
174
|
+
plugin :validation_helpers
|
175
|
+
|
176
|
+
Attacher.validate do
|
177
|
+
validate_extension_inclusion %w[jpg jpeg png gif]
|
178
|
+
validate_mime_type_inclusion %w[image/jpeg image/png image/gif]
|
179
|
+
validate_max_size 10*1024*1024 unless record.admin?
|
180
|
+
end
|
156
181
|
end
|
157
182
|
```
|
158
|
-
```rb
|
159
|
-
# POST /attachments/images/cache/upload
|
160
|
-
{
|
161
|
-
"id": "43kewit94.jpg",
|
162
|
-
"storage": "cache",
|
163
|
-
"metadata": {
|
164
|
-
"size": 384393,
|
165
|
-
"filename": "nature.jpg",
|
166
|
-
"mime_type": "image/jpeg"
|
167
|
-
}
|
168
|
-
}
|
169
|
-
```
|
170
|
-
|
171
|
-
Unlike Refile, Shrine doesn't ship with a JavaScript script which you can just
|
172
|
-
include to make it work. Instead, you're expected to use one of the many
|
173
|
-
excellent JavaScript libraries for generic file uploads, for example
|
174
|
-
[jQuery-File-Upload].
|
175
183
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
you just need to pass the `presign: true` option. In the same way as with regular
|
180
|
-
direct uploads, you can use a generic JavaScript file upload library. For the
|
181
|
-
details read the [Direct Uploads to S3] guide.
|
184
|
+
Refile extracts the MIME type from the file extension, which means it can
|
185
|
+
easily be spoofed (just give a PHP file a `.jpg` extension). Shrine has the
|
186
|
+
determine_mime_type plugin for determining MIME type from file *content*.
|
182
187
|
|
183
188
|
### Multiple uploads
|
184
189
|
|
185
190
|
Shrine doesn't have a built-in solution for accepting multiple uploads, but
|
186
|
-
it's actually very easy to do manually, see the [
|
191
|
+
it's actually very easy to do manually, see the [demo app] on how you can do
|
187
192
|
multiple uploads directly to S3.
|
188
193
|
|
194
|
+
## Direct uploads
|
195
|
+
|
196
|
+
Shrine borrows Refile's idea of direct uploads, and ships with a
|
197
|
+
direct_upload plugin which provides an endpoint for uploading files and
|
198
|
+
generating presigns.
|
199
|
+
|
200
|
+
```rb
|
201
|
+
Shrine.plugin :direct_upload
|
202
|
+
# POST /:storage/upload
|
203
|
+
# GET /:storage/presign
|
204
|
+
```
|
205
|
+
|
206
|
+
Unlike Refile, Shrine doesn't ship with complete JavaScript which you can just
|
207
|
+
include to make it work. Instead, you're expected to use one of the excellent
|
208
|
+
JavaScript libraries for generic file uploads like [jQuery-File-Upload]. See
|
209
|
+
also the [Direct Uploads to S3] guide.
|
210
|
+
|
189
211
|
## Migrating from Refile
|
190
212
|
|
191
213
|
You have an existing app using Refile and you want to transfer it to
|
@@ -390,7 +412,9 @@ No equivalent currently exists in Shrine.
|
|
390
412
|
|
391
413
|
[shrine-cloudinary]: https://github.com/janko-m/shrine-cloudinary
|
392
414
|
[shrine-imgix]: https://github.com/janko-m/shrine-imgix
|
415
|
+
[shrine-uploadcare]: https://github.com/janko-m/shrine-uploadcare
|
416
|
+
[Attache]: https://github.com/choonkeat/attache
|
393
417
|
[image_processing]: https://github.com/janko-m/image_processing
|
394
418
|
[jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
|
395
419
|
[Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
|
396
|
-
[
|
420
|
+
[demo app]: https://github.com/janko-m/shrine/tree/master/demo
|
@@ -113,9 +113,8 @@ old_versions = []
|
|
113
113
|
User.paged_each do |user|
|
114
114
|
attacher, attachment = user.avatar_attacher, user.avatar
|
115
115
|
if attacher.stored? && attachment[:old_version]
|
116
|
-
|
117
|
-
|
118
|
-
old_versions << old_version if swapped
|
116
|
+
old_versions << attachment.delete(:old_version)
|
117
|
+
attacher.swap(attachment)
|
119
118
|
end
|
120
119
|
end
|
121
120
|
|
data/lib/shrine.rb
CHANGED
@@ -451,16 +451,20 @@ class Shrine
|
|
451
451
|
end
|
452
452
|
|
453
453
|
module AttacherMethods
|
454
|
-
attr_reader :
|
454
|
+
attr_reader :cache, :store, :context, :errors
|
455
455
|
|
456
456
|
def initialize(record, name, cache: :cache, store: :store)
|
457
|
-
@
|
458
|
-
@
|
459
|
-
@
|
460
|
-
@
|
461
|
-
@errors = []
|
457
|
+
@cache = shrine_class.new(cache)
|
458
|
+
@store = shrine_class.new(store)
|
459
|
+
@context = {record: record, name: name}
|
460
|
+
@errors = []
|
462
461
|
end
|
463
462
|
|
463
|
+
# Returns the model instance associated with the attacher.
|
464
|
+
def record; context[:record]; end
|
465
|
+
# Returns the attachment name associated with the attacher.
|
466
|
+
def name; context[:name]; end
|
467
|
+
|
464
468
|
# Receives the attachment value from the form. If it receives a JSON
|
465
469
|
# string or a hash, it will assume this refrences an already cached
|
466
470
|
# file (e.g. when it persisted after validation errors).
|
@@ -483,9 +487,10 @@ class Shrine
|
|
483
487
|
validate
|
484
488
|
end
|
485
489
|
|
486
|
-
#
|
487
|
-
def
|
488
|
-
|
490
|
+
# Runs the validations defined by `Attacher.validate`.
|
491
|
+
def validate
|
492
|
+
errors.clear
|
493
|
+
instance_exec(&validate_block) if validate_block && get
|
489
494
|
end
|
490
495
|
|
491
496
|
# Returns true if a new file has been attached.
|
@@ -558,15 +563,15 @@ class Shrine
|
|
558
563
|
get && store.uploaded?(get)
|
559
564
|
end
|
560
565
|
|
561
|
-
#
|
562
|
-
def
|
563
|
-
|
564
|
-
instance_exec(&validate_block) if validate_block && get
|
566
|
+
# Retrieves the uploaded file from the record column.
|
567
|
+
def get
|
568
|
+
uploaded_file(read) if read
|
565
569
|
end
|
566
570
|
|
567
|
-
#
|
568
|
-
def
|
569
|
-
|
571
|
+
# It reads from the record's `<attachment>_data` column.
|
572
|
+
def read
|
573
|
+
value = record.send(:"#{name}_data")
|
574
|
+
value unless value.nil? || value.empty?
|
570
575
|
end
|
571
576
|
|
572
577
|
# Uploads the file to cache passing context.
|
@@ -587,6 +592,11 @@ class Shrine
|
|
587
592
|
store.delete(uploaded_file, context.merge(_equalize_phase_and_action(options)))
|
588
593
|
end
|
589
594
|
|
595
|
+
# Delegates to `Shrine.uploaded_file`.
|
596
|
+
def uploaded_file(*args, &block)
|
597
|
+
shrine_class.uploaded_file(*args, &block)
|
598
|
+
end
|
599
|
+
|
590
600
|
# Returns the Shrine class related to this attacher.
|
591
601
|
def shrine_class
|
592
602
|
self.class.shrine_class
|
@@ -619,18 +629,6 @@ class Shrine
|
|
619
629
|
record.send(:"#{name}_data=", value)
|
620
630
|
end
|
621
631
|
|
622
|
-
# It reads from the record's `<attachment>_data` column.
|
623
|
-
def read
|
624
|
-
value = record.send(:"#{name}_data")
|
625
|
-
value unless value.nil? || value.empty?
|
626
|
-
end
|
627
|
-
|
628
|
-
# The context that's sent to Shrine on upload and delete. It holds the
|
629
|
-
# record and the name of the attachment.
|
630
|
-
def context
|
631
|
-
{name: name, record: record}
|
632
|
-
end
|
633
|
-
|
634
632
|
# Temporary method used for transitioning from :phase to :action.
|
635
633
|
def _equalize_phase_and_action(options)
|
636
634
|
options[:phase] = options[:action] if options.key?(:action)
|