refile 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of refile might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -2
  3. data/.travis.yml +2 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +33 -0
  6. data/History.md +9 -0
  7. data/README.md +67 -16
  8. data/app/assets/javascripts/refile.js +19 -17
  9. data/lib/refile.rb +36 -6
  10. data/lib/refile/app.rb +15 -12
  11. data/lib/refile/attacher.rb +119 -49
  12. data/lib/refile/attachment.rb +29 -16
  13. data/lib/refile/attachment/active_record.rb +5 -2
  14. data/lib/refile/backend/file_system.rb +61 -1
  15. data/lib/refile/backend/s3.rb +66 -0
  16. data/lib/refile/custom_logger.rb +46 -0
  17. data/lib/refile/file.rb +32 -1
  18. data/lib/refile/image_processing.rb +72 -3
  19. data/lib/refile/rails.rb +2 -8
  20. data/lib/refile/rails/attachment_helper.rb +77 -19
  21. data/lib/refile/signature.rb +16 -1
  22. data/lib/refile/type.rb +28 -0
  23. data/lib/refile/version.rb +1 -1
  24. data/refile.gemspec +1 -1
  25. data/spec/refile/active_record_helper.rb +27 -0
  26. data/spec/refile/attachment/active_record_spec.rb +92 -0
  27. data/spec/refile/attachment_spec.rb +153 -28
  28. data/spec/refile/custom_logger_spec.rb +22 -0
  29. data/spec/refile/features/direct_upload_spec.rb +19 -2
  30. data/spec/refile/features/normal_upload_spec.rb +41 -11
  31. data/spec/refile/features/presigned_upload_spec.rb +1 -2
  32. data/spec/refile/rails/attachment_helper_spec.rb +1 -1
  33. data/spec/refile/test_app.rb +16 -14
  34. data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +1 -1
  35. data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +1 -1
  36. data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +1 -1
  37. data/spec/refile/test_app/app/views/direct_posts/new.html.erb +4 -0
  38. data/spec/refile/test_app/app/views/normal_posts/show.html.erb +5 -3
  39. metadata +27 -17
@@ -1,7 +1,21 @@
1
1
  module Refile
2
+ # A signature summarizes an HTTP request a client can make to upload a file
3
+ # to directly upload a file to a backend. This signature is usually generated
4
+ # by a backend's `presign` method.
2
5
  class Signature
3
- attr_reader :as, :id, :url, :fields
6
+ # @return [String] the name of the field that the file will be uploaded as.
7
+ attr_reader :as
4
8
 
9
+ # @return [String] the id the file will receive once uploaded.
10
+ attr_reader :id
11
+
12
+ # @return [String] the url the file should be uploaded to.
13
+ attr_reader :url
14
+
15
+ # @return [String] additional fields to be sent alongside the file.
16
+ attr_reader :fields
17
+
18
+ # @api private
5
19
  def initialize(as:, id:, url:, fields:)
6
20
  @as = as
7
21
  @id = id
@@ -9,6 +23,7 @@ module Refile
9
23
  @fields = fields
10
24
  end
11
25
 
26
+ # @return [Hash{Symbol => Object}] an object suitable for serialization to JSON
12
27
  def as_json(*)
13
28
  { as: @as, id: @id, url: @url, fields: @fields }
14
29
  end
@@ -0,0 +1,28 @@
1
+ module Refile
2
+ # A type represents an alias for one or multiple content types.
3
+ # By adding types, you could simplify this:
4
+ #
5
+ # attachment :document, content_type: %w[text/plain application/pdf]
6
+ #
7
+ # To this:
8
+ #
9
+ # attachment :document, type: :document
10
+ #
11
+ # Simply define a new type like this:
12
+ #
13
+ # Refile.types[:document] = Refile::Type.new(:document,
14
+ # content_type: %w[text/plain application/pdf]
15
+ # )
16
+ #
17
+ class Type
18
+ # @return [String, Array<String>] The type's content types
19
+ attr_accessor :content_type
20
+
21
+ # @param [Symbol] name the name of the type
22
+ # @param [String, Array<String>] content_type content types which are valid for this type
23
+ def initialize(name, content_type: nil)
24
+ @name = name
25
+ @content_type = content_type
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Refile
2
- VERSION = "0.4.2"
2
+ VERSION = "0.5.0"
3
3
  end
data/refile.gemspec CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.required_ruby_version = ">= 2.1.0"
21
21
 
22
- spec.add_dependency "rest-client", "~> 1.7.2"
23
22
  spec.add_dependency "sinatra", "~> 1.4.5"
24
23
  spec.add_dependency "mime-types"
25
24
 
@@ -38,4 +37,5 @@ Gem::Specification.new do |spec|
38
37
  spec.add_development_dependency "selenium-webdriver"
39
38
  spec.add_development_dependency "yard"
40
39
  spec.add_development_dependency "rubocop"
40
+ spec.add_development_dependency "redcarpet"
41
41
  end
@@ -0,0 +1,27 @@
1
+ require "active_record"
2
+
3
+ I18n.enforce_available_locales = true
4
+
5
+ ActiveRecord::Base.establish_connection(
6
+ adapter: "sqlite3",
7
+ database: File.expand_path("test_app/db/db.sqlite", File.dirname(__FILE__)),
8
+ verbosity: "quiet"
9
+ )
10
+
11
+ class TestMigration < ActiveRecord::Migration
12
+ def self.up
13
+ create_table :posts, force: true do |t|
14
+ t.column :title, :string
15
+ t.column :image_id, :string
16
+ t.column :image_content_type, :string
17
+ t.column :document_id, :string
18
+ t.column :document_filename, :string
19
+ t.column :document_content_type, :string
20
+ t.column :document_size, :integer
21
+ end
22
+ end
23
+ end
24
+
25
+ quietly do
26
+ TestMigration.up
27
+ end
@@ -0,0 +1,92 @@
1
+ require "refile/active_record_helper"
2
+ require "refile/attachment/active_record"
3
+
4
+ describe Refile::ActiveRecord::Attachment do
5
+ let(:options) { {} }
6
+ let(:klass) do
7
+ opts = options
8
+ Class.new(ActiveRecord::Base) do
9
+ self.table_name = :posts
10
+
11
+ def self.name
12
+ "Post"
13
+ end
14
+
15
+ attachment :document, **opts
16
+ end
17
+ end
18
+
19
+ describe "#valid?" do
20
+ let(:options) { { type: :image } }
21
+
22
+ context "with file" do
23
+ it "returns true when no file is assigned" do
24
+ post = klass.new
25
+ expect(post.valid?).to be_truthy
26
+ expect(post.errors[:document]).to be_empty
27
+ end
28
+
29
+ it "returns false when type is invalid" do
30
+ post = klass.new
31
+ post.document = Refile::FileDouble.new("hello", content_type: "text/plain")
32
+ expect(post.valid?).to be_falsy
33
+ expect(post.errors[:document].length).to eq(1)
34
+ end
35
+
36
+ it "returns true when type is invalid" do
37
+ post = klass.new
38
+ post.document = Refile::FileDouble.new("hello", content_type: "image/png")
39
+ expect(post.valid?).to be_truthy
40
+ expect(post.errors[:document]).to be_empty
41
+ end
42
+ end
43
+
44
+ context "with metadata" do
45
+ it "returns false when metadata doesn't have an id" do
46
+ Refile.cache.upload(StringIO.new("hello"))
47
+ post = klass.new
48
+ post.document = { content_type: "text/png" }.to_json
49
+ expect(post.valid?).to be_falsy
50
+ expect(post.errors[:document].length).to eq(1)
51
+ end
52
+
53
+ it "returns false when type is invalid" do
54
+ file = Refile.cache.upload(StringIO.new("hello"))
55
+ post = klass.new
56
+ post.document = { id: file.id, content_type: "text/png" }.to_json
57
+ expect(post.valid?).to be_falsy
58
+ expect(post.errors[:document].length).to eq(1)
59
+ end
60
+
61
+ it "returns true when type is invalid" do
62
+ file = Refile.cache.upload(StringIO.new("hello"))
63
+ post = klass.new
64
+ post.document = { id: file.id, content_type: "image/png" }.to_json
65
+ expect(post.valid?).to be_truthy
66
+ expect(post.errors[:document]).to be_empty
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#save" do
72
+ it "stores the assigned file" do
73
+ post = klass.new
74
+ post.document = Refile::FileDouble.new("hello")
75
+ post.save
76
+ post = klass.find(post.id)
77
+ expect(post.document.read).to eq("hello")
78
+ expect(Refile.store.read(post.document.id)).to eq("hello")
79
+ end
80
+ end
81
+
82
+ describe "#destroy" do
83
+ it "removes the stored file" do
84
+ post = klass.new
85
+ post.document = Refile::FileDouble.new("hello")
86
+ post.save
87
+ file = post.document
88
+ post.destroy
89
+ expect(file.exists?).to be_falsy
90
+ end
91
+ end
92
+ end
@@ -5,7 +5,7 @@ describe Refile::Attachment do
5
5
  Class.new do
6
6
  extend Refile::Attachment
7
7
 
8
- attr_accessor :document_id, :document_name, :document_size
8
+ attr_accessor :document_id, :document_filename, :document_size, :document_content_type
9
9
 
10
10
  attachment :document, **opts
11
11
  end
@@ -14,10 +14,46 @@ describe Refile::Attachment do
14
14
 
15
15
  describe ":name=" do
16
16
  it "receives a file, caches it and sets the _id parameter" do
17
- instance.document = Refile::FileDouble.new("hello")
17
+ instance.document = Refile::FileDouble.new("hello", "foo.txt", content_type: "text/plain")
18
+
19
+ expect(instance.document.read).to eq("hello")
20
+
21
+ expect(instance.document_attacher.data[:filename]).to eq("foo.txt")
22
+ expect(instance.document_attacher.data[:size]).to eq(5)
23
+ expect(instance.document_attacher.data[:content_type]).to eq("text/plain")
24
+
25
+ expect(instance.document_filename).to eq("foo.txt")
26
+ expect(instance.document_size).to eq(5)
27
+ expect(instance.document_content_type).to eq("text/plain")
28
+ end
29
+
30
+ it "receives serialized data and retrieves file from it" do
31
+ file = Refile.cache.upload(Refile::FileDouble.new("hello"))
32
+ instance.document = { id: file.id, filename: "foo.txt", content_type: "text/plain", size: 5 }.to_json
33
+
34
+ expect(instance.document.read).to eq("hello")
35
+
36
+ expect(instance.document_attacher.data[:filename]).to eq("foo.txt")
37
+ expect(instance.document_attacher.data[:size]).to eq(5)
38
+ expect(instance.document_attacher.data[:content_type]).to eq("text/plain")
18
39
 
19
- expect(Refile.cache.get(instance.document.id).read).to eq("hello")
20
- expect(Refile.cache.get(instance.document_cache_id).read).to eq("hello")
40
+ expect(instance.document_filename).to eq("foo.txt")
41
+ expect(instance.document_size).to eq(5)
42
+ expect(instance.document_content_type).to eq("text/plain")
43
+ end
44
+
45
+ it "does nothing when assigned string lacks an id" do
46
+ instance.document = { size: 5 }.to_json
47
+
48
+ expect(instance.document).to be_nil
49
+ expect(instance.document_size).to be_nil
50
+ end
51
+
52
+ it "does nothing when assigned string is not valid JSON" do
53
+ instance.document = "size:f{oo}"
54
+
55
+ expect(instance.document).to be_nil
56
+ expect(instance.document_size).to be_nil
21
57
  end
22
58
  end
23
59
 
@@ -43,13 +79,26 @@ describe Refile::Attachment do
43
79
 
44
80
  context "without redirects" do
45
81
  before(:each) do
46
- stub_request(:get, "http://www.example.com/some_file").to_return(status: 200, body: "abc", headers: { "Content-Length" => 3 })
82
+ stub_request(:get, "http://www.example.com/some_file.txt").to_return(
83
+ status: 200,
84
+ body: "abc",
85
+ headers: { "Content-Length" => 3, "Content-Type" => "text/plain" }
86
+ )
47
87
  end
48
88
 
49
- it "downloads file, caches it and sets the _id parameter" do
50
- instance.remote_document_url = "http://www.example.com/some_file"
89
+ it "downloads file, caches it and sets the _id parameter and metadata" do
90
+ instance.remote_document_url = "http://www.example.com/some_file.txt"
91
+ expect(instance.document.read).to eq("abc")
92
+
93
+ expect(instance.document_attacher.data[:filename]).to eq("some_file.txt")
94
+ expect(instance.document_attacher.data[:size]).to eq(3)
95
+ expect(instance.document_attacher.data[:content_type]).to eq("text/plain")
96
+
97
+ expect(instance.document_filename).to eq("some_file.txt")
98
+ expect(instance.document_size).to eq(3)
99
+ expect(instance.document_content_type).to eq("text/plain")
100
+
51
101
  expect(Refile.cache.get(instance.document.id).read).to eq("abc")
52
- expect(Refile.cache.get(instance.document_cache_id).read).to eq("abc")
53
102
  end
54
103
  end
55
104
 
@@ -62,8 +111,9 @@ describe Refile::Attachment do
62
111
 
63
112
  it "follows redirects and fetches the file, caches it and sets the _id parameter" do
64
113
  instance.remote_document_url = "http://www.example.com/1"
114
+ expect(instance.document_filename).to eq("2")
115
+ expect(instance.document.read).to eq("woop")
65
116
  expect(Refile.cache.get(instance.document.id).read).to eq("woop")
66
- expect(Refile.cache.get(instance.document_cache_id).read).to eq("woop")
67
117
  end
68
118
 
69
119
  context "when errors enabled" do
@@ -71,7 +121,7 @@ describe Refile::Attachment do
71
121
  it "handles redirect loops by trowing errors" do
72
122
  expect do
73
123
  instance.remote_document_url = "http://www.example.com/loop"
74
- end.to raise_error(RestClient::MaxRedirectsReached)
124
+ end.to raise_error(RuntimeError, /redirection loop/)
75
125
  end
76
126
  end
77
127
 
@@ -88,15 +138,6 @@ describe Refile::Attachment do
88
138
  end
89
139
  end
90
140
 
91
- describe ":name_cache_id" do
92
- it "doesn't overwrite a cached file" do
93
- instance.document = Refile::FileDouble.new("hello")
94
- instance.document_cache_id = "xyz"
95
-
96
- expect(instance.document.read).to eq("hello")
97
- end
98
- end
99
-
100
141
  describe ":name_attacher.store!" do
101
142
  it "puts a cached file into the store" do
102
143
  instance.document = Refile::FileDouble.new("hello")
@@ -107,7 +148,8 @@ describe Refile::Attachment do
107
148
  expect(Refile.store.get(instance.document_id).read).to eq("hello")
108
149
  expect(Refile.store.get(instance.document.id).read).to eq("hello")
109
150
 
110
- expect(instance.document_cache_id).to be_nil
151
+ expect(instance.document.read).to eq("hello")
152
+ expect(instance.document_size).to eq(5)
111
153
  expect(Refile.cache.get(cache.id).exists?).to be_falsy
112
154
  end
113
155
 
@@ -133,7 +175,7 @@ describe Refile::Attachment do
133
175
  expect(Refile.store.get(instance.document_id).read).to eq("world")
134
176
  expect(Refile.store.get(instance.document.id).read).to eq("world")
135
177
 
136
- expect(instance.document_cache_id).to be_nil
178
+ expect(instance.document.read).to eq("world")
137
179
  expect(Refile.cache.get(cache.id).exists?).to be_falsy
138
180
  expect(Refile.store.get(file.id).exists?).to be_falsy
139
181
  end
@@ -146,29 +188,28 @@ describe Refile::Attachment do
146
188
  instance.document_attacher.store!
147
189
 
148
190
  expect(instance.document_id).to be_nil
191
+ expect(instance.document_size).to be_nil
149
192
  expect(Refile.store.exists?(file.id)).to be_falsy
150
193
  end
151
194
  end
152
195
 
153
196
  describe ":name_attacher.delete!" do
154
197
  it "deletes a stored file" do
155
- file = Refile.store.upload(Refile::FileDouble.new("hello"))
156
- instance.document_id = file.id
198
+ instance.document = Refile::FileDouble.new("hello")
199
+ instance.document_attacher.store!
200
+ file = instance.document
157
201
 
158
202
  instance.document_attacher.delete!
159
203
 
160
- expect(instance.document_id).to be_nil
161
204
  expect(Refile.store.exists?(file.id)).to be_falsy
162
205
  end
163
206
 
164
207
  it "deletes a cached file" do
165
- file = Refile.cache.upload(Refile::FileDouble.new("hello"))
166
- instance.document_cache_id = file.id
208
+ instance.document = Refile::FileDouble.new("hello")
209
+ file = instance.document
167
210
 
168
211
  instance.document_attacher.delete!
169
212
 
170
- expect(instance.document_id).to be_nil
171
- expect(instance.document_cache_id).to be_nil
172
213
  expect(Refile.cache.exists?(file.id)).to be_falsy
173
214
  end
174
215
  end
@@ -200,6 +241,83 @@ describe Refile::Attachment do
200
241
  end
201
242
  end
202
243
 
244
+ describe ":name_attacher.valid?" do
245
+ let(:options) { { type: :image, raise_errors: false } }
246
+
247
+ it "returns false if no file is attached" do
248
+ expect(instance.document_attacher.valid?).to be_falsy
249
+ end
250
+
251
+ it "returns false and if valid file is attached" do
252
+ file = Refile::FileDouble.new("hello", content_type: "image/png")
253
+
254
+ instance.document = file
255
+
256
+ expect(instance.document_attacher.valid?).to be_truthy
257
+ end
258
+
259
+ it "returns false and sets errors if invalid file is attached" do
260
+ file = Refile::FileDouble.new("hello", content_type: "text/plain")
261
+
262
+ instance.document = file
263
+
264
+ expect(instance.document_attacher.valid?).to be_falsy
265
+ expect(instance.document_attacher.errors).to eq([:invalid_content_type])
266
+ end
267
+ end
268
+
269
+ describe ":name_attacher.extension" do
270
+ it "is nil when not inferrable" do
271
+ file = Refile::FileDouble.new("hello")
272
+ instance.document = file
273
+ expect(instance.document_attacher.extension).to be_nil
274
+ end
275
+
276
+ it "is inferred from the filename" do
277
+ file = Refile::FileDouble.new("hello", "hello.txt")
278
+ instance.document = file
279
+ expect(instance.document_attacher.extension).to eq("txt")
280
+ end
281
+
282
+ it "is inferred from the content type" do
283
+ file = Refile::FileDouble.new("hello", content_type: "image/png")
284
+ instance.document = file
285
+ expect(instance.document_attacher.extension).to eq("png")
286
+ end
287
+
288
+ it "returns nil with unknown content type" do
289
+ file = Refile::FileDouble.new("hello", content_type: "foo/doesnotexist")
290
+ instance.document = file
291
+ expect(instance.document_attacher.extension).to be_nil
292
+ end
293
+
294
+ it "is nil when filename has no extension" do
295
+ file = Refile::FileDouble.new("hello", "hello")
296
+ instance.document = file
297
+ expect(instance.document_attacher.extension).to be_nil
298
+ end
299
+ end
300
+
301
+ describe ":name_attacher.basename" do
302
+ it "is nil when not inferrable" do
303
+ file = Refile::FileDouble.new("hello")
304
+ instance.document = file
305
+ expect(instance.document_attacher.basename).to be_nil
306
+ end
307
+
308
+ it "is inferred from the filename" do
309
+ file = Refile::FileDouble.new("hello", "hello.txt")
310
+ instance.document = file
311
+ expect(instance.document_attacher.basename).to eq("hello")
312
+ end
313
+
314
+ it "returns filename if filename has no extension" do
315
+ file = Refile::FileDouble.new("hello", "hello")
316
+ instance.document = file
317
+ expect(instance.document_attacher.basename).to eq("hello")
318
+ end
319
+ end
320
+
203
321
  describe ":name_attacher.error" do
204
322
  let(:options) { { cache: :limited_cache, raise_errors: false } }
205
323
 
@@ -358,4 +476,11 @@ describe Refile::Attachment do
358
476
  expect(instance.document).to be_nil
359
477
  end
360
478
  end
479
+
480
+ it "includes the module with methods in an instrospectable way" do
481
+ expect { puts klass.ancestors }
482
+ .to output(/Refile::Attachment\(document\)/).to_stdout
483
+ expect { p klass.ancestors }
484
+ .to output(/Refile::Attachment\(document\)/).to_stdout
485
+ end
361
486
  end