leifcr-refile 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|