shrine 2.0.1 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a35f41e1e5941af4a9f726f6ff2057a3a65203f
4
- data.tar.gz: 17d53c374878d16e8d20f5c5e6819fb90bb45888
3
+ metadata.gz: 24f4131513e38de7fad11e2b540643b268026427
4
+ data.tar.gz: daad25a613048ed9c5c66a818050dddcd007fb6f
5
5
  SHA512:
6
- metadata.gz: 0ba8c4a7b7e65caa42e101a3197aa18fc9039c0b72abe82dc7d77641935ccfa5aa7f906d1ad6d1e1cad63aeb017d471d48df8646c5ead787ecea9ca2730cd2fd
7
- data.tar.gz: accad9b2d8743585c20ec36dac4f9d73b664bd6ab63687caf2ceb13c1536f4b185165d74c45e79ad43a5aee08cdc251240f8bed9f6e8567a92fc505f8a917279
6
+ metadata.gz: 23591ec752213879f06ddd761b371276ac00c7eef5830ea2360e343044526a2717aaae540db89da61753630c4b5f3c2d8d05f79b72a266a25722a72bb4759395
7
+ data.tar.gz: c10ce66b0ba150e4c29678850b19154f67adac844fb7c2e3f34f5ff6d92c74901089c5f0f93b9642f736b8928f1a9ba4531a4a08d4074561138e13a74b530947
data/README.md CHANGED
@@ -12,13 +12,70 @@ explains the motivation behind Shrine.
12
12
  - Bugs: [github.com/janko-m/shrine/issues](https://github.com/janko-m/shrine/issues)
13
13
  - Help & Discussion: [groups.google.com/group/ruby-shrine](https://groups.google.com/forum/#!forum/ruby-shrine)
14
14
 
15
- ## Installation
15
+ ## Quick start
16
+
17
+ Add Shrine to the Gemfile and write an initializer:
16
18
 
17
19
  ```rb
18
20
  gem "shrine"
19
21
  ```
20
22
 
21
- Shrine has been tested on MRI 2.1, MRI 2.2, MRI 2.3 and JRuby.
23
+ ```rb
24
+ require "shrine"
25
+ require "shrine/storage/file_system"
26
+
27
+ Shrine.storages = {
28
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
29
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
30
+ }
31
+
32
+ Shrine.plugin :sequel # :activerecord
33
+ Shrine.plugin :cached_attachment_data # for forms
34
+ ```
35
+
36
+ Next write a migration to add a column which will hold attachment data, and run
37
+ it:
38
+
39
+ ```rb
40
+ Sequel.migration do # class AddImageDataToPhotos < ActiveRecord::Migration
41
+ change do # def change
42
+ add_column :photos, :image_data, :text # add_column :photos, :image_data, :text
43
+ end # end
44
+ end # end
45
+ ```
46
+
47
+ Now you can create an uploader class for the type of files you want to upload,
48
+ and make your model handle attachments:
49
+
50
+ ```rb
51
+ class ImageUploader < Shrine
52
+ # plugins and uploading logic
53
+ end
54
+ ```
55
+
56
+ ```rb
57
+ class Photo < Sequel::Model # ActiveRecord::Base
58
+ include ImageUploader[:image]
59
+ end
60
+ ```
61
+
62
+ Finally, you can add the attachment fields to your form:
63
+
64
+ ```erb
65
+ <form action="/photos" method="post" enctype="multipart/form-data">
66
+ <input name="photo[image]" type="hidden" value="<%= @photo.cached_image_data %>">
67
+ <input name="photo[image]" type="file">
68
+ </form>
69
+
70
+ <!-- Rails: -->
71
+
72
+ <%= form_for @photo do |f| %>
73
+ <%= f.hidden_field :image, value: @photo.cached_image_data %>
74
+ <%= f.file_field :image %>
75
+ <% end %>
76
+ ```
77
+
78
+ ----------
22
79
 
23
80
  ## Basics
24
81
 
@@ -139,19 +196,24 @@ Shrine::Attachment.new(:document)
139
196
  * `#image` – returns a `Shrine::UploadedFile` based on data from `image_data`
140
197
  * `#image_url` – calls `image.url` if attachment is present, otherwise returns nil.
141
198
 
142
- This is how you would typically create the form for a `@photo`:
199
+ This is how you should create a form for a `@photo`:
143
200
 
201
+ ```rb
202
+ Shrine.plugin :cached_attachment_data
203
+ ```
144
204
  ```erb
145
205
  <form action="/photos" method="post" enctype="multipart/form-data">
146
- <input name="photo[image]" type="hidden" value="<%= @photo.image_data %>">
206
+ <input name="photo[image]" type="hidden" value="<%= @photo.cached_image_data %>">
147
207
  <input name="photo[image]" type="file">
148
208
  </form>
149
209
  ```
150
210
 
151
211
  The "file" field is for file upload, while the "hidden" field is to make the
152
- file persist in case of validation errors, and for direct uploads. This code
153
- works because `#image=` also accepts an already cached file via its JSON
154
- representation:
212
+ file persist in case of validation errors, and for direct uploads. Note that
213
+ the hidden field should always be *before* the file field.
214
+
215
+ This code works because `#image=` also accepts an already cached file via its
216
+ JSON representation (which is what `#cached_image_data` returns):
155
217
 
156
218
  ```rb
157
219
  photo.image = '{"id":"9jsdf02kd", "storage":"cache", "metadata": {...}}'
@@ -165,10 +227,10 @@ Sequel and ActiveRecord ORMs. It uses the `<attachment>_data` column for
165
227
  storing data for uploaded files, so you'll need to add it in a migration.
166
228
 
167
229
  ```rb
168
- add_column :movies, :video_data, :text # or a JSON column
230
+ Shrine.plugin :sequel # :activerecord
169
231
  ```
170
232
  ```rb
171
- Shrine.plugin :sequel # or :activerecord
233
+ add_column :movies, :video_data, :text
172
234
  ```
173
235
  ```rb
174
236
  class Movie < Sequel::Model
@@ -264,7 +326,7 @@ require "image_processing/mini_magick"
264
326
 
265
327
  class ImageUploader < Shrine
266
328
  include ImageProcessing::MiniMagick
267
- plugin :versions, names: [:large, :medium, :small]
329
+ plugin :versions
268
330
 
269
331
  def process(io, context)
270
332
  if context[:phase] == :store
@@ -528,7 +590,7 @@ See the documentation of the plugin for more details, as well as the
528
590
  [Roda](https://github.com/janko-m/shrine-example)/[Rails](https://github.com/erikdahlstrand/shrine-rails-example)
529
591
  example app which demonstrates multiple uploads directly to S3.
530
592
 
531
- ## Background jobs
593
+ ## Backgrounding
532
594
 
533
595
  Shrine is the first file upload library designed for backgrounding support.
534
596
  Moving phases of managing attachments to background jobs is essential for
data/doc/carrierwave.md CHANGED
@@ -50,7 +50,7 @@ require "image_processing/mini_magick" # part of the "image_processing" gem
50
50
 
51
51
  class ImageUploader < Shrine
52
52
  include ImageProcessing::MiniMagick
53
- plugin :versions, names: [:small, :medium, :large]
53
+ plugin :versions
54
54
 
55
55
  def process(io, context)
56
56
  if context[:phase] == :store
@@ -37,11 +37,11 @@ class Shrine
37
37
  # deletes the file from the storage
38
38
  end
39
39
 
40
- def url(id, options = {})
40
+ def url(id, **options)
41
41
  # URL to the remote file, accepts options for customizing the URL
42
42
  end
43
43
 
44
- def clear!(confirm = nil)
44
+ def clear!
45
45
  # deletes all the files in the storage
46
46
  end
47
47
  end
@@ -49,12 +49,19 @@ class Shrine
49
49
  end
50
50
  ```
51
51
 
52
+ ## Upload
53
+
54
+ The job of `Storage#upload` is to upload the given IO object to the storage.
55
+ It's good practice to test the storage with a [fake IO] object which responds
56
+ only to required methods. Some HTTP libraries don't support uploading non-file
57
+ IOs, although for [Faraday] and [REST client] you can work around that.
58
+
52
59
  If your storage doesn't control which id the uploaded file will have, you
53
- can modify the `id` variable:
60
+ can modify the `id` variable before returning:
54
61
 
55
62
  ```rb
56
63
  def upload(io, id, shrine_metadata: {}, **upload_options)
57
- actual_id = do_upload(io, id, metadata)
64
+ # ...
58
65
  id.replace(actual_id)
59
66
  end
60
67
  ```
@@ -64,12 +71,12 @@ you can modify the metadata hash:
64
71
 
65
72
  ```rb
66
73
  def upload(io, id, shrine_metadata: {}, **upload_options)
67
- additional_metadata = do_upload(io, id, metadata)
68
- metadata.merge!(additional_metadata)
74
+ # ...
75
+ shrine_metadata.merge!(returned_metadata)
69
76
  end
70
77
  ```
71
78
 
72
- ## Updating
79
+ ## Update
73
80
 
74
81
  If your storage supports updating data of existing files (e.g. some metadata),
75
82
  the convention is to create an `#update` method:
@@ -90,31 +97,7 @@ class Shrine
90
97
  end
91
98
  ```
92
99
 
93
- ## Streaming
94
-
95
- If your storage can stream files by yielding chunks, you can add an additional
96
- `#stream` method (used by the `download_endpoint` plugin):
97
-
98
- ```rb
99
- class Shrine
100
- module Storage
101
- class MyStorage
102
- # ...
103
-
104
- def stream(id)
105
- # yields chunks of the file
106
- end
107
-
108
- # ...
109
- end
110
- end
111
- end
112
- ```
113
-
114
- You should also yield the total filesize as the second argument, so that
115
- download_endpoint can set `Content-Length` before it starts streaming.
116
-
117
- ## Moving
100
+ ## Move
118
101
 
119
102
  If your storage can move files, you can add 2 additional methods, and they will
120
103
  automatically get used by the `moving` plugin:
@@ -195,3 +178,7 @@ linter = Shrine::Storage::Linter.new(storage, action: :warn)
195
178
  Note that using the linter doesn't mean that you shouldn't write any manual
196
179
  tests for your storage. There will likely be some edge cases that won't be
197
180
  tested by the linter.
181
+
182
+ [fake IO]: https://github.com/janko-m/shrine-cloudinary/blob/ca587c580ea0762992a2df33fd590c9a1e534905/test/test_helper.rb#L20-L27
183
+ [REST client]: https://github.com/janko-m/shrine-cloudinary/blob/ca587c580ea0762992a2df33fd590c9a1e534905/lib/shrine/storage/cloudinary.rb#L138-L141
184
+ [Faraday]: https://github.com/janko-m/shrine-uploadcare/blob/2038781ace0f54d82fa06cc04c4c2958919208ad/lib/shrine/storage/uploadcare.rb#L140
data/doc/paperclip.md CHANGED
@@ -56,7 +56,7 @@ require "image_processing/mini_magick" # part of the "image_processing" gem
56
56
 
57
57
  class ImageUploader < Shrine
58
58
  include ImageProcessing::MiniMagick
59
- plugin :versions, names: [:original, :thumb]
59
+ plugin :versions
60
60
 
61
61
  def process(io, context)
62
62
  if context[:phase] == :store
data/doc/refile.md CHANGED
@@ -29,7 +29,7 @@ end
29
29
 
30
30
  While in Refile you configure attachments by passing options to `.attachment`,
31
31
  in Shrine you define all your uploading logic inside uploaders, and then
32
- generate an attacment module with that uploader which is included into the
32
+ generate an attachment module with that uploader which is included into the
33
33
  model:
34
34
 
35
35
  ```rb
@@ -15,7 +15,7 @@ versions:
15
15
 
16
16
  ```rb
17
17
  class ImageUploader < Shrine
18
- plugin :versions, names: [:original, :thumb]
18
+ plugin :versions
19
19
 
20
20
  def process(io, context)
21
21
  case context[:phase]
@@ -75,7 +75,7 @@ update your processing code to generate it, and deploy it:
75
75
 
76
76
  ```rb
77
77
  class ImageUploader < Shrine
78
- plugin :versions, names: [:small, :medium, :new] # we add the ":new" version
78
+ plugin :versions
79
79
 
80
80
  def process(io, context)
81
81
  case context[:phase]
data/lib/shrine.rb CHANGED
@@ -19,7 +19,7 @@ class Shrine
19
19
  private
20
20
 
21
21
  def missing_methods_string
22
- @missing_methods.map { |m, args| "`#{m}(#{args.join(", ")})`" }.join(", ")
22
+ @missing_methods.map { |m, args| "##{m}" }.join(", ")
23
23
  end
24
24
  end
25
25
 
@@ -322,8 +322,11 @@ class Shrine
322
322
 
323
323
  # Does the actual uploading, calling `#upload` on the storage.
324
324
  def copy(io, context)
325
- context[:upload_options] = (context[:upload_options] || {}).merge(shrine_metadata: context[:metadata])
326
- storage.upload(io, context[:location], context[:upload_options])
325
+ location = context[:location]
326
+ metadata = context[:metadata]
327
+ upload_options = context[:upload_options] || {}
328
+
329
+ storage.upload(io, location, shrine_metadata: metadata, **upload_options)
327
330
  ensure
328
331
  io.close rescue nil
329
332
  end
@@ -358,10 +361,7 @@ class Shrine
358
361
  # `#read`, `#eof?`, `#rewind`, `#size` and `#close`, otherwise raises
359
362
  # Shrine::InvalidFile.
360
363
  def _enforce_io(io)
361
- missing_methods = IO_METHODS.reject do |m, a|
362
- io.respond_to?(m) && [a.count, -1].include?(io.method(m).arity)
363
- end
364
-
364
+ missing_methods = IO_METHODS.select { |m, a| !io.respond_to?(m) }
365
365
  raise InvalidFile.new(io, missing_methods) if missing_methods.any?
366
366
  end
367
367
 
@@ -469,7 +469,8 @@ class Shrine
469
469
  # Otherwise it assumes that it's an IO object and caches it.
470
470
  def assign(value)
471
471
  if value.is_a?(String)
472
- assign_cached(value) unless value == "" || value == read
472
+ return if value == "" || value == read || !cache.uploaded?(uploaded_file(value))
473
+ assign_cached(uploaded_file(value))
473
474
  else
474
475
  uploaded_file = cache!(value, phase: :cache) if value
475
476
  set(uploaded_file)
@@ -593,9 +594,8 @@ class Shrine
593
594
  private
594
595
 
595
596
  # Assigns a cached file (refuses if the file is stored).
596
- def assign_cached(value)
597
- cached_file = uploaded_file(value)
598
- set(cached_file) if cache.uploaded?(cached_file)
597
+ def assign_cached(cached_file)
598
+ set(cached_file)
599
599
  end
600
600
 
601
601
  # Sets and saves the uploaded file.
@@ -682,7 +682,7 @@ class Shrine
682
682
 
683
683
  # The filesize of the original file.
684
684
  def size
685
- Integer(metadata["size"]) if metadata["size"]
685
+ (@io && @io.size) || (metadata["size"] && Integer(metadata["size"]))
686
686
  end
687
687
 
688
688
  # The MIME type of the original file.
@@ -1,23 +1,19 @@
1
1
  class Shrine
2
2
  module Plugins
3
- # The cached_attachment_data adds a method for assigning cached files that
4
- # is more convenient for forms.
3
+ # The cached_attachment_data plugin adds the ability to retain the cached
4
+ # file across form redisplays, which means the file doesn't have to be
5
+ # reuploaded in case of validation errors.
5
6
  #
6
7
  # plugin :cached_attachment_data
7
8
  #
8
- # If for example your attachment is called "avatar", this plugin will add
9
- # `#cached_avatar_data` and `#cached_avatar_data=` methods to your model.
10
- # This allows you to write your hidden field without explicitly setting
11
- # `:value`:
9
+ # The plugin adds `#cached_<attachment>_data` to the model, which returns
10
+ # the cached file as JSON, and should be used to set the value of the
11
+ # hidden form field:
12
12
  #
13
13
  # <%= form_for @user do |f| %>
14
- # <%= f.hidden_field :cached_avatar_data %>
14
+ # <%= f.hidden_field :avatar, value: @user.cached_avatar_data %>
15
15
  # <%= f.field_field :avatar %>
16
16
  # <% end %>
17
- #
18
- # Additionally, the hidden field will only be set when the attachment is
19
- # cached (as opposed to the default where `user.avatar_data` will return
20
- # both cached and stored files). This keeps Rails logs cleaner.
21
17
  module CachedAttachmentData
22
18
  module AttachmentMethods
23
19
  def initialize(*)
@@ -29,6 +25,7 @@ class Shrine
29
25
  end
30
26
 
31
27
  def cached_#{@name}_data=(value)
28
+ warn "Calling #cached_#{@name}_data= is deprecated and will be removed in Shrine 3. You should use the original field name: `f.hidden_field :#{@name}, value: record.cached_#{@name}_data`."
32
29
  #{@name}_attacher.assign(value)
33
30
  end
34
31
  RUBY
@@ -37,7 +34,7 @@ class Shrine
37
34
 
38
35
  module AttacherMethods
39
36
  def read_cached
40
- get.to_json if get && cache.uploaded?(get)
37
+ get.to_json if cached? && attached?
41
38
  end
42
39
  end
43
40
  end
@@ -50,12 +50,12 @@ class Shrine
50
50
  # [mime-types]: https://github.com/mime-types/ruby-mime-types
51
51
  module DetermineMimeType
52
52
  def self.configure(uploader, opts = {})
53
- uploader.opts[:mime_type_analyzer] = opts.fetch(:analyzer, :file)
53
+ uploader.opts[:mime_type_analyzer] = opts.fetch(:analyzer, uploader.opts.fetch(:mime_type_analyzer, :file))
54
+ uploader.opts[:mime_type_magic_header] = opts.fetch(:magic_header, uploader.opts.fetch(:mime_type_magic_header, MAGIC_NUMBER))
54
55
  end
55
56
 
56
- # How many bytes we have to read to get the magic file header which
57
- # contains the MIME type of the file.
58
- MAGIC_NUMBER = 1024
57
+ # How many bytes we need to read in order to determine the MIME type.
58
+ MAGIC_NUMBER = 256 * 1024
59
59
 
60
60
  module InstanceMethods
61
61
  private
@@ -83,14 +83,8 @@ class Shrine
83
83
  def _extract_mime_type_with_file(io)
84
84
  require "open3"
85
85
 
86
- cmd = ["file", "--mime-type", "--brief", "--"]
87
-
88
- if io.respond_to?(:path)
89
- mime_type, * = Open3.capture2(*cmd, io.path)
90
- else
91
- mime_type, * = Open3.capture2(*cmd, "-", stdin_data: io.read(MAGIC_NUMBER), binmode: true)
92
- io.rewind
93
- end
86
+ mime_type, status = Open3.capture2("file", "--mime-type", "--brief", "-",
87
+ stdin_data: magic_header(io), binmode: true)
94
88
 
95
89
  mime_type.strip unless mime_type.empty?
96
90
  end
@@ -108,9 +102,7 @@ class Shrine
108
102
  require "filemagic"
109
103
 
110
104
  filemagic = FileMagic.new(FileMagic::MAGIC_MIME_TYPE)
111
- mime_type = filemagic.buffer(io.read(MAGIC_NUMBER))
112
-
113
- io.rewind
105
+ mime_type = filemagic.buffer(magic_header(io))
114
106
  filemagic.close
115
107
 
116
108
  mime_type
@@ -128,6 +120,12 @@ class Shrine
128
120
  mime_type.to_s if mime_type
129
121
  end
130
122
  end
123
+
124
+ def magic_header(io)
125
+ content = io.read(opts[:mime_type_magic_header])
126
+ io.rewind
127
+ content
128
+ end
131
129
  end
132
130
  end
133
131
 
@@ -1,4 +1,5 @@
1
1
  require "roda"
2
+ require "down"
2
3
 
3
4
  class Shrine
4
5
  module Plugins
@@ -106,15 +107,17 @@ class Shrine
106
107
  response["Content-Disposition"] = "#{disposition}; filename=#{filename.inspect}"
107
108
  response["Content-Type"] = Rack::Mime.mime_type(extname)
108
109
 
109
- stream = get_stream(id)
110
- _, content_length = stream.peek
111
- response['Content-Length'] = content_length.to_s if content_length
110
+ io = get_stream_io(id)
111
+ response["Content-Length"] = io.size.to_s if io.size
112
112
 
113
113
  chunks = Enumerator.new do |y|
114
- loop do
115
- chunk, * = stream.next
116
- y << chunk
114
+ if io.respond_to?(:each_chunk)
115
+ io.each_chunk { |chunk| y.yield(chunk) }
116
+ else
117
+ y.yield io.read(16*1024, buffer ||= "") until io.eof?
117
118
  end
119
+ io.close
120
+ io.delete if io.class.name == "Tempfile"
118
121
  end
119
122
 
120
123
  r.halt response.finish_with_body(chunks)
@@ -131,17 +134,14 @@ class Shrine
131
134
  shrine_class.find_storage(storage_key)
132
135
  end
133
136
 
134
- def get_stream(id)
137
+ def get_stream_io(id)
135
138
  if storage.respond_to?(:stream)
136
- storage.enum_for(:stream, id)
139
+ warn "Storage#stream is deprecated, in Shrine 3 the download_endpoint plugin will use only Storage#open. You should update your storage library."
140
+ stream = storage.enum_for(:stream, id)
141
+ chunks = Enumerator.new { |y| y << Array(stream.next)[0] }
142
+ Down::ChunkedIO.new(size: Array(stream.peek)[1], chunks: chunks)
137
143
  else
138
- Enumerator.new do |y|
139
- io = storage.open(id)
140
- buffer = ""
141
- y.yield(io.read(16*1024, buffer), io.size) until io.eof?
142
- io.close
143
- io.delete if io.class.name == "Tempfile"
144
- end
144
+ storage.open(id)
145
145
  end
146
146
  end
147
147
 
@@ -1,16 +1,12 @@
1
1
  class Shrine
2
2
  module Plugins
3
3
  # The included plugin allows you to hook up to the `.included` hook of the
4
- # attachment module, and do additional action on the model which includes
4
+ # attachment module, and call additional methods on the model which includes
5
5
  # it.
6
6
  #
7
7
  # plugin :included do |name|
8
- # define_method("#{name}_width") do
9
- # send(name).width if send(name)
10
- # end
11
- #
12
- # define_method("#{name}_height") do
13
- # send(name).height if send(name)
8
+ # before_save do
9
+ # # ...
14
10
  # end
15
11
  # end
16
12
  #
@@ -82,20 +82,20 @@ class Shrine
82
82
  end
83
83
 
84
84
  module InstanceMethods
85
- def around_process(io, context)
86
- log("process", io, context) { super }
87
- end
88
-
89
- def around_store(io, context)
85
+ def store(io, context = {})
90
86
  log("store", io, context) { super }
91
87
  end
92
88
 
93
- def around_delete(io, context)
89
+ def delete(io, context = {})
94
90
  log("delete", io, context) { super }
95
91
  end
96
92
 
97
93
  private
98
94
 
95
+ def processed(io, context = {})
96
+ log("process", io, context) { super }
97
+ end
98
+
99
99
  # Collects the data and sends it for logging.
100
100
  def log(action, input, context)
101
101
  result, duration = benchmark { yield }
@@ -7,17 +7,30 @@ class Shrine
7
7
  #
8
8
  # To add a module to a core class, call the appropriate method:
9
9
  #
10
- # Shrine.attachment_module CustomAttachmentMethods
11
- # Shrine.attacher_module CustomAttacherMethods
12
- # Shrine.file_module CustomFileMethods
10
+ # attachment_module CustomAttachmentMethods
11
+ # attacher_module CustomAttacherMethods
12
+ # file_module CustomFileMethods
13
13
  #
14
14
  # Alternatively you can pass in a block (which internally creates a module):
15
15
  #
16
- # Shrine.file_module do
17
- # def base64
18
- # Base64.encode64(read)
16
+ # attachment_module do
17
+ # def included(model)
18
+ # super
19
+ #
20
+ # module_eval <<-RUBY, __FILE__, __LINE + 1
21
+ # def #{@name}_size(version)
22
+ # if #{@name}.is_a?(Hash)
23
+ # #{@name}[version].size
24
+ # else
25
+ # #{@name}.size if #{@name}
26
+ # end
27
+ # end
28
+ # RUBY
19
29
  # end
20
30
  # end
31
+ #
32
+ # The above defines an additional `#<attachment>_size` method on the
33
+ # attachment module, which is what is included in your model.
21
34
  module ModuleInclude
22
35
  module ClassMethods
23
36
  def attachment_module(mod = nil, &block)
@@ -29,8 +29,11 @@ class Shrine
29
29
  end
30
30
 
31
31
  def move(io, context)
32
- context[:upload_options] = (context[:upload_options] || {}).merge(shrine_metadata: context[:metadata])
33
- storage.move(io, context[:location], context[:upload_options])
32
+ location = context[:location]
33
+ metadata = context[:metadata]
34
+ upload_options = context[:upload_options] || {}
35
+
36
+ storage.move(io, location, shrine_metadata: metadata, **upload_options)
34
37
  end
35
38
 
36
39
  def movable?(io, context)
@@ -15,13 +15,17 @@ class Shrine
15
15
  uploader.opts[:parallelize_threads] = opts.fetch(:threads, uploader.opts.fetch(:parallelize_threads, 3))
16
16
  end
17
17
 
18
+ def self.load_dependencies(uploader, opts = {})
19
+ uploader.plugin :hooks
20
+ end
21
+
18
22
  module InstanceMethods
19
- def store(io, context = {})
20
- with_pool { |pool| super(io, thread_pool: pool, **context) }
23
+ def around_store(io, context)
24
+ with_pool { |pool| super(io, context.update(thread_pool: pool)) }
21
25
  end
22
26
 
23
- def delete(uploaded_file, context = {})
24
- with_pool { |pool| super(uploaded_file, thread_pool: pool, **context) }
27
+ def around_delete(uploaded_file, context)
28
+ with_pool { |pool| super(uploaded_file, context.update(thread_pool: pool)) }
25
29
  end
26
30
 
27
31
  private
@@ -41,31 +45,31 @@ class Shrine
41
45
  pool.perform
42
46
  result
43
47
  end
48
+ end
44
49
 
45
- class ThreadPool
46
- def initialize(size)
47
- @size = size
48
- @tasks = Queue.new
49
- end
50
+ class ThreadPool
51
+ def initialize(size)
52
+ @size = size
53
+ @tasks = Queue.new
54
+ end
50
55
 
51
- def enqueue(&task)
52
- @tasks.enq(task)
53
- end
56
+ def enqueue(&task)
57
+ @tasks.enq(task)
58
+ end
54
59
 
55
- def perform
56
- threads = @size.times.map { spawn_thread }
57
- threads.each(&:join)
58
- end
60
+ def perform
61
+ threads = @size.times.map { spawn_thread }
62
+ threads.each(&:join)
63
+ end
59
64
 
60
- private
65
+ private
61
66
 
62
- def spawn_thread
63
- Thread.new do
64
- Thread.current.abort_on_exception = true
65
- loop do
66
- task = @tasks.deq(true) rescue break
67
- task.call
68
- end
67
+ def spawn_thread
68
+ Thread.new do
69
+ Thread.current.abort_on_exception = true
70
+ loop do
71
+ task = @tasks.deq(true) rescue break
72
+ task.call
69
73
  end
70
74
  end
71
75
  end
@@ -10,12 +10,12 @@ class Shrine
10
10
  module AttacherMethods
11
11
  private
12
12
 
13
- def assign_cached(value)
14
- cached_file = uploaded_file(value) do |cached_file|
15
- next unless cache.uploaded?(cached_file)
16
- real_metadata = cache.extract_metadata(cached_file.to_io, context)
17
- cached_file.metadata.update(real_metadata)
18
- cached_file.close
13
+ def assign_cached(cached_file)
14
+ uploaded_file(cached_file) do |file|
15
+ file.to_io # open
16
+ real_metadata = cache.extract_metadata(file, context)
17
+ file.metadata.update(real_metadata)
18
+ file.close
19
19
  end
20
20
 
21
21
  super(cached_file)
@@ -9,7 +9,7 @@ class Shrine
9
9
  #
10
10
  # class ImageUploader < Shrine
11
11
  # include ImageProcessing::MiniMagick
12
- # plugin :versions, names: [:large, :medium, :small]
12
+ # plugin :versions
13
13
  #
14
14
  # def process(io, context)
15
15
  # if context[:phase] == :store
@@ -77,9 +77,7 @@ class Shrine
77
77
  # If you already have some versions processed in the foreground when a
78
78
  # background job is kicked off, you can setup explicit URL fallbacks:
79
79
  #
80
- # plugin :versions,
81
- # names: [:thumb, :thumb_2x, :large, :large_2x],
82
- # fallbacks: {:thumb_2x => :thumb, :large_2x => :large}
80
+ # plugin :versions, fallbacks: {:thumb_2x => :thumb, :large_2x => :large}
83
81
  #
84
82
  # # ... (background job is kicked off)
85
83
  #
@@ -112,14 +110,15 @@ class Shrine
112
110
  end
113
111
 
114
112
  def self.configure(uploader, opts = {})
113
+ warn "The versions Shrine plugin doesn't need the :names option anymore, you can safely remove it." if opts.key?(:names)
114
+
115
115
  uploader.opts[:version_names] = opts.fetch(:names, uploader.opts[:version_names])
116
116
  uploader.opts[:version_fallbacks] = opts.fetch(:fallbacks, uploader.opts.fetch(:version_fallbacks, {}))
117
-
118
- raise Error, "The :names option is required for versions plugin" if uploader.opts[:version_names].nil?
119
117
  end
120
118
 
121
119
  module ClassMethods
122
120
  def version_names
121
+ warn "Shrine.version_names is deprecated and will be removed in Shrine 3."
123
122
  opts[:version_names]
124
123
  end
125
124
 
@@ -129,14 +128,14 @@ class Shrine
129
128
 
130
129
  # Checks that the identifier is a registered version.
131
130
  def version?(name)
132
- version_names.map(&:to_s).include?(name.to_s)
131
+ warn "Shrine.version? is deprecated and will be removed in Shrine 3."
132
+ version_names.nil? || version_names.map(&:to_s).include?(name.to_s)
133
133
  end
134
134
 
135
135
  # Converts a hash of data into a hash of versions.
136
136
  def uploaded_file(object, &block)
137
137
  if (hash = object).is_a?(Hash) && !hash.key?("storage")
138
138
  hash.inject({}) do |result, (name, data)|
139
- next result if !version?(name)
140
139
  result.update(name.to_sym => uploaded_file(data, &block))
141
140
  end
142
141
  else
@@ -165,7 +164,6 @@ class Shrine
165
164
  if (hash = io).is_a?(Hash)
166
165
  raise Error, ":location is not applicable to versions" if context.key?(:location)
167
166
  hash.inject({}) do |result, (name, version)|
168
- raise Error, "unknown version: #{name.inspect}" if !self.class.version?(name)
169
167
  result.update(name => _store(version, version: name, **context))
170
168
  end
171
169
  else
@@ -190,7 +188,6 @@ class Shrine
190
188
  def url(version = nil, **options)
191
189
  if get.is_a?(Hash)
192
190
  if version
193
- raise Error, "unknown version: #{version.inspect}" if !shrine_class.version?(version)
194
191
  if file = get[version]
195
192
  file.url(**options)
196
193
  elsif fallback = shrine_class.version_fallbacks[version]
@@ -209,6 +206,14 @@ class Shrine
209
206
  end
210
207
  end
211
208
  end
209
+
210
+ private
211
+
212
+ def assign_cached(value)
213
+ cached_file = uploaded_file(value)
214
+ warn "Generating versions in the :cache phase is deprecated and will be forbidden in Shrine 3." if cached_file.is_a?(Hash)
215
+ super(cached_file)
216
+ end
212
217
  end
213
218
  end
214
219
 
@@ -40,7 +40,6 @@ class Shrine
40
40
  lint_read(id)
41
41
  lint_exists(id)
42
42
  lint_url(id)
43
- lint_stream(id) if storage.respond_to?(:stream)
44
43
  lint_delete(id)
45
44
 
46
45
  if storage.respond_to?(:move)
@@ -67,6 +66,7 @@ class Shrine
67
66
  opened = storage.open(id)
68
67
  error :open, "doesn't return a valid IO object" if !io?(opened)
69
68
  error :open, "returns an empty IO object" if opened.read.empty?
69
+ opened.close
70
70
  end
71
71
 
72
72
  def lint_read(id)
@@ -85,20 +85,6 @@ class Shrine
85
85
  error :url, "should return either nil or a string" if !(url.nil? || url.is_a?(String))
86
86
  end
87
87
 
88
- def lint_stream(id)
89
- streamed = storage.enum_for(:stream, id).to_a
90
- chunks = streamed.map { |(chunk, _)| chunk }
91
- content_length = Array(streamed[0])[1]
92
-
93
- error :stream, "doesn't yield any chunks" if chunks.empty?
94
- error :stream, "yielded chunks sum up to empty content" if chunks.inject("", :+).empty?
95
-
96
- if Array(streamed.first).size == 2
97
- error :stream, "yielded content length isn't a number" if !content_length.is_a?(Integer)
98
- error :stream, "yielded chunks don't sum up to given content length" if content_length != chunks.inject("", :+).length
99
- end
100
- end
101
-
102
88
  def lint_delete(id)
103
89
  storage.delete(id)
104
90
  error :delete, "file still #exists? after deleting" if storage.exists?(id)
@@ -75,7 +75,7 @@ class Shrine
75
75
  # [presigning]: http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#presigned_post-instance_method
76
76
  # [aws-sdk]: https://github.com/aws/aws-sdk-ruby
77
77
  class S3
78
- attr_reader :prefix, :bucket, :s3, :host, :upload_options
78
+ attr_reader :s3, :bucket, :prefix, :host, :upload_options
79
79
 
80
80
  # Initializes a storage for uploading to S3.
81
81
  #
@@ -133,15 +133,9 @@ class Shrine
133
133
  Down.download(url(id), ssl_ca_cert: Aws.config[:ssl_ca_bundle])
134
134
  end
135
135
 
136
- # Streams the object from S3, yielding downloaded chunks.
137
- def stream(id)
138
- object = object(id)
139
- object.get { |chunk| yield chunk, object.content_length }
140
- end
141
-
142
136
  # Alias for #download.
143
137
  def open(id)
144
- download(id)
138
+ Down.open(url(id), ssl_ca_cert: Aws.config[:ssl_ca_bundle])
145
139
  end
146
140
 
147
141
  # Returns the contents of the file as a String.
@@ -218,6 +212,17 @@ class Shrine
218
212
  object(id).presigned_post(options)
219
213
  end
220
214
 
215
+ # Catches the deprecated `#stream` method.
216
+ def method_missing(name, *args)
217
+ if name == :stream
218
+ warn "Shrine::Storage::S3#stream is deprecated over calling #each_chunk on S3#open."
219
+ object = object(*args)
220
+ object.get { |chunk| yield chunk, object.content_length }
221
+ else
222
+ super
223
+ end
224
+ end
225
+
221
226
  protected
222
227
 
223
228
  # Returns the S3 object.
@@ -5,8 +5,8 @@ class Shrine
5
5
 
6
6
  module VERSION
7
7
  MAJOR = 2
8
- MINOR = 0
9
- TINY = 1
8
+ MINOR = 1
9
+ TINY = 0
10
10
  PRE = nil
11
11
 
12
12
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
data/shrine.gemspec CHANGED
@@ -25,7 +25,7 @@ column, and tying them to record's lifecycle.
25
25
  gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "shrine.gemspec", "doc/*.md"]
26
26
  gem.require_path = "lib"
27
27
 
28
- gem.add_dependency "down", ">= 2.2.0"
28
+ gem.add_dependency "down", ">= 2.3.3"
29
29
 
30
30
  gem.add_development_dependency "rake", "~> 11.1"
31
31
  gem.add_development_dependency "minitest", "~> 5.8"
@@ -34,13 +34,13 @@ column, and tying them to record's lifecycle.
34
34
  gem.add_development_dependency "webmock"
35
35
  gem.add_development_dependency "rack-test_app"
36
36
  gem.add_development_dependency "dotenv"
37
- gem.add_development_dependency "shrine-memory", "~> 0.2.0"
37
+ gem.add_development_dependency "shrine-memory", ">= 0.2.1"
38
38
 
39
39
  gem.add_development_dependency "roda"
40
40
  gem.add_development_dependency "mimemagic"
41
41
  gem.add_development_dependency "mime-types"
42
42
  gem.add_development_dependency "fastimage"
43
- gem.add_development_dependency "aws-sdk", "~> 2.1.30"
43
+ gem.add_development_dependency "aws-sdk"
44
44
 
45
45
  unless RUBY_ENGINE == "jruby" || ENV["CI"]
46
46
  gem.add_development_dependency "ruby-filemagic", "~> 0.7"
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: 2.0.1
4
+ version: 2.1.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-05-30 00:00:00.000000000 Z
11
+ date: 2016-06-26 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: 2.2.0
19
+ version: 2.3.3
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: 2.2.0
26
+ version: 2.3.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -126,16 +126,16 @@ dependencies:
126
126
  name: shrine-memory
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - "~>"
129
+ - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: 0.2.0
131
+ version: 0.2.1
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - "~>"
136
+ - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: 0.2.0
138
+ version: 0.2.1
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: roda
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -196,16 +196,16 @@ dependencies:
196
196
  name: aws-sdk
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
- - - "~>"
199
+ - - ">="
200
200
  - !ruby/object:Gem::Version
201
- version: 2.1.30
201
+ version: '0'
202
202
  type: :development
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
- - - "~>"
206
+ - - ">="
207
207
  - !ruby/object:Gem::Version
208
- version: 2.1.30
208
+ version: '0'
209
209
  - !ruby/object:Gem::Dependency
210
210
  name: ruby-filemagic
211
211
  requirement: !ruby/object:Gem::Requirement