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
@@ -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
- if shrine_class.opts[:activerecord_validations]
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 shrine_class.opts[:activerecord_callbacks]
92
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
93
- before_save do
94
- #{@name}_attacher.save if #{@name}_attacher.attached?
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
- after_commit on: [:create, :update] do
98
- #{@name}_attacher.finalize if #{@name}_attacher.attached?
99
- end
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
- after_commit on: [:destroy] do
102
- #{@name}_attacher.destroy
103
- end
104
- RUBY
105
- end
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 metadata plugin provides a convenient method for extracting and
3
+ # The `add_metadata` plugin provides a convenient method for extracting and
4
4
  # adding custom metadata values.
5
5
  #
6
6
  # plugin :add_metadata
@@ -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
- record = find_record(record_class, record_id) ||
131
- record_class.new.tap { |object| object.id = record_id }
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 plugin allows setting the URL which will be returned when
3
+ # The `default_url` plugin allows setting the URL which will be returned when
4
4
  # the attachment is missing.
5
5
  #
6
6
  # plugin :default_url do |context|
@@ -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
- # If none of these quite suit your needs, you can build a custom analyzer:
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 [Roda] endpoint which can be used for
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 [Dropzone].
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, which also
93
- # accepts some service-specific options. You can generate these additional
94
- # options per-request with `:presign_options`:
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
- # options = {}
100
- # options[:content_length_range] = 0..(5*1024*1024) # limit the filesize to 5 MB
101
- # options[:content_type] = request.params["content_type"] # use "content_type" query parameter
102
- # options
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 [`Roda::Request`].
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
- # While Shrine only accepts cached attachments on form submits (for security
121
- # reasons), you can use this endpoint to upload files to any storage, just
122
- # add it to allowed storages:
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 can be easily customized via
129
- # plugins:
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
- # class MyUploader
132
- # class UploadEndpoint
133
- # plugin :hooks
139
+ # def call(env)
140
+ # result = @app.call(env)
134
141
  #
135
- # after do |response|
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
- # [supports]: https://github.com/blueimp/jQuery-File-Upload/wiki/Options#progress
149
- # ["accept" attribute]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-accept
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