leifcr-refile 0.6.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/app/assets/javascripts/refile.js +4 -1
- data/lib/refile.rb +12 -2
- data/lib/refile/attacher.rb +7 -7
- data/lib/refile/attachment.rb +58 -0
- data/lib/refile/attachment/active_record.rb +16 -41
- data/lib/refile/attachment/multiple_attachments.rb +54 -0
- data/lib/refile/download.rb +98 -0
- data/lib/refile/rails/attachment_helper.rb +4 -1
- data/lib/refile/version.rb +1 -1
- data/spec/refile/active_record_helper.rb +1 -1
- data/spec/refile/attachment/active_record_spec.rb +22 -97
- data/spec/refile/attachment_helper_spec.rb +27 -4
- data/spec/refile/attachment_spec.rb +55 -1
- data/spec/refile/download_spec.rb +51 -0
- data/spec/refile/features/normal_upload_spec.rb +1 -1
- data/spec/refile/features/single_upload_spec.rb +38 -0
- data/spec/refile/support/accepts_attachments_for_shared_examples.rb +232 -0
- metadata +8 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 207442fab5c7ffc212b6a306de4750f4c400c616c6f04bcca3d497b9cdc95cbb
|
4
|
+
data.tar.gz: '09ec1056de2707eeee3586a6b0bcc4ab67a88adcd4d4ba81ab14d2d63a64881d'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d63019644aa26fbfae2998559b4c43fceb148fd018a71bd78d1a8bb59b49f7db62c1190f6f2c01735f3fdb34659876594444003d512fb66d863a5517a314c09
|
7
|
+
data.tar.gz: 1415e64857f076ff3ed80c419560661e03d9a827c32e4f742c844b6289a529299101060f8fef3a4fa0b8a0f4d8f0bdcee109c895208fd26b972a008006b11888
|
@@ -115,7 +115,10 @@
|
|
115
115
|
return data;
|
116
116
|
});
|
117
117
|
if(!input.multiple) dataObj = dataObj[0];
|
118
|
-
if(metadataField)
|
118
|
+
if(metadataField) {
|
119
|
+
metadataField.value = JSON.stringify(dataObj);
|
120
|
+
metadataField.removeAttribute('disabled');
|
121
|
+
};
|
119
122
|
|
120
123
|
input.removeAttribute("name");
|
121
124
|
}
|
data/lib/refile.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
require "uri"
|
2
2
|
require "fileutils"
|
3
3
|
require "tempfile"
|
4
|
-
require "rest_client"
|
5
4
|
require "logger"
|
6
5
|
require "mime/types"
|
7
6
|
|
8
7
|
module Refile
|
9
8
|
# @api private
|
10
|
-
class
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
class Invalid < Error; end
|
11
13
|
|
12
14
|
# @api private
|
13
15
|
class InvalidID < Invalid; end
|
@@ -18,6 +20,12 @@ module Refile
|
|
18
20
|
# @api private
|
19
21
|
class InvalidFile < Invalid; end
|
20
22
|
|
23
|
+
# Raised when the given URL couldn't be parsed.
|
24
|
+
class InvalidUrl < Error; end
|
25
|
+
|
26
|
+
# Raised when the given URL redirects more than allowed.
|
27
|
+
class TooManyRedirects < Error; end
|
28
|
+
|
21
29
|
# @api private
|
22
30
|
class Confirm < StandardError
|
23
31
|
def message
|
@@ -489,8 +497,10 @@ module Refile
|
|
489
497
|
require "refile/type"
|
490
498
|
require "refile/backend_macros"
|
491
499
|
require "refile/attachment_definition"
|
500
|
+
require "refile/download"
|
492
501
|
require "refile/attacher"
|
493
502
|
require "refile/attachment"
|
503
|
+
require "refile/attachment/multiple_attachments"
|
494
504
|
require "refile/random_hasher"
|
495
505
|
require "refile/file"
|
496
506
|
require "refile/custom_logger"
|
data/lib/refile/attacher.rb
CHANGED
@@ -73,6 +73,7 @@ module Refile
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def set(value)
|
76
|
+
self.remove = false
|
76
77
|
case value
|
77
78
|
when nil then self.remove = true
|
78
79
|
when String, Hash then retrieve!(value)
|
@@ -105,21 +106,20 @@ module Refile
|
|
105
106
|
|
106
107
|
def download(url)
|
107
108
|
unless url.to_s.empty?
|
108
|
-
|
109
|
+
download = Refile::Download.new(url)
|
109
110
|
@metadata = {
|
110
|
-
size:
|
111
|
-
filename:
|
112
|
-
content_type:
|
111
|
+
size: download.size,
|
112
|
+
filename: download.original_filename,
|
113
|
+
content_type: download.content_type
|
113
114
|
}
|
114
115
|
if valid?
|
115
|
-
|
116
|
-
@metadata[:id] = cache.upload(response.file).id
|
116
|
+
@metadata[:id] = cache.upload(download.io).id
|
117
117
|
write_metadata
|
118
118
|
elsif @definition.raise_errors?
|
119
119
|
raise Refile::Invalid, @errors.join(", ")
|
120
120
|
end
|
121
121
|
end
|
122
|
-
rescue
|
122
|
+
rescue Refile::Error
|
123
123
|
@errors = [:download_failed]
|
124
124
|
raise if @definition.raise_errors?
|
125
125
|
end
|
data/lib/refile/attachment.rb
CHANGED
@@ -104,5 +104,63 @@ module Refile
|
|
104
104
|
|
105
105
|
include mod
|
106
106
|
end
|
107
|
+
|
108
|
+
# Macro which generates accessors in pure Ruby classes for assigning
|
109
|
+
# multiple attachments at once. This is primarily useful together with
|
110
|
+
# multiple file uploads. There is also an Active Record version of
|
111
|
+
# this macro.
|
112
|
+
#
|
113
|
+
# The name of the generated accessors will be the name of the association
|
114
|
+
# (represented by an attribute accessor) and the name of the attachment in
|
115
|
+
# the associated class. So if a `Post` accepts attachments for `images`, and
|
116
|
+
# the attachment in the `Image` class is named `file`, then the accessors will
|
117
|
+
# be named `images_files`.
|
118
|
+
#
|
119
|
+
# @example in associated class
|
120
|
+
# class Document
|
121
|
+
# extend Refile::Attachment
|
122
|
+
# attr_accessor :file_id
|
123
|
+
#
|
124
|
+
# attachment :file
|
125
|
+
#
|
126
|
+
# def initialize(attributes = {})
|
127
|
+
# self.file = attributes[:file]
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# @example in class
|
132
|
+
# class Post
|
133
|
+
# extend Refile::Attachment
|
134
|
+
# include ActiveModel::Model
|
135
|
+
#
|
136
|
+
# attr_accessor :documents
|
137
|
+
#
|
138
|
+
# accepts_attachments_for :documents, accessor_prefix: 'documents_files', collection_class: Document
|
139
|
+
#
|
140
|
+
# def initialize(attributes = {})
|
141
|
+
# @documents = attributes[:documents] || []
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# @example in form
|
146
|
+
# <%= form_for @post do |form| %>
|
147
|
+
# <%= form.attachment_field :documents_files, multiple: true %>
|
148
|
+
# <% end %>
|
149
|
+
#
|
150
|
+
# @param [Symbol] collection_name Name of the association
|
151
|
+
# @param [Class] collection_class Associated class
|
152
|
+
# @param [String] accessor_prefix Name of the generated accessors
|
153
|
+
# @param [Symbol] attachment Name of the attachment in the associated class
|
154
|
+
# @param [Boolean] append If true, new files are appended instead of replacing the entire list of associated classes.
|
155
|
+
# @return [void]
|
156
|
+
def accepts_attachments_for(collection_name, collection_class:, accessor_prefix:, attachment: :file, append: false)
|
157
|
+
include MultipleAttachments.new(
|
158
|
+
collection_name,
|
159
|
+
collection_class: collection_class,
|
160
|
+
name: accessor_prefix,
|
161
|
+
attachment: attachment,
|
162
|
+
append: append
|
163
|
+
)
|
164
|
+
end
|
107
165
|
end
|
108
166
|
end
|
@@ -47,8 +47,9 @@ module Refile
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
# Macro which generates accessors
|
51
|
-
# once. This is primarily useful together with
|
50
|
+
# Macro which generates accessors in Active Record classes for assigning
|
51
|
+
# multiple attachments at once. This is primarily useful together with
|
52
|
+
# multiple file uploads. There is also a pure Ruby version of this macro.
|
52
53
|
#
|
53
54
|
# The name of the generated accessors will be the name of the association
|
54
55
|
# and the name of the attachment in the associated model. So if a `Post`
|
@@ -71,55 +72,29 @@ module Refile
|
|
71
72
|
# <%= form.attachment_field :images_files, multiple: true %>
|
72
73
|
# <% end %>
|
73
74
|
#
|
74
|
-
# @param [Symbol]
|
75
|
-
# @param [Symbol]
|
76
|
-
# @param [
|
75
|
+
# @param [Symbol] association_name Name of the association
|
76
|
+
# @param [Symbol] attachment Name of the attachment in the associated model
|
77
|
+
# @param [Boolean] append If true, new files are appended instead of replacing the entire list of associated models.
|
77
78
|
# @return [void]
|
78
79
|
def accepts_attachments_for(association_name, attachment: :file, append: false)
|
79
80
|
association = reflect_on_association(association_name)
|
80
81
|
attachment_pluralized = attachment.to_s.pluralize
|
81
82
|
name = "#{association_name}_#{attachment_pluralized}"
|
83
|
+
collection_class = association && association.klass
|
82
84
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
options = {
|
86
|
+
collection_class: collection_class,
|
87
|
+
name: name,
|
88
|
+
attachment: attachment,
|
89
|
+
append: append
|
90
|
+
}
|
87
91
|
|
88
|
-
|
92
|
+
mod = MultipleAttachments.new association_name, **options do
|
93
|
+
define_method(:method_missing) do |method, *args|
|
89
94
|
if method == attachment_pluralized.to_sym
|
90
95
|
raise NoMethodError, "wrong association name #{method}, use like this #{name}"
|
91
96
|
else
|
92
|
-
super(method)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
define_method :"#{name}_data" do
|
97
|
-
if send(association_name).all? { |record| record.send("#{attachment}_attacher").valid? }
|
98
|
-
send(association_name).map(&:"#{attachment}_data").select(&:present?)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
define_method :"#{name}" do
|
103
|
-
send(association_name).map(&attachment)
|
104
|
-
end
|
105
|
-
|
106
|
-
define_method :"#{name}=" do |files|
|
107
|
-
cache, files = files.partition { |file| file.is_a?(String) }
|
108
|
-
|
109
|
-
cache = Refile.parse_json(cache.first)
|
110
|
-
|
111
|
-
if not append and (files.present? or cache.present?)
|
112
|
-
send("#{association_name}=", [])
|
113
|
-
end
|
114
|
-
|
115
|
-
if files.empty? and cache.present?
|
116
|
-
cache.select(&:present?).each do |file|
|
117
|
-
send(association_name).build(attachment => file.to_json)
|
118
|
-
end
|
119
|
-
else
|
120
|
-
files.select(&:present?).each do |file|
|
121
|
-
send(association_name).build(attachment => file)
|
122
|
-
end
|
97
|
+
super(method, *args)
|
123
98
|
end
|
124
99
|
end
|
125
100
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Refile
|
2
|
+
module Attachment
|
3
|
+
# Builds a module to be used by "accepts_attachments_for"
|
4
|
+
#
|
5
|
+
# @api private
|
6
|
+
module MultipleAttachments
|
7
|
+
def self.new(collection_name, collection_class:, name:, attachment:, append:, &block)
|
8
|
+
Module.new do
|
9
|
+
define_method :"#{name}_attachment_definition" do
|
10
|
+
collection_class.send("#{attachment}_attachment_definition")
|
11
|
+
end
|
12
|
+
|
13
|
+
define_method :"#{name}_data" do
|
14
|
+
collection = send(collection_name)
|
15
|
+
|
16
|
+
all_attachers_valid = collection.all? do |record|
|
17
|
+
record.send("#{attachment}_attacher").valid?
|
18
|
+
end
|
19
|
+
|
20
|
+
collection.map(&:"#{attachment}_data") if all_attachers_valid
|
21
|
+
end
|
22
|
+
|
23
|
+
define_method :"#{name}" do
|
24
|
+
send(collection_name).map(&attachment)
|
25
|
+
end
|
26
|
+
|
27
|
+
define_method :"#{name}=" do |files|
|
28
|
+
cache, files = [files].flatten.partition { |file| file.is_a?(String) }
|
29
|
+
cache = Refile.parse_json(cache.first) || []
|
30
|
+
cache = cache.reject(&:empty?)
|
31
|
+
files = files.compact
|
32
|
+
|
33
|
+
if not append and (!files.empty? or !cache.empty?)
|
34
|
+
send("#{collection_name}=", [])
|
35
|
+
end
|
36
|
+
|
37
|
+
collection = send(collection_name)
|
38
|
+
|
39
|
+
if files.empty? and !cache.empty?
|
40
|
+
cache.each do |file|
|
41
|
+
collection << collection_class.new(attachment => file.to_json)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
files.each do |file|
|
45
|
+
collection << collection_class.new(attachment => file)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
module_eval(&block) if block_given?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
require "forwardable"
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
module Refile
|
6
|
+
# This class downloads a given URL and returns its IO, size, content type and
|
7
|
+
# original file name.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# download = Refile::Download.new('http://example.com/my/data.bin')
|
12
|
+
# download.io
|
13
|
+
# #=> #<StringIO:0x00007fdcb3932fc8 ...>
|
14
|
+
# download.size
|
15
|
+
# #=> 389620
|
16
|
+
# download.content_type
|
17
|
+
# #=> "application/octet-stream"
|
18
|
+
# download.original_file_name
|
19
|
+
# #=> "data.bin"
|
20
|
+
class Download
|
21
|
+
OPTIONS = {
|
22
|
+
"User-Agent" => "Refile/#{Refile::VERSION}",
|
23
|
+
open_timeout: 30,
|
24
|
+
read_timeout: 30,
|
25
|
+
redirect: false
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
extend Forwardable
|
29
|
+
def_delegators :@io, :size, :content_type
|
30
|
+
|
31
|
+
attr_reader :io, :original_filename
|
32
|
+
|
33
|
+
def initialize(uri)
|
34
|
+
@io = download(uri)
|
35
|
+
@original_filename = extract_original_filename
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def download(uri)
|
41
|
+
uri = ensure_uri(uri)
|
42
|
+
follows_remaining = 10
|
43
|
+
|
44
|
+
begin
|
45
|
+
uri.open(OPTIONS)
|
46
|
+
rescue OpenURI::HTTPRedirect => exception
|
47
|
+
raise Refile::TooManyRedirects if follows_remaining.zero?
|
48
|
+
|
49
|
+
uri = ensure_uri(exception.uri)
|
50
|
+
follows_remaining -= 1
|
51
|
+
|
52
|
+
retry
|
53
|
+
rescue OpenURI::HTTPError => exception
|
54
|
+
if exception.message.include?("(Invalid Location URI)")
|
55
|
+
raise Refile::InvalidUrl, "Invalid Redirect URI: #{response["Location"]}"
|
56
|
+
end
|
57
|
+
|
58
|
+
raise exception
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def ensure_uri(url)
|
63
|
+
begin
|
64
|
+
uri = URI(url)
|
65
|
+
rescue URI::InvalidURIError
|
66
|
+
raise Refile::InvalidUrl, "Invalid URI: #{uri.inspect}"
|
67
|
+
end
|
68
|
+
|
69
|
+
unless uri.is_a?(URI::HTTP)
|
70
|
+
raise Refile::InvalidUrl, "URL scheme needs to be http or https: #{uri}"
|
71
|
+
end
|
72
|
+
|
73
|
+
uri
|
74
|
+
end
|
75
|
+
|
76
|
+
def extract_original_filename
|
77
|
+
filename_from_content_disposition || filename_from_path
|
78
|
+
end
|
79
|
+
|
80
|
+
def filename_from_content_disposition
|
81
|
+
content_disposition = @io.meta["content-disposition"].to_s
|
82
|
+
|
83
|
+
escaped_filename =
|
84
|
+
content_disposition[/filename\*=UTF-8''(\S+)/, 1] ||
|
85
|
+
content_disposition[/filename="([^"]*)"/, 1] ||
|
86
|
+
content_disposition[/filename=(\S+)/, 1]
|
87
|
+
|
88
|
+
filename = CGI.unescape(escaped_filename.to_s)
|
89
|
+
|
90
|
+
filename unless filename.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def filename_from_path
|
94
|
+
filename = @io.base_uri.path.split("/").last
|
95
|
+
CGI.unescape(filename) if filename
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -106,10 +106,13 @@ module Refile
|
|
106
106
|
options[:data] ||= {}
|
107
107
|
options[:data][:reference] ||= SecureRandom.hex
|
108
108
|
|
109
|
+
attacher_value = object.send("#{method}_data")
|
110
|
+
|
109
111
|
hidden_options = {
|
110
112
|
multiple: options[:multiple],
|
111
|
-
value:
|
113
|
+
value: attacher_value.try(:to_json),
|
112
114
|
object: object,
|
115
|
+
disabled: attacher_value.blank?,
|
113
116
|
id: nil,
|
114
117
|
data: { reference: options[:data][:reference] }
|
115
118
|
}
|
data/lib/refile/version.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "refile/active_record_helper"
|
2
2
|
require "refile/attachment/active_record"
|
3
|
+
require_relative "../support/accepts_attachments_for_shared_examples"
|
3
4
|
|
4
5
|
describe Refile::ActiveRecord::Attachment do
|
5
6
|
let(:options) { {} }
|
@@ -273,6 +274,18 @@ describe Refile::ActiveRecord::Attachment do
|
|
273
274
|
expect(Refile.store.read(post.document.id)).to eq("hello")
|
274
275
|
expect(post.document.id).not_to be eq old_document.id
|
275
276
|
end
|
277
|
+
|
278
|
+
it "replaces a attachment which was nil" do
|
279
|
+
post = klass.new
|
280
|
+
post.document = nil
|
281
|
+
post.save
|
282
|
+
|
283
|
+
post.document = Refile::FileDouble.new("hello")
|
284
|
+
post.save
|
285
|
+
|
286
|
+
expect(Refile.store.read(post.document.id)).to eq("hello")
|
287
|
+
expect(post.document).not_to be_nil
|
288
|
+
end
|
276
289
|
end
|
277
290
|
|
278
291
|
describe "#destroy" do
|
@@ -339,119 +352,31 @@ describe Refile::ActiveRecord::Attachment do
|
|
339
352
|
"Document"
|
340
353
|
end
|
341
354
|
|
342
|
-
attachment :file
|
355
|
+
attachment :file, type: :image
|
343
356
|
end
|
344
357
|
end
|
345
358
|
|
346
359
|
let(:post) { post_class.new }
|
347
360
|
|
348
|
-
|
361
|
+
it_should_behave_like "accepts_attachments_for"
|
362
|
+
|
363
|
+
it "shouldn't raise error on setter method_missing" do
|
364
|
+
expect { post.creator = nil }.to_not raise_error(ArgumentError)
|
365
|
+
end
|
366
|
+
|
367
|
+
context "when a wrong association is called" do
|
349
368
|
let(:wrong_method) { "files" }
|
350
369
|
let(:wrong_association_message) do
|
351
370
|
"wrong association name #{wrong_method}, use like this documents_files"
|
352
371
|
end
|
353
372
|
|
354
|
-
it "returns a friendly error message
|
373
|
+
it "returns a friendly error message" do
|
355
374
|
expect { post.send(wrong_method) }.to raise_error(wrong_association_message)
|
356
375
|
end
|
357
376
|
|
358
377
|
it "return method missing" do
|
359
378
|
expect { post.foo }.to_not raise_error(wrong_association_message)
|
360
379
|
end
|
361
|
-
|
362
|
-
it "builds records from assigned files" do
|
363
|
-
post.documents_files = [Refile::FileDouble.new("hello"), Refile::FileDouble.new("world")]
|
364
|
-
expect(post.documents[0].file.read).to eq("hello")
|
365
|
-
expect(post.documents[1].file.read).to eq("world")
|
366
|
-
expect(post.documents.size).to eq(2)
|
367
|
-
end
|
368
|
-
|
369
|
-
it "builds records from cache" do
|
370
|
-
post.documents_files = [
|
371
|
-
[
|
372
|
-
{ id: Refile.cache.upload(Refile::FileDouble.new("hello")).id },
|
373
|
-
{ id: Refile.cache.upload(Refile::FileDouble.new("world")).id }
|
374
|
-
].to_json
|
375
|
-
]
|
376
|
-
expect(post.documents[0].file.read).to eq("hello")
|
377
|
-
expect(post.documents[1].file.read).to eq("world")
|
378
|
-
expect(post.documents.size).to eq(2)
|
379
|
-
end
|
380
|
-
|
381
|
-
it "prefers newly uploaded files over cache" do
|
382
|
-
post.documents_files = [
|
383
|
-
[
|
384
|
-
{ id: Refile.cache.upload(Refile::FileDouble.new("moo")).id }
|
385
|
-
].to_json,
|
386
|
-
Refile::FileDouble.new("hello"),
|
387
|
-
Refile::FileDouble.new("world")
|
388
|
-
]
|
389
|
-
expect(post.documents[0].file.read).to eq("hello")
|
390
|
-
expect(post.documents[1].file.read).to eq("world")
|
391
|
-
expect(post.documents.size).to eq(2)
|
392
|
-
end
|
393
|
-
|
394
|
-
it "clears previously assigned files" do
|
395
|
-
post.documents_files = [
|
396
|
-
Refile::FileDouble.new("hello"),
|
397
|
-
Refile::FileDouble.new("world")
|
398
|
-
]
|
399
|
-
post.save
|
400
|
-
post.update_attributes documents_files: [
|
401
|
-
Refile::FileDouble.new("foo")
|
402
|
-
]
|
403
|
-
retrieved = post_class.find(post.id)
|
404
|
-
expect(retrieved.documents[0].file.read).to eq("foo")
|
405
|
-
expect(retrieved.documents.size).to eq(1)
|
406
|
-
end
|
407
|
-
|
408
|
-
context "with append: true" do
|
409
|
-
let(:options) { { append: true } }
|
410
|
-
|
411
|
-
it "appends to previously assigned files" do
|
412
|
-
post.documents_files = [
|
413
|
-
Refile::FileDouble.new("hello"),
|
414
|
-
Refile::FileDouble.new("world")
|
415
|
-
]
|
416
|
-
post.save
|
417
|
-
post.update_attributes documents_files: [
|
418
|
-
Refile::FileDouble.new("foo")
|
419
|
-
]
|
420
|
-
retrieved = post_class.find(post.id)
|
421
|
-
expect(retrieved.documents[0].file.read).to eq("hello")
|
422
|
-
expect(retrieved.documents[1].file.read).to eq("world")
|
423
|
-
expect(retrieved.documents[2].file.read).to eq("foo")
|
424
|
-
expect(retrieved.documents.size).to eq(3)
|
425
|
-
end
|
426
|
-
|
427
|
-
it "appends to previously assigned files with cached files" do
|
428
|
-
post.documents_files = [
|
429
|
-
Refile::FileDouble.new("hello"),
|
430
|
-
Refile::FileDouble.new("world")
|
431
|
-
]
|
432
|
-
post.save
|
433
|
-
post.update_attributes documents_files: [
|
434
|
-
[{
|
435
|
-
id: Refile.cache.upload(Refile::FileDouble.new("hello")).id,
|
436
|
-
filename: "some.jpg",
|
437
|
-
content_type: "image/jpeg",
|
438
|
-
size: 1234
|
439
|
-
}].to_json
|
440
|
-
]
|
441
|
-
retrieved = post_class.find(post.id)
|
442
|
-
expect(retrieved.documents.size).to eq(3)
|
443
|
-
end
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
describe "#:association_:name_data" do
|
448
|
-
it "returns metadata of all files" do
|
449
|
-
post.documents_files = [nil, Refile::FileDouble.new("hello"), Refile::FileDouble.new("world")]
|
450
|
-
data = post.documents_files_data
|
451
|
-
expect(Refile.cache.read(data[0][:id])).to eq("hello")
|
452
|
-
expect(Refile.cache.read(data[1][:id])).to eq("world")
|
453
|
-
expect(data.size).to eq(2)
|
454
|
-
end
|
455
380
|
end
|
456
381
|
end
|
457
382
|
|
@@ -39,13 +39,36 @@ describe Refile::AttachmentHelper do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
describe "#attachment_field" do
|
42
|
+
subject(:field) { attachment_field("post", :document, field_options) }
|
43
|
+
let(:field_options) { { object: klass.new } }
|
44
|
+
let(:html) { Capybara.string(field) }
|
45
|
+
let(:expected_field_name) { "post[0][document]" }
|
46
|
+
let(:selector_css) { "input[name='#{expected_field_name}'][type=hidden]" }
|
47
|
+
let(:input_css) { "input[name='post[document]'][type=hidden]" }
|
48
|
+
|
42
49
|
context "with index given" do
|
43
|
-
let(:
|
50
|
+
let(:field_options) { super().merge index: 0 }
|
44
51
|
|
45
52
|
it "generates file and hidden inputs with identical names" do
|
46
|
-
|
47
|
-
expect(html).to
|
48
|
-
|
53
|
+
expect(html).to have_field(expected_field_name, type: "file")
|
54
|
+
expect(html).to have_selector(:css, selector_css, visible: false, count: 1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "when attacher value is blank" do
|
59
|
+
let(:field_options) { super().merge object: klass.new(document: nil) }
|
60
|
+
it "generates metadata hidden with disabled attribute" do
|
61
|
+
expect(html.find(input_css, visible: false)["disabled"]).to eq "disabled"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when attacher value is present" do
|
66
|
+
let(:field_options) do
|
67
|
+
super().merge object: klass.new(document: StringIO.new("New params"))
|
68
|
+
end
|
69
|
+
|
70
|
+
it "generates metadata input without disabled attribute" do
|
71
|
+
expect(html.find(input_css, visible: false)["disabled"]).to be_nil
|
49
72
|
end
|
50
73
|
end
|
51
74
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require "refile/active_record_helper"
|
2
|
+
require_relative "support/accepts_attachments_for_shared_examples"
|
3
|
+
|
1
4
|
describe Refile::Attachment do
|
2
5
|
let(:options) { {} }
|
3
6
|
let(:klass) do
|
@@ -189,7 +192,7 @@ describe Refile::Attachment do
|
|
189
192
|
it "handles redirect loops by trowing errors" do
|
190
193
|
expect do
|
191
194
|
instance.remote_document_url = "http://www.example.com/loop"
|
192
|
-
end.to raise_error(
|
195
|
+
end.to raise_error(Refile::TooManyRedirects)
|
193
196
|
end
|
194
197
|
end
|
195
198
|
|
@@ -586,4 +589,55 @@ describe Refile::Attachment do
|
|
586
589
|
expect { p klass.ancestors }
|
587
590
|
.to output(/Refile::Attachment\(document\)/).to_stdout
|
588
591
|
end
|
592
|
+
|
593
|
+
describe ".accepts_nested_attributes_for" do
|
594
|
+
it_should_behave_like "accepts_attachments_for" do
|
595
|
+
let(:options) { {} }
|
596
|
+
|
597
|
+
# This class is a PORO, but it's implementing an interface that's similar
|
598
|
+
# to ActiveRecord's in order to simplify specs via shared examples.
|
599
|
+
let(:post_class) do
|
600
|
+
opts = options
|
601
|
+
foo = document_class
|
602
|
+
|
603
|
+
Class.new do
|
604
|
+
extend Refile::Attachment
|
605
|
+
|
606
|
+
attr_accessor :documents
|
607
|
+
|
608
|
+
accepts_attachments_for(
|
609
|
+
:documents,
|
610
|
+
accessor_prefix: "documents_files",
|
611
|
+
collection_class: foo,
|
612
|
+
**opts
|
613
|
+
)
|
614
|
+
|
615
|
+
def initialize(attributes)
|
616
|
+
@documents = attributes[:documents]
|
617
|
+
end
|
618
|
+
|
619
|
+
def save!; end
|
620
|
+
|
621
|
+
def update_attributes!(attributes)
|
622
|
+
attributes.each { |k, v| public_send("#{k}=", v) }
|
623
|
+
end
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
let(:document_class) do
|
628
|
+
Class.new do
|
629
|
+
extend Refile::Attachment
|
630
|
+
attr_accessor :file_id
|
631
|
+
|
632
|
+
attachment :file, type: :image, extension: %w[jpeg], raise_errors: false
|
633
|
+
|
634
|
+
def initialize(attributes = {})
|
635
|
+
self.file = attributes[:file]
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
let(:post) { post_class.new(documents: []) }
|
641
|
+
end
|
642
|
+
end
|
589
643
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
describe Refile::Download do
|
2
|
+
context "without redirects" do
|
3
|
+
it "fetches the file" do
|
4
|
+
stub_request(:get, "http://www.example.com/dummy").to_return(
|
5
|
+
status: 200,
|
6
|
+
body: "dummy",
|
7
|
+
headers: { "Content-Length" => 5, "Content-Type" => "text/plain" }
|
8
|
+
)
|
9
|
+
|
10
|
+
download = described_class.new("http://www.example.com/dummy")
|
11
|
+
|
12
|
+
expect(download.io.read).to eq("dummy")
|
13
|
+
expect(download.size).to eq(5)
|
14
|
+
expect(download.content_type).to eq("text/plain")
|
15
|
+
expect(download.original_filename).to eq("dummy")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with redirects" do
|
20
|
+
it "follows redirects and fetches the file" do
|
21
|
+
stub_request(:get, "http://www.example.com/1").to_return(
|
22
|
+
status: 302,
|
23
|
+
headers: { "Location" => "http://www.example.com/2" }
|
24
|
+
)
|
25
|
+
|
26
|
+
stub_request(:get, "http://www.example.com/2").to_return(
|
27
|
+
status: 200,
|
28
|
+
body: "dummy",
|
29
|
+
headers: { "Content-Length" => 5 }
|
30
|
+
)
|
31
|
+
|
32
|
+
download = described_class.new("http://www.example.com/1")
|
33
|
+
|
34
|
+
expect(download.io.read).to eq("dummy")
|
35
|
+
expect(download.size).to eq(5)
|
36
|
+
expect(download.content_type).to eq("application/octet-stream")
|
37
|
+
expect(download.original_filename).to eq("2")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "handles redirect loops by throwing errors" do
|
41
|
+
stub_request(:get, "http://www.example.com/loop").to_return(
|
42
|
+
status: 302,
|
43
|
+
headers: { "Location" => "http://www.example.com/loop" }
|
44
|
+
)
|
45
|
+
|
46
|
+
expect do
|
47
|
+
described_class.new("http://www.example.com/loop")
|
48
|
+
end.to raise_error(Refile::TooManyRedirects)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -139,6 +139,6 @@ feature "Normal HTTP Post file uploads" do
|
|
139
139
|
expect(download_link("Document")).to eq("abc")
|
140
140
|
expect(page).to have_selector(".content-type", text: "image/png")
|
141
141
|
expect(page).to have_selector(".size", text: "3")
|
142
|
-
expect(page).to have_selector(".filename", text: "some_file.png"
|
142
|
+
expect(page).to have_selector(".filename", text: "some_file.png")
|
143
143
|
end
|
144
144
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "refile/test_app"
|
2
|
+
|
3
|
+
feature "single attribute form upload" do
|
4
|
+
scenario "upload a single file insteaf of an array of files" do
|
5
|
+
visit "/single/posts/new"
|
6
|
+
fill_in "Title", with: "A cool post"
|
7
|
+
attach_file "Documents", path("hello.txt")
|
8
|
+
click_button "Create"
|
9
|
+
|
10
|
+
expect(download_link("Document: hello.txt")).to eq("hello")
|
11
|
+
end
|
12
|
+
|
13
|
+
scenario "Edit with changes" do
|
14
|
+
visit "/single/posts/new"
|
15
|
+
fill_in "Title", with: "A cool post"
|
16
|
+
attach_file "Documents", path("hello.txt")
|
17
|
+
click_button "Create"
|
18
|
+
|
19
|
+
visit "/single/posts/#{Post.last.id}/edit"
|
20
|
+
attach_file "Documents", path("monkey.txt")
|
21
|
+
click_button "Update"
|
22
|
+
|
23
|
+
expect(download_link("Document: monkey.txt")).to eq("monkey")
|
24
|
+
expect(page).not_to have_link("Document: hello.txt")
|
25
|
+
end
|
26
|
+
|
27
|
+
scenario "Edit without changes" do
|
28
|
+
visit "/single/posts/new"
|
29
|
+
fill_in "Title", with: "A cool post"
|
30
|
+
attach_file "Documents", path("hello.txt")
|
31
|
+
click_button "Create"
|
32
|
+
|
33
|
+
visit "/single/posts/#{Post.last.id}/edit"
|
34
|
+
click_button "Update"
|
35
|
+
|
36
|
+
expect(download_link("Document: hello.txt")).to eq("hello")
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
RSpec.shared_examples "accepts_attachments_for" do
|
2
|
+
describe "#:association_:name=" do
|
3
|
+
it "builds records from assigned files" do
|
4
|
+
post.documents_files = [
|
5
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
6
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
7
|
+
]
|
8
|
+
post.save!
|
9
|
+
|
10
|
+
expect(post.documents.size).to eq(2)
|
11
|
+
expect(post.documents[0].file.read).to eq("hello")
|
12
|
+
expect(post.documents[1].file.read).to eq("world")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "clears previously assigned files" do
|
16
|
+
post.documents_files = [
|
17
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
18
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
19
|
+
]
|
20
|
+
post.save!
|
21
|
+
post.update_attributes! documents_files: [
|
22
|
+
Refile::FileDouble.new("foo", content_type: "image/jpeg")
|
23
|
+
]
|
24
|
+
|
25
|
+
expect(post.documents[0].file.read).to eq("foo")
|
26
|
+
expect(post.documents.size).to eq(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "ignores nil assigned files" do
|
30
|
+
post.documents_files = [
|
31
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
32
|
+
nil,
|
33
|
+
nil
|
34
|
+
]
|
35
|
+
post.save!
|
36
|
+
|
37
|
+
expect(post.documents.size).to eq(1)
|
38
|
+
expect(post.documents[0].file.read).to eq("hello")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "builds records from cache" do
|
42
|
+
post.documents_files = [
|
43
|
+
[
|
44
|
+
{
|
45
|
+
id: Refile.cache.upload(Refile::FileDouble.new("hello")).id,
|
46
|
+
filename: "some.jpg",
|
47
|
+
content_type: "image/jpeg",
|
48
|
+
size: 1234
|
49
|
+
},
|
50
|
+
{
|
51
|
+
id: Refile.cache.upload(Refile::FileDouble.new("world")).id,
|
52
|
+
filename: "some.jpg",
|
53
|
+
content_type: "image/jpeg",
|
54
|
+
size: 1234
|
55
|
+
}
|
56
|
+
].to_json
|
57
|
+
]
|
58
|
+
post.save!
|
59
|
+
|
60
|
+
expect(post.documents.size).to eq(2)
|
61
|
+
expect(post.documents[0].file.read).to eq("hello")
|
62
|
+
expect(post.documents[1].file.read).to eq("world")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "prefers uploaded files over cache when both are present" do
|
66
|
+
post.documents_files = [
|
67
|
+
[
|
68
|
+
{
|
69
|
+
id: Refile.cache.upload(Refile::FileDouble.new("moo")).id,
|
70
|
+
filename: "some.jpg",
|
71
|
+
content_type: "image/jpeg",
|
72
|
+
size: 1234
|
73
|
+
}
|
74
|
+
].to_json,
|
75
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
76
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
77
|
+
]
|
78
|
+
post.save!
|
79
|
+
|
80
|
+
expect(post.documents.size).to eq(2)
|
81
|
+
expect(post.documents[0].file.read).to eq("hello")
|
82
|
+
expect(post.documents[1].file.read).to eq("world")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "ignores empty caches" do
|
86
|
+
post.documents_files = [
|
87
|
+
[
|
88
|
+
{
|
89
|
+
id: Refile.cache.upload(Refile::FileDouble.new("moo")).id,
|
90
|
+
filename: "some.jpg",
|
91
|
+
content_type: "image/jpeg",
|
92
|
+
size: 1234
|
93
|
+
},
|
94
|
+
{},
|
95
|
+
{}
|
96
|
+
].to_json
|
97
|
+
]
|
98
|
+
post.save!
|
99
|
+
|
100
|
+
expect(post.documents.size).to eq(1)
|
101
|
+
expect(post.documents[0].file.read).to eq("moo")
|
102
|
+
end
|
103
|
+
|
104
|
+
it "ignores caches with malformed json" do
|
105
|
+
post.documents_files = [
|
106
|
+
"[{id: 'this is a ruby hash'}]"
|
107
|
+
]
|
108
|
+
|
109
|
+
expect(post.documents.size).to be_zero
|
110
|
+
end
|
111
|
+
|
112
|
+
context "with append: true" do
|
113
|
+
let(:options) { { append: true } }
|
114
|
+
|
115
|
+
it "appends to previously assigned files" do
|
116
|
+
post.documents_files = [
|
117
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
118
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
119
|
+
]
|
120
|
+
post.save!
|
121
|
+
post.update_attributes! documents_files: [
|
122
|
+
Refile::FileDouble.new("foo", content_type: "image/jpeg")
|
123
|
+
]
|
124
|
+
|
125
|
+
expect(post.documents.size).to eq(3)
|
126
|
+
expect(post.documents[0].file.read).to eq("hello")
|
127
|
+
expect(post.documents[1].file.read).to eq("world")
|
128
|
+
expect(post.documents[2].file.read).to eq("foo")
|
129
|
+
end
|
130
|
+
|
131
|
+
it "appends to previously assigned files with cached files" do
|
132
|
+
post.documents_files = [
|
133
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
134
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
135
|
+
]
|
136
|
+
post.save!
|
137
|
+
post.update_attributes! documents_files: [
|
138
|
+
[{
|
139
|
+
id: Refile.cache.upload(Refile::FileDouble.new("hello world")).id,
|
140
|
+
filename: "some.jpg",
|
141
|
+
content_type: "image/jpeg",
|
142
|
+
size: 1234
|
143
|
+
}].to_json
|
144
|
+
]
|
145
|
+
|
146
|
+
expect(post.documents.size).to eq(3)
|
147
|
+
expect(post.documents[0].file.read).to eq("hello")
|
148
|
+
expect(post.documents[1].file.read).to eq("world")
|
149
|
+
expect(post.documents[2].file.read).to eq("hello world")
|
150
|
+
end
|
151
|
+
|
152
|
+
it "appends to previously cached files with cached files" do
|
153
|
+
post.documents_files = [
|
154
|
+
[
|
155
|
+
{
|
156
|
+
id: Refile.cache.upload(Refile::FileDouble.new("moo")).id,
|
157
|
+
filename: "some1.jpg",
|
158
|
+
content_type: "image/jpeg",
|
159
|
+
size: 123
|
160
|
+
}
|
161
|
+
].to_json
|
162
|
+
]
|
163
|
+
post.documents_files = [
|
164
|
+
[
|
165
|
+
{
|
166
|
+
id: Refile.cache.upload(Refile::FileDouble.new("hello")).id,
|
167
|
+
filename: "some2.jpg",
|
168
|
+
content_type: "image/jpeg",
|
169
|
+
size: 1234
|
170
|
+
}
|
171
|
+
].to_json
|
172
|
+
]
|
173
|
+
post.save!
|
174
|
+
|
175
|
+
expect(post.documents.size).to eq(2)
|
176
|
+
expect(post.documents[0].file.read).to eq("moo")
|
177
|
+
expect(post.documents[1].file.read).to eq("hello")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "#:association_:name_data" do
|
183
|
+
it "returns metadata for all files" do
|
184
|
+
post.documents_files = [
|
185
|
+
nil,
|
186
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
187
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
188
|
+
]
|
189
|
+
data = post.documents_files_data
|
190
|
+
|
191
|
+
expect(data.size).to eq(2)
|
192
|
+
expect(Refile.cache.read(data[0][:id])).to eq("hello")
|
193
|
+
expect(Refile.cache.read(data[1][:id])).to eq("world")
|
194
|
+
end
|
195
|
+
|
196
|
+
context "when there are invalid files" do
|
197
|
+
it "only returns metadata for valid files " do
|
198
|
+
invalid_file = Refile::FileDouble.new("world", content_type: "text/plain")
|
199
|
+
|
200
|
+
post.documents_files = [invalid_file]
|
201
|
+
data = post.documents_files_data
|
202
|
+
|
203
|
+
expect(data).to be_nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#:association_:name" do
|
209
|
+
it "builds records from assigned files" do
|
210
|
+
post.documents_files = [
|
211
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg"),
|
212
|
+
Refile::FileDouble.new("world", content_type: "image/jpeg")
|
213
|
+
]
|
214
|
+
|
215
|
+
expect(post.documents_files.size).to eq(2)
|
216
|
+
expect(post.documents_files[0].read).to eq("hello")
|
217
|
+
expect(post.documents_files[1].read).to eq("world")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe "#:association_:name_attachment_definition" do
|
222
|
+
it "returns attachment definition" do
|
223
|
+
post.documents_files = [
|
224
|
+
Refile::FileDouble.new("hello", content_type: "image/jpeg")
|
225
|
+
]
|
226
|
+
|
227
|
+
definition = post.documents_files_attachment_definition
|
228
|
+
expect(definition).to be_a Refile::AttachmentDefinition
|
229
|
+
expect(definition.name).to eq(:file)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
metadata
CHANGED
@@ -1,35 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leifcr-refile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonas Nicklas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: rest-client
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.8'
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '3.0'
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.8'
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '3.0'
|
33
13
|
- !ruby/object:Gem::Dependency
|
34
14
|
name: sinatra
|
35
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -79,11 +59,13 @@ files:
|
|
79
59
|
- lib/refile/attacher.rb
|
80
60
|
- lib/refile/attachment.rb
|
81
61
|
- lib/refile/attachment/active_record.rb
|
62
|
+
- lib/refile/attachment/multiple_attachments.rb
|
82
63
|
- lib/refile/attachment_definition.rb
|
83
64
|
- lib/refile/backend/file_system.rb
|
84
65
|
- lib/refile/backend/s3.rb
|
85
66
|
- lib/refile/backend_macros.rb
|
86
67
|
- lib/refile/custom_logger.rb
|
68
|
+
- lib/refile/download.rb
|
87
69
|
- lib/refile/file.rb
|
88
70
|
- lib/refile/file_double.rb
|
89
71
|
- lib/refile/image_processing.rb
|
@@ -103,17 +85,20 @@ files:
|
|
103
85
|
- spec/refile/backend_examples.rb
|
104
86
|
- spec/refile/backend_macros_spec.rb
|
105
87
|
- spec/refile/custom_logger_spec.rb
|
88
|
+
- spec/refile/download_spec.rb
|
106
89
|
- spec/refile/features/direct_upload_spec.rb
|
107
90
|
- spec/refile/features/multiple_upload_spec.rb
|
108
91
|
- spec/refile/features/normal_upload_spec.rb
|
109
92
|
- spec/refile/features/presigned_upload_spec.rb
|
110
93
|
- spec/refile/features/simple_form_spec.rb
|
94
|
+
- spec/refile/features/single_upload_spec.rb
|
111
95
|
- spec/refile/fixtures/hello.txt
|
112
96
|
- spec/refile/fixtures/image.jpg
|
113
97
|
- spec/refile/fixtures/large.txt
|
114
98
|
- spec/refile/fixtures/monkey.txt
|
115
99
|
- spec/refile/fixtures/world.txt
|
116
100
|
- spec/refile/spec_helper.rb
|
101
|
+
- spec/refile/support/accepts_attachments_for_shared_examples.rb
|
117
102
|
- spec/refile_spec.rb
|
118
103
|
homepage: https://github.com/refile/refile
|
119
104
|
licenses:
|
@@ -135,8 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
120
|
- !ruby/object:Gem::Version
|
136
121
|
version: '0'
|
137
122
|
requirements: []
|
138
|
-
|
139
|
-
rubygems_version: 2.6.14
|
123
|
+
rubygems_version: 3.0.3
|
140
124
|
signing_key:
|
141
125
|
specification_version: 4
|
142
126
|
summary: Simple and powerful file upload library
|