shrine 2.6.1 → 2.7.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.

@@ -193,20 +193,22 @@ multiple uploads directly to S3.
193
193
 
194
194
  ## Direct uploads
195
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.
196
+ Shrine borrows Refile's idea of direct uploads, and ships with
197
+ `upload_endpoint` and `presign_endpoint` plugins which provide endpoints for
198
+ uploading files and generating presigns.
199
199
 
200
200
  ```rb
201
- Shrine.plugin :direct_upload
202
- # POST /:storage/upload
203
- # GET /:storage/presign
201
+ Shrine.plugin :upload_endpoint
202
+ Shrine.upload_endpoint(:cache) # Rack app that uploads files to specified storage
203
+
204
+ Shrine.plugin :upload_endpoint
205
+ Shrine.presign_endpoint(:cache) # Rack app that generates presigns for specified storage
204
206
  ```
205
207
 
206
208
  Unlike Refile, Shrine doesn't ship with complete JavaScript which you can just
207
209
  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
+ JavaScript libraries for generic file uploads like [FineUploader], [Dropzone]
211
+ or [jQuery-File-Upload]. See also the [Direct Uploads to S3] guide.
210
212
 
211
213
  ## Migrating from Refile
212
214
 
@@ -287,22 +289,21 @@ Shrine.storages = {
287
289
 
288
290
  #### `.app`, `.mount_point`, `.automount`
289
291
 
290
- The `direct_upload` plugin provides a subset of Refile's app's functionality,
291
- and you have to mount it in your framework's router:
292
+ The `upload_endpoint` and `presign_endpoint` plugins provide methods for
293
+ generating Rack apps, but you need to mount them explicitly:
292
294
 
293
295
  ```rb
294
296
  # config/routes.rb
295
297
  Rails.application.routes.draw do
296
- # adds `POST /attachments/images/:storage/:name`
297
- mount ImageUploader::UploadEndpoint => "/attachments/images"
298
+ # adds `POST /images/upload` endpoint
299
+ mount ImageUploader.upload_endpoint(:cache) => "/images/upload"
298
300
  end
299
301
  ```
300
302
 
301
303
  #### `.allow_uploads_to`
302
304
 
303
- ```rb
304
- Shrine.plugin :direct_upload, storages: [:cache]
305
- ```
305
+ The `Shrine.upload_endpoint` and `Shrine.presign_endpoint` require you to
306
+ specify the storage that will be used.
306
307
 
307
308
  #### `.logger`
308
309
 
@@ -477,6 +478,8 @@ Shrine.plugin :remote_url
477
478
  [shrine-uploadcare]: https://github.com/janko-m/shrine-uploadcare
478
479
  [Attache]: https://github.com/choonkeat/attache
479
480
  [image_processing]: https://github.com/janko-m/image_processing
481
+ [FineUploader]: https://github.com/FineUploader/fine-uploader
482
+ [Dropzone]: https://github.com/enyo/dropzone
480
483
  [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
481
484
  [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
482
485
  [demo app]: https://github.com/janko-m/shrine/tree/master/demo
@@ -2,7 +2,7 @@
2
2
 
3
3
  While your app is serving uploads in production, you may realize that you want
4
4
  to change how your attachment's versions are generated. This means that, in
5
- addition to changing you processing code, you also need to reprocess the
5
+ addition to changing your processing code, you also need to reprocess the
6
6
  existing attachments. This guide is aimed to help doing this migration with
7
7
  zero downtime and no unused files left in the main storage.
8
8
 
@@ -50,20 +50,20 @@ being uploaded to cache and the temporary directory.
50
50
 
51
51
  ### Limiting filesize in direct uploads
52
52
 
53
- If you're doing direct uploads with the `direct_upload` plugin, you can pass
54
- in the `:max_size` option, which will refuse too large files and automatically
55
- delete it from temporary storage.
53
+ If you're doing direct uploads with the `upload_endpoint` plugin, you can pass
54
+ in the `:max_size` option to reject files that are larger than the specified
55
+ limit:
56
56
 
57
57
  ```rb
58
- plugin :direct_upload, max_size: 20*1024*1024 # 20 MB
58
+ plugin :upload_endpoint, max_size: 20*1024*1024 # 20 MB
59
59
  ```
60
60
 
61
- This option doesn't apply to presigned uploads, if you're using S3 you can
62
- limit the filesize on presigning:
61
+ If you're doing direct uploads to Amazon S3 using the `presign_endpoint`
62
+ plugin, you can pass in the `:content_length_range` presign option:
63
63
 
64
64
  ```rb
65
- plugin :direct_upload, presign: ->(request) do
66
- {content_length_range: 0..20*1024*1024}
65
+ plugin :presign_endpoint, presign_options: -> (request) do
66
+ { content_length_range: 0..20*1024*1024 }
67
67
  end
68
68
  ```
69
69
 
@@ -58,16 +58,8 @@ Shrine.storages = {
58
58
  }
59
59
  ```
60
60
 
61
- Alternatively, if you're using Amazon S3 storage, in tests (and development)
62
- you can swap it out for [FakeS3]. You just need tell aws-sdk that instead of
63
- `s3.amazonaws.com` it should use the host of your FakeS3 server when generating
64
- URLs.
65
-
66
- ```rb
67
- Shrine::Storage::S3.new(endpoint: "http://localhost:10000")
68
- ```
69
-
70
- Note that for using FakeS3 you need aws-sdk version 2.2.25 or higher.
61
+ Alternatively, if you're using Amazon S3 storage, in tests you can use
62
+ [aws-sdk-ruby stubs].
71
63
 
72
64
  ## Test data
73
65
 
@@ -76,14 +68,14 @@ you can have the test file assigned dynamically when the record is created:
76
68
 
77
69
  ```rb
78
70
  factory :photo do
79
- image File.open("test/files/image.jpg")
71
+ image { File.open("test/files/image.jpg") }
80
72
  end
81
73
  ```
82
74
 
83
75
  On the other hand, if you're setting up test data using Rails' YAML fixtures,
84
76
  you unfortunately won't be able to use them for assigning files. This is
85
77
  because Rails fixtures only allow assigning primitive data types, and don't
86
- allow you to specify Shrine attributes, you can only assign to columns
78
+ allow you to specify Shrine attributes - you can only assign to columns
87
79
  directly.
88
80
 
89
81
  ## Background jobs
@@ -250,15 +242,29 @@ isolation.
250
242
 
251
243
  ## Direct upload
252
244
 
253
- In case you're doing direct uploads to S3 on production and staging
254
- environments, in development and test you might want to just store files on
255
- the filesystem for speed.
245
+ If you've set up direct uploads to Amazon S3 (using the `presign_endpoint`
246
+ plugin), in tests you'll probably want to just use filesystem or memory storage
247
+ to avoid network requests.
248
+
249
+ The easiest way to do that is to add an `upload_endpoint`, modify it so that it
250
+ behaves like S3, and change `presign_endpoint` response to point to the upload
251
+ endpoint. Here is how one could modify the test helper in a Rails application:
256
252
 
257
- In that case you can swap out S3 for FileSystem, and the `direct_upload` app
258
- should still continue to work without any changes. This is because Shrine
259
- detects that you're using a storage which isn't an external service, and in
260
- that case the presign endpoint returns an URL to the upload route that's also
261
- provided by the `direct_upload` app mounted in your routes.
253
+ ```rb
254
+ # test/test_helper.rb
255
+
256
+ # create and mount a fake S3 upload endpoint
257
+ Shrine.plugin :upload_endpoint
258
+ fake_s3 = Shrine.upload_endpoint(:cache, upload_context: -> (request) {
259
+ { location: request.params["key"].match(/^cache\//).post_match }
260
+ })
261
+ Rails.application.routes.prepend { mount fake_s3 => "/s3" }
262
+
263
+ # override presigns to return URLs to the fake S3 upload endpoint
264
+ Shrine.plugin :presign_endpoint, presign: -> (id, options, request) do
265
+ Struct.new(:url, :fields).new("#{request.base_url}/s3", { "key" => "cache/#{id}" })
266
+ end
267
+ ```
262
268
 
263
269
  [DatabaseCleaner]: https://github.com/DatabaseCleaner/database_cleaner
264
270
  [shrine-memory]: https://github.com/janko-m/shrine-memory
@@ -267,4 +273,4 @@ provided by the `direct_upload` app mounted in your routes.
267
273
  [`#attach_file`]: http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions#attach_file-instance_method
268
274
  [Rack::Test]: https://github.com/brynary/rack-test
269
275
  [Rack::TestApp]: https://github.com/kwatch/rack-test_app
270
- [FakeS3]: https://github.com/jubos/fake-s3
276
+ [aws-sdk-ruby stubs]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ClientStubs.html
@@ -154,8 +154,8 @@ class Shrine
154
154
  # class Photo
155
155
  # include Shrine.attachment(:image) # creates a Shrine::Attachment object
156
156
  # end
157
- def attachment(name)
158
- self::Attachment.new(name)
157
+ def attachment(name, **options)
158
+ self::Attachment.new(name, **options)
159
159
  end
160
160
  alias [] attachment
161
161
 
@@ -167,7 +167,6 @@ class Shrine
167
167
  def uploaded_file(object, &block)
168
168
  case object
169
169
  when String
170
- deprecation("Giving a string to Shrine.uploaded_file is deprecated and won't be possible in Shrine 3. Use Attacher#uploaded_file instead.")
171
170
  uploaded_file(JSON.parse(object), &block)
172
171
  when Hash
173
172
  uploaded_file(self::UploadedFile.new(object), &block)
@@ -390,17 +389,22 @@ class Shrine
390
389
 
391
390
  module AttachmentMethods
392
391
  # Instantiates an attachment module for a given attribute name, which
393
- # can then be included to a model class.
394
- def initialize(name)
395
- @name = name
396
-
397
- # We store the attacher class so that the model can instantiate the
398
- # correct attacher instance.
399
- class_variable_set(:"@@#{name}_attacher_class", shrine_class::Attacher)
392
+ # can then be included to a model class. Second argument will be passed
393
+ # to an attacher module.
394
+ def initialize(name, **options)
395
+ @name = name
396
+ @options = options
400
397
 
401
398
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
402
399
  def #{name}_attacher
403
- @#{name}_attacher ||= @@#{name}_attacher_class.new(self, :#{name})
400
+ @#{name}_attacher ||= (
401
+ attachments = self.class.ancestors.grep(Shrine::Attachment)
402
+ attachment = attachments.find { |mod| mod.attachment_name == :#{name} }
403
+ attacher_class = attachment.shrine_class::Attacher
404
+ options = attachment.options
405
+
406
+ attacher_class.new(self, :#{name}, options)
407
+ )
404
408
  end
405
409
 
406
410
  def #{name}=(value)
@@ -417,18 +421,28 @@ class Shrine
417
421
  RUBY
418
422
  end
419
423
 
420
- # Includes the attachment name in the output.
424
+ # Returns name of the attachment this module provides.
425
+ def attachment_name
426
+ @name
427
+ end
428
+
429
+ # Returns options that are to be passed to the Attacher.
430
+ def options
431
+ @options
432
+ end
433
+
434
+ # Returns class name with attachment name included.
421
435
  #
422
436
  # Shrine[:image].to_s #=> "#<Shrine::Attachment(image)>"
423
437
  def to_s
424
- "#<#{self.class.inspect}(#{@name})>"
438
+ "#<#{self.class.inspect}(#{attachment_name})>"
425
439
  end
426
440
 
427
- # Includes the attachment name in the output.
441
+ # Returns class name with attachment name included.
428
442
  #
429
443
  # Shrine[:image].inspect #=> "#<Shrine::Attachment(image)>"
430
444
  def inspect
431
- "#<#{self.class.inspect}(#{@name})>"
445
+ "#<#{self.class.inspect}(#{attachment_name})>"
432
446
  end
433
447
 
434
448
  # Returns the Shrine class that this attachment's class is namespaced
@@ -459,7 +473,8 @@ class Shrine
459
473
  # end
460
474
  # end
461
475
  def validate(&block)
462
- shrine_class.opts[:validate] = block
476
+ define_method(:validate_block, &block)
477
+ private :validate_block
463
478
  end
464
479
  end
465
480
 
@@ -519,7 +534,7 @@ class Shrine
519
534
  # Runs the validations defined by `Attacher.validate`.
520
535
  def validate
521
536
  errors.clear
522
- instance_exec(&validate_block) if validate_block && get
537
+ validate_block if get
523
538
  end
524
539
 
525
540
  # Returns true if a new file has been attached.
@@ -629,11 +644,7 @@ class Shrine
629
644
  # Enhances `Shrine.uploaded_file` with the ability to recognize uploaded
630
645
  # files as JSON strings.
631
646
  def uploaded_file(object, &block)
632
- if object.is_a?(String)
633
- uploaded_file(JSON.parse(object), &block)
634
- else
635
- shrine_class.uploaded_file(object, &block)
636
- end
647
+ shrine_class.uploaded_file(object, &block)
637
648
  end
638
649
 
639
650
  # The name of the attribute on the model instance that is used to store
@@ -661,9 +672,9 @@ class Shrine
661
672
  _set(uploaded_file)
662
673
  end
663
674
 
664
- # The validation block registered with `Attacher.validate`.
675
+ # Performs validation actually.
676
+ # This method is redefined with `Attacher.validate`.
665
677
  def validate_block
666
- shrine_class.opts[:validate]
667
678
  end
668
679
 
669
680
  # Converts the UploadedFile to a data hash and writes it to the
@@ -40,6 +40,10 @@ class Shrine
40
40
  # end
41
41
  # end
42
42
  #
43
+ # Note that ActiveRecord currently has a [bug with transaction callbacks],
44
+ # so if you have any "after commit" callbacks, make sure to include Shrine's
45
+ # attachment module *after* they have all been defined.
46
+ #
43
47
  # If you don't want the attachment module to add any callbacks to the
44
48
  # model, and would instead prefer to call these actions manually, you can
45
49
  # disable callbacks:
@@ -49,8 +53,21 @@ class Shrine
49
53
  # ## Validations
50
54
  #
51
55
  # Additionally, any Shrine validation errors will be added to
52
- # ActiveRecord's errors upon validation. If you want to validate presence
53
- # of the attachment, you can do it directly on the model.
56
+ # ActiveRecord's errors upon validation. Note that Shrine validation
57
+ # messages don't have to be strings, they can also be symbols or symbols
58
+ # and options, which allows them to be internationalized together with
59
+ # other ActiveRecord validation messages.
60
+ #
61
+ # class MyUploader < Shrine
62
+ # plugin :validation_helpers
63
+ #
64
+ # Attacher.validate do
65
+ # validate_max_size 256 * 1024**2, message: ->(max) { [:max_size, max: max] }
66
+ # end
67
+ # end
68
+ #
69
+ # If you want to validate presence of the attachment, you can do it
70
+ # directly on the model.
54
71
  #
55
72
  # class User < ActiveRecord::Base
56
73
  # include ImageUploader::Attachment.new(:avatar)
@@ -61,6 +78,8 @@ class Shrine
61
78
  # model errors, you can disable it:
62
79
  #
63
80
  # plugin :activerecord, validations: false
81
+ #
82
+ # [bug with transaction callbacks]: https://github.com/rails/rails/issues/14493
64
83
  module Activerecord
65
84
  def self.configure(uploader, opts = {})
66
85
  uploader.opts[:activerecord_callbacks] = opts.fetch(:callbacks, uploader.opts.fetch(:activerecord_callbacks, true))
@@ -78,7 +97,7 @@ class Shrine
78
97
  model.class_eval <<-RUBY, __FILE__, __LINE__ + 1 if opts[:activerecord_validations]
79
98
  validate do
80
99
  #{@name}_attacher.errors.each do |message|
81
- errors.add(:#{@name}, message)
100
+ errors.add(:#{@name}, *message)
82
101
  end
83
102
  end
84
103
  RUBY
@@ -19,7 +19,7 @@ class Shrine
19
19
  def initialize(*)
20
20
  super
21
21
 
22
- module_eval <<-RUBY
22
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
23
23
  def initialize_copy(record)
24
24
  super
25
25
  @#{@name}_attacher = nil # reload the attacher
@@ -1,6 +1,6 @@
1
1
  require "base64"
2
2
  require "strscan"
3
- require "cgi/util"
3
+ require "cgi"
4
4
  require "stringio"
5
5
  require "forwardable"
6
6
 
@@ -169,7 +169,7 @@ class Shrine
169
169
  # Returns contents of the file base64-encoded.
170
170
  def base64
171
171
  binary = open { |io| io.read }
172
- result = Base64.encode64(binary).chomp
172
+ result = Base64.strict_encode64(binary)
173
173
  binary.clear # deallocate string
174
174
  result
175
175
  end
@@ -189,7 +189,7 @@ class Shrine
189
189
  end
190
190
 
191
191
  extend Forwardable
192
- delegate Shrine::IO_METHODS.keys => :@io
192
+ delegate [:read, :size, :rewind, :eof?] => :@io
193
193
 
194
194
  def close
195
195
  @io.close
@@ -30,7 +30,12 @@ class Shrine
30
30
  #
31
31
  # :mime_types
32
32
  # : Uses the [mime-types] gem to determine the MIME type from the file
33
- # *extension*. Note that unlike other solutions, this analyzer is not
33
+ # extension. Note that unlike other solutions, this analyzer is not
34
+ # guaranteed to return the actual MIME type of the file.
35
+ #
36
+ # :mini_mime
37
+ # : Uses the [mini_mime] gem to determine the MIME type from the file
38
+ # extension. Note that unlike other solutions, this analyzer is not
34
39
  # guaranteed to return the actual MIME type of the file.
35
40
  #
36
41
  # :default
@@ -65,6 +70,7 @@ class Shrine
65
70
  # [ruby-filemagic]: https://github.com/blackwinter/ruby-filemagic
66
71
  # [mimemagic]: https://github.com/minad/mimemagic
67
72
  # [mime-types]: https://github.com/mime-types/ruby-mime-types
73
+ # [mini_mime]: https://github.com/discourse/mini_mime
68
74
  module DetermineMimeType
69
75
  def self.configure(uploader, opts = {})
70
76
  uploader.opts[:mime_type_analyzer] = opts.fetch(:analyzer, uploader.opts.fetch(:mime_type_analyzer, :file))
@@ -115,7 +121,7 @@ class Shrine
115
121
  end
116
122
 
117
123
  class MimeTypeAnalyzer
118
- SUPPORTED_TOOLS = [:file, :filemagic, :mimemagic, :mime_types]
124
+ SUPPORTED_TOOLS = [:file, :filemagic, :mimemagic, :mime_types, :mini_mime]
119
125
  MAGIC_NUMBER = 256 * 1024
120
126
 
121
127
  def initialize(tool)
@@ -125,8 +131,11 @@ class Shrine
125
131
  end
126
132
 
127
133
  def call(io)
134
+ return nil if io.eof? # empty file doesn't have a MIME type
135
+
128
136
  mime_type = send(:"extract_with_#{@tool}", io)
129
137
  io.rewind
138
+
130
139
  mime_type
131
140
  end
132
141
 
@@ -135,8 +144,8 @@ class Shrine
135
144
  def extract_with_file(io)
136
145
  require "open3"
137
146
 
138
- cmd = ["file", "--mime-type", "--brief", "-"]
139
- options = {stdin_data: io.read(MAGIC_NUMBER), binmode: true}
147
+ cmd = %W[file --mime-type --brief -]
148
+ options = { stdin_data: io.read(MAGIC_NUMBER), binmode: true }
140
149
 
141
150
  begin
142
151
  stdout, stderr, status = Open3.capture3(*cmd, options)
@@ -168,15 +177,20 @@ class Shrine
168
177
  end
169
178
 
170
179
  def extract_with_mime_types(io)
171
- begin
172
- require "mime/types/columnar"
173
- rescue LoadError
174
- require "mime/types"
175
- end
180
+ require "mime/types"
176
181
 
177
182
  if filename = extract_filename(io)
178
183
  mime_type = MIME::Types.of(filename).first
179
- mime_type.to_s if mime_type
184
+ mime_type.content_type if mime_type
185
+ end
186
+ end
187
+
188
+ def extract_with_mini_mime(io)
189
+ require "mini_mime"
190
+
191
+ if filename = extract_filename(io)
192
+ info = MiniMime.lookup_by_filename(filename)
193
+ info.content_type if info
180
194
  end
181
195
  end
182
196