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
@@ -1,191 +1,213 @@
1
1
  # Shrine for Refile Users
2
2
 
3
- This guide is aimed at helping Refile users transition to Shrine. We will first
4
- generally mention what are the key differences, and afterwards we will give a
5
- complete reference of Refile's interface and note what is the equivalent in
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 has the concept of storages very similar to Refile's backends. However,
11
- while in Refile you usually work with storages directly, in Shrine you use
12
- *uploaders* which act as wrappers around storages, and they are subclasses of
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
- ```rb
16
- require "shrine/storage/file_system"
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
- class ImageUploader < Shrine
26
- # uploading logic
27
- end
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
- While in Refile you configure attachments by passing options to `.attachment`,
31
- in Shrine you define all your uploading logic inside uploaders, and then
32
- generate an attachment module with that uploader which is included into the
33
- model:
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
- plugin :store_dimensions
38
- plugin :determine_mime_type
39
- plugin :keep_files, destroyed: true
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 User
44
- include ImageUploader[:avatar] # requires "avatar_data" column
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
- Unlike Refile which has just a few options of configuring attachments, Shrine
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
- ### ORMs
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
- In Refile you extend the model with an Attachment module specific to the ORM
55
- you're using. In Shrine you load the appropriate ORM plugin:
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
- Shrine.plugin :sequel # or :activerecord
59
- ```
60
- ```rb
61
- class Photo < Sequel::Model
62
- include ImageUploader[:image]
63
- end
64
- ```
68
+ class ImageUploader < Shrine
69
+ include ImageProcessing::MiniMagick
70
+ plugin :processing
71
+ plugin :versions
65
72
 
66
- These integrations work much like Refile; on assignment the file is cached,
67
- and on saving the record file is moved from cache to store. Shrine doesn't
68
- provide form helpers for Rails, because it's so easy to do it yourself:
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
- ```erb
71
- <%= form_for @photo do |f| %>
72
- <%= f.hidden_field :image, value: @photo.image_data %>
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
- ### URLs
83
+ ### URL
78
84
 
79
- To get file URLs, in Shrine you just call `#url` on the file:
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.image.url
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
- user.avatar_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
93
+ @photo.image.url #=> "https://my-bucket.s3.amazonaws.com/cache/50dfl833lfs0gfh.jpg"
97
94
  ```
98
95
 
99
- By default Shrine stores "filename", "size" and "mime_type" metadata, but you
100
- can also store image dimensions by loading the `store_dimensions` plugin.
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
- One of the key differences between Refile and Shrine is that in Refile you do
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
- Processing is defined and performed on the instance level, and the result of
111
- can be a single file or a hash of versions:
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
- require "image_processing/mini_magick"
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
- include ImageProcessing::MiniMagick
118
- plugin :processing
116
+ plugin :sequel
117
+ plugin :keep_files, destroyed: true
118
+ end
119
119
 
120
- process(:store) do |io, context|
121
- resize_to_fit!(io.download, 700, 700)
122
- end
120
+ class Photo < Sequel::Model
121
+ include ImageUploader[:image]
123
122
  end
124
123
  ```
125
124
 
126
- ### Validations
125
+ This way we can encapsulate all attachment logic inside a class and share it
126
+ between different models.
127
127
 
128
- While in Refile you can do extension, mime type and filesize validation by
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
- ```rb
133
- class ImageUploader < Shrine
134
- plugin :validation_helpers
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
- Attacher.validate do
137
- validate_mime_type_inclusion ["image/jpeg", "image/png", "image/gif"]
138
- end
139
- end
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
- ### Direct uploads
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
- Shrine borrows Refile's idea of direct uploads, and ships with a
145
- `direct_upload` plugin which provides the endpoint that you can mount:
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 ImageUploader < Shrine
149
- plugin :direct_upload
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
- # config/routes.rb
154
- Rails.application.routes.draw do
155
- mount ImageUploader::UploadEndpoint => "/attachments/images"
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
- #### Presigned S3 uploads
177
-
178
- The `direct_upload` plugin also provides an endpoint for getting S3 presigns,
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 [example app] on how you can do
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
- [example app]: https://github.com/janko-m/shrine-example
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
- old_version = attachment.delete(:old_version)
117
- swapped = attacher.swap(attachment)
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
 
@@ -451,16 +451,20 @@ class Shrine
451
451
  end
452
452
 
453
453
  module AttacherMethods
454
- attr_reader :record, :name, :cache, :store, :errors
454
+ attr_reader :cache, :store, :context, :errors
455
455
 
456
456
  def initialize(record, name, cache: :cache, store: :store)
457
- @record = record
458
- @name = name
459
- @cache = shrine_class.new(cache)
460
- @store = shrine_class.new(store)
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
- # Retrieves the uploaded file from the record column.
487
- def get
488
- uploaded_file(read) if read
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
- # Runs the validations defined by `Attacher.validate`.
562
- def validate
563
- errors.clear
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
- # Delegates to `Shrine.uploaded_file`.
568
- def uploaded_file(*args, &block)
569
- shrine_class.uploaded_file(*args, &block)
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)