refile 0.5.5 → 0.6.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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/refile.rb +252 -27
  3. data/lib/refile/app.rb +55 -14
  4. data/lib/refile/attacher.rb +39 -40
  5. data/lib/refile/attachment.rb +28 -13
  6. data/lib/refile/attachment/active_record.rb +90 -1
  7. data/lib/refile/attachment_definition.rb +47 -0
  8. data/lib/refile/backend/s3.rb +1 -147
  9. data/lib/refile/backend_macros.rb +13 -5
  10. data/lib/refile/custom_logger.rb +3 -1
  11. data/lib/refile/file.rb +9 -0
  12. data/lib/refile/image_processing.rb +1 -143
  13. data/lib/refile/rails.rb +30 -0
  14. data/lib/refile/rails/attachment_helper.rb +27 -16
  15. data/lib/refile/signature.rb +5 -0
  16. data/lib/refile/simple_form.rb +17 -0
  17. data/lib/refile/version.rb +1 -1
  18. data/spec/refile/active_record_helper.rb +11 -0
  19. data/spec/refile/app_spec.rb +197 -20
  20. data/spec/refile/attachment/active_record_spec.rb +298 -1
  21. data/spec/refile/attachment_helper_spec.rb +39 -0
  22. data/spec/refile/attachment_spec.rb +53 -5
  23. data/spec/refile/backend_examples.rb +13 -2
  24. data/spec/refile/backend_macros_spec.rb +27 -6
  25. data/spec/refile/custom_logger_spec.rb +2 -3
  26. data/spec/refile/features/direct_upload_spec.rb +18 -0
  27. data/spec/refile/features/multiple_upload_spec.rb +122 -0
  28. data/spec/refile/features/normal_upload_spec.rb +5 -3
  29. data/spec/refile/features/presigned_upload_spec.rb +4 -0
  30. data/spec/refile/features/simple_form_spec.rb +8 -0
  31. data/spec/refile/fixtures/monkey.txt +1 -0
  32. data/spec/refile/fixtures/world.txt +1 -0
  33. data/spec/refile/spec_helper.rb +21 -11
  34. data/spec/refile_spec.rb +253 -24
  35. metadata +12 -303
  36. data/.gitignore +0 -27
  37. data/.rspec +0 -2
  38. data/.rubocop.yml +0 -68
  39. data/.travis.yml +0 -21
  40. data/.yardopts +0 -1
  41. data/CONTRIBUTING.md +0 -33
  42. data/Gemfile +0 -3
  43. data/History.md +0 -96
  44. data/LICENSE.txt +0 -22
  45. data/README.md +0 -651
  46. data/Rakefile +0 -19
  47. data/app/assets/javascripts/refile.js +0 -63
  48. data/config.ru +0 -8
  49. data/config/locales/en.yml +0 -8
  50. data/config/routes.rb +0 -5
  51. data/refile.gemspec +0 -42
  52. data/spec/refile/backend/s3_spec.rb +0 -11
  53. data/spec/refile/test_app.rb +0 -65
  54. data/spec/refile/test_app/app/assets/javascripts/application.js +0 -42
  55. data/spec/refile/test_app/app/controllers/application_controller.rb +0 -2
  56. data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +0 -15
  57. data/spec/refile/test_app/app/controllers/home_controller.rb +0 -4
  58. data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +0 -48
  59. data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +0 -31
  60. data/spec/refile/test_app/app/models/post.rb +0 -5
  61. data/spec/refile/test_app/app/views/direct_posts/new.html.erb +0 -20
  62. data/spec/refile/test_app/app/views/home/index.html.erb +0 -1
  63. data/spec/refile/test_app/app/views/layouts/application.html.erb +0 -14
  64. data/spec/refile/test_app/app/views/normal_posts/_form.html.erb +0 -28
  65. data/spec/refile/test_app/app/views/normal_posts/edit.html.erb +0 -1
  66. data/spec/refile/test_app/app/views/normal_posts/index.html +0 -5
  67. data/spec/refile/test_app/app/views/normal_posts/new.html.erb +0 -1
  68. data/spec/refile/test_app/app/views/normal_posts/show.html.erb +0 -19
  69. data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +0 -16
  70. data/spec/refile/test_app/config/database.yml +0 -7
  71. data/spec/refile/test_app/config/routes.rb +0 -17
  72. data/spec/refile/test_app/public/favicon.ico +0 -0
@@ -17,7 +17,60 @@ describe Refile::ActiveRecord::Attachment do
17
17
  end
18
18
 
19
19
  describe "#valid?" do
20
- let(:options) { { type: :image } }
20
+ let(:options) { { type: :image, cache: :limited_cache } }
21
+
22
+ context "extension validation" do
23
+ let(:options) { { cache: :limited_cache, extension: %w[Png] } }
24
+
25
+ context "with file" do
26
+ it "returns true when extension is included in list" do
27
+ post = klass.new
28
+ post.document = Refile::FileDouble.new("hello", "image.Png")
29
+ expect(post.valid?).to be_truthy
30
+ expect(post.errors[:document]).to be_empty
31
+ end
32
+
33
+ it "returns true when extension is included in list but chars are randomcase" do
34
+ post = klass.new
35
+ post.document = Refile::FileDouble.new("hello", "image.PNG")
36
+ expect(post.valid?).to be_truthy
37
+ expect(post.errors[:document]).to be_empty
38
+ end
39
+
40
+ it "returns false when extension is invalid" do
41
+ post = klass.new
42
+ post.document = Refile::FileDouble.new("hello", "image.jpg")
43
+ expect(post.valid?).to be_falsy
44
+ expect(post.errors[:document].length).to eq(1)
45
+ end
46
+ end
47
+
48
+ context "with metadata" do
49
+ it "returns true when extension is included in list" do
50
+ file = Refile.cache.upload(StringIO.new("hello"))
51
+ post = klass.new
52
+ post.document = { id: file.id, filename: "image.Png" }.to_json
53
+ expect(post.valid?).to be_truthy
54
+ expect(post.errors[:document]).to be_empty
55
+ end
56
+
57
+ it "returns true when extension is included in list but chars are randomcase" do
58
+ file = Refile.cache.upload(StringIO.new("hello"))
59
+ post = klass.new
60
+ post.document = { id: file.id, filename: "image.PNG" }.to_json
61
+ expect(post.valid?).to be_truthy
62
+ expect(post.errors[:document]).to be_empty
63
+ end
64
+
65
+ it "returns false when extension is invalid" do
66
+ file = Refile.cache.upload(StringIO.new("hello"))
67
+ post = klass.new
68
+ post.document = { id: file.id, filename: "image.jpg" }.to_json
69
+ expect(post.valid?).to be_falsy
70
+ expect(post.errors[:document].length).to eq(1)
71
+ end
72
+ end
73
+ end
21
74
 
22
75
  context "with file" do
23
76
  it "returns true when no file is assigned" do
@@ -33,6 +86,13 @@ describe Refile::ActiveRecord::Attachment do
33
86
  expect(post.errors[:document].length).to eq(1)
34
87
  end
35
88
 
89
+ it "returns false when it has multiple errors" do
90
+ post = klass.new
91
+ post.document = Refile::FileDouble.new("h" * 200, content_type: "text/plain")
92
+ expect(post.valid?).to be_falsy
93
+ expect(post.errors[:document].length).to eq(2)
94
+ end
95
+
36
96
  it "returns true when type is invalid" do
37
97
  post = klass.new
38
98
  post.document = Refile::FileDouble.new("hello", content_type: "image/png")
@@ -77,6 +137,20 @@ describe Refile::ActiveRecord::Attachment do
77
137
  expect(post.document.read).to eq("hello")
78
138
  expect(Refile.store.read(post.document.id)).to eq("hello")
79
139
  end
140
+
141
+ it "replaces an existing file" do
142
+ post = klass.new
143
+ post.document = Refile::FileDouble.new("hello")
144
+ post.save
145
+ old_document = post.document
146
+
147
+ post = klass.find(post.id)
148
+ post.document = Refile::FileDouble.new("hello")
149
+ post.save
150
+
151
+ expect(Refile.store.read(post.document.id)).to eq("hello")
152
+ expect(post.document.id).not_to be eq old_document.id
153
+ end
80
154
  end
81
155
 
82
156
  describe "#destroy" do
@@ -89,4 +163,227 @@ describe Refile::ActiveRecord::Attachment do
89
163
  expect(file.exists?).to be_falsy
90
164
  end
91
165
  end
166
+
167
+ describe ".accepts_nested_attributes_for" do
168
+ let(:options) { {} }
169
+ let(:post_class) do
170
+ opts = options
171
+ foo = document_class
172
+ Class.new(ActiveRecord::Base) do
173
+ self.table_name = :posts
174
+
175
+ def self.name
176
+ "Post"
177
+ end
178
+
179
+ has_many :documents, anonymous_class: foo, dependent: :destroy
180
+ accepts_attachments_for :documents, **opts
181
+ end
182
+ end
183
+
184
+ let(:document_class) do
185
+ Class.new(ActiveRecord::Base) do
186
+ self.table_name = :documents
187
+
188
+ def self.name
189
+ "Document"
190
+ end
191
+
192
+ attachment :file
193
+ end
194
+ end
195
+
196
+ let(:post) { post_class.new }
197
+
198
+ describe "#:association_:name" do
199
+ it "builds records from assigned files" do
200
+ post.documents_files = [Refile::FileDouble.new("hello"), Refile::FileDouble.new("world")]
201
+ expect(post.documents[0].file.read).to eq("hello")
202
+ expect(post.documents[1].file.read).to eq("world")
203
+ expect(post.documents.size).to eq(2)
204
+ end
205
+
206
+ it "builds records from cache" do
207
+ post.documents_files = [
208
+ [
209
+ { id: Refile.cache.upload(Refile::FileDouble.new("hello")).id },
210
+ { id: Refile.cache.upload(Refile::FileDouble.new("world")).id }
211
+ ].to_json
212
+ ]
213
+ expect(post.documents[0].file.read).to eq("hello")
214
+ expect(post.documents[1].file.read).to eq("world")
215
+ expect(post.documents.size).to eq(2)
216
+ end
217
+
218
+ it "prefers newly uploaded files over cache" do
219
+ post.documents_files = [
220
+ [
221
+ { id: Refile.cache.upload(Refile::FileDouble.new("moo")).id }
222
+ ].to_json,
223
+ Refile::FileDouble.new("hello"),
224
+ Refile::FileDouble.new("world")
225
+ ]
226
+ expect(post.documents[0].file.read).to eq("hello")
227
+ expect(post.documents[1].file.read).to eq("world")
228
+ expect(post.documents.size).to eq(2)
229
+ end
230
+
231
+ it "clears previously assigned files" do
232
+ post.documents_files = [
233
+ Refile::FileDouble.new("hello"),
234
+ Refile::FileDouble.new("world")
235
+ ]
236
+ post.save
237
+ post.update_attributes documents_files: [
238
+ Refile::FileDouble.new("foo")
239
+ ]
240
+ retrieved = post_class.find(post.id)
241
+ expect(retrieved.documents[0].file.read).to eq("foo")
242
+ expect(retrieved.documents.size).to eq(1)
243
+ end
244
+
245
+ context "with append: true" do
246
+ let(:options) { { append: true } }
247
+
248
+ it "appends to previously assigned files" do
249
+ post.documents_files = [
250
+ Refile::FileDouble.new("hello"),
251
+ Refile::FileDouble.new("world")
252
+ ]
253
+ post.save
254
+ post.update_attributes documents_files: [
255
+ Refile::FileDouble.new("foo")
256
+ ]
257
+ retrieved = post_class.find(post.id)
258
+ expect(retrieved.documents[0].file.read).to eq("hello")
259
+ expect(retrieved.documents[1].file.read).to eq("world")
260
+ expect(retrieved.documents[2].file.read).to eq("foo")
261
+ expect(retrieved.documents.size).to eq(3)
262
+ end
263
+
264
+ it "appends to previously assigned files with cached files" do
265
+ post.documents_files = [
266
+ Refile::FileDouble.new("hello"),
267
+ Refile::FileDouble.new("world")
268
+ ]
269
+ post.save
270
+ post.update_attributes documents_files: [
271
+ [{
272
+ id: Refile.cache.upload(Refile::FileDouble.new("hello")).id,
273
+ filename: "some.jpg",
274
+ content_type: "image/jpeg",
275
+ size: 1234
276
+ }].to_json
277
+ ]
278
+ retrieved = post_class.find(post.id)
279
+ expect(retrieved.documents.size).to eq(3)
280
+ end
281
+ end
282
+ end
283
+
284
+ describe "#:association_:name_data" do
285
+ it "returns metadata of all files" do
286
+ post.documents_files = [nil, Refile::FileDouble.new("hello"), Refile::FileDouble.new("world")]
287
+ data = post.documents_files_data
288
+ expect(Refile.cache.read(data[0][:id])).to eq("hello")
289
+ expect(Refile.cache.read(data[1][:id])).to eq("world")
290
+ expect(data.size).to eq(2)
291
+ end
292
+ end
293
+ end
294
+
295
+ context "when attachment assigned to nested model" do
296
+ let(:base_users_class) do
297
+ Class.new(ActiveRecord::Base) do
298
+ self.table_name = :users
299
+
300
+ def self.name
301
+ "User"
302
+ end
303
+ end
304
+ end
305
+
306
+ context "when model has one nested attachment" do
307
+ let(:users_class) do
308
+ posts_class = klass
309
+ base_users_class.tap do |klass|
310
+ klass.instance_eval do
311
+ has_one :post, anonymous_class: posts_class
312
+ accepts_nested_attributes_for :post
313
+ end
314
+ end
315
+ end
316
+
317
+ describe "#save" do
318
+ it "stores the assigned file" do
319
+ user = users_class.create! post_attributes: { document: Refile::FileDouble.new("foo") }
320
+
321
+ post = user.post.reload
322
+ expect(post.document.read).to eq("foo")
323
+ expect(Refile.store.read(post.document.id)).to eq("foo")
324
+ end
325
+
326
+ it "removes files marked for removal" do
327
+ user = users_class.create!
328
+ post = klass.create!(user_id: user.id, document: Refile::FileDouble.new("foo"))
329
+
330
+ user.update_attributes!(post_attributes: { id: post.id, remove_document: true })
331
+
332
+ expect(post.reload.document).to be_nil
333
+ end
334
+
335
+ it "replaces an existing file" do
336
+ user = users_class.create! post_attributes: { document: Refile::FileDouble.new("foo") }
337
+ post = user.post
338
+
339
+ user.update! post_attributes: { id: post.id, document: Refile::FileDouble.new("bar") }
340
+
341
+ post.reload
342
+ expect(post.document.read).to eq("bar")
343
+ expect(Refile.store.read(post.document.id)).to eq("bar")
344
+ end
345
+ end
346
+ end
347
+
348
+ context "when model has many nested attachments" do
349
+ let(:users_class) do
350
+ posts_class = klass
351
+ base_users_class.tap do |klass|
352
+ klass.instance_eval do
353
+ has_many :posts, anonymous_class: posts_class
354
+ accepts_nested_attributes_for :posts
355
+ end
356
+ end
357
+ end
358
+
359
+ describe "#save" do
360
+ it "stores the assigned file" do
361
+ user = users_class.create! posts_attributes: [{ document: Refile::FileDouble.new("foo") }]
362
+
363
+ post = user.posts.first.reload
364
+ expect(post.document.read).to eq("foo")
365
+ expect(Refile.store.read(post.document.id)).to eq("foo")
366
+ end
367
+
368
+ it "removes files marked for removal" do
369
+ user = users_class.create!
370
+ post = klass.create!(user_id: user.id, document: Refile::FileDouble.new("foo"))
371
+
372
+ user.update_attributes!(posts_attributes: { id: post.id, remove_document: true })
373
+
374
+ expect(post.reload.document).to be_nil
375
+ end
376
+
377
+ it "replaces an existing file" do
378
+ user = users_class.create! posts_attributes: [{ document: Refile::FileDouble.new("foo") }]
379
+ post = user.posts.first
380
+ user.update! posts_attributes: [{ id: post.id, document: Refile::FileDouble.new("bar") }]
381
+
382
+ post.reload
383
+ expect(post.document.read).to eq("bar")
384
+ expect(Refile.store.read(post.document.id)).to eq("bar")
385
+ end
386
+ end
387
+ end
388
+ end
92
389
  end
@@ -0,0 +1,39 @@
1
+ require "refile/rails/attachment_helper"
2
+ require "refile/active_record_helper"
3
+ require "refile/attachment/active_record"
4
+ require "action_view"
5
+
6
+ describe Refile::AttachmentHelper do
7
+ include Refile::AttachmentHelper
8
+ include ActionView::Helpers::AssetTagHelper
9
+
10
+ let(:klass) do
11
+ Class.new(ActiveRecord::Base) do
12
+ self.table_name = :posts
13
+
14
+ def self.name
15
+ "Post"
16
+ end
17
+
18
+ attachment :document
19
+ end
20
+ end
21
+ let(:attachment_path) { "/attachments/00cc2633d08c6045485f1fae2cd6d4de20a5a159/store/xxx/document" }
22
+
23
+ before do
24
+ allow(Refile).to receive(:secret_key).and_return("xxxxxxxxxxx")
25
+ end
26
+
27
+ describe "#attachment_image_tag" do
28
+ let(:src) { attachment_image_tag(klass.new(document_id: "xxx"), :document)[/src="(\S+)"/, 1] }
29
+
30
+ it "builds with path" do
31
+ allow(Refile).to receive(:app_host).and_return(nil)
32
+ expect(src).to eq attachment_path
33
+ end
34
+
35
+ it "builds with host" do
36
+ expect(src).to eq "http://localhost:56120#{attachment_path}"
37
+ end
38
+ end
39
+ end
@@ -42,6 +42,21 @@ describe Refile::Attachment do
42
42
  expect(instance.document_content_type).to eq("text/plain")
43
43
  end
44
44
 
45
+ it "receives parsed data and retrieves file from it" do
46
+ file = Refile.cache.upload(Refile::FileDouble.new("hello"))
47
+ instance.document = { id: file.id, filename: "foo.txt", content_type: "text/plain", size: 5 }
48
+
49
+ expect(instance.document.read).to eq("hello")
50
+
51
+ expect(instance.document_attacher.data[:filename]).to eq("foo.txt")
52
+ expect(instance.document_attacher.data[:size]).to eq(5)
53
+ expect(instance.document_attacher.data[:content_type]).to eq("text/plain")
54
+
55
+ expect(instance.document_filename).to eq("foo.txt")
56
+ expect(instance.document_size).to eq(5)
57
+ expect(instance.document_content_type).to eq("text/plain")
58
+ end
59
+
45
60
  it "does nothing when assigned string lacks an id" do
46
61
  instance.document = { size: 5 }.to_json
47
62
 
@@ -64,15 +79,36 @@ describe Refile::Attachment do
64
79
 
65
80
  expect(instance.document.id).to eq(file.id)
66
81
  end
82
+
83
+ it "complains when `id` attribute not readable" do
84
+ klass.class_eval do
85
+ undef :document_id
86
+ end
87
+
88
+ expect do
89
+ instance.document
90
+ end.to raise_error(NoMethodError)
91
+ end
92
+ end
93
+
94
+ describe ":url" do
95
+ it "generates URL with extra options" do
96
+ allow(Refile).to receive(:token).and_return("token")
97
+
98
+ file = Refile.store.upload(Refile::FileDouble.new("hello"))
99
+ instance.document_id = file.id
100
+
101
+ expect(instance.document_url("fill", 800, 800)).to eq("http://localhost:56120/attachments/token/store/fill/800/800/#{file.id}/document")
102
+ end
67
103
  end
68
104
 
69
105
  describe "remote_:name_url=" do
70
- it "does nothign when nil is assigned" do
106
+ it "does nothing when nil is assigned" do
71
107
  instance.remote_document_url = nil
72
108
  expect(instance.document).to be_nil
73
109
  end
74
110
 
75
- it "does nothign when empty string is assigned" do
111
+ it "does nothing when empty string is assigned" do
76
112
  instance.remote_document_url = nil
77
113
  expect(instance.document).to be_nil
78
114
  end
@@ -152,6 +188,18 @@ describe Refile::Attachment do
152
188
  expect(Refile.cache.get(cache.id).exists?).to be_falsy
153
189
  end
154
190
 
191
+ it "complains when `id` attribute not writable" do
192
+ instance.document = Refile::FileDouble.new("hello")
193
+
194
+ klass.class_eval do
195
+ undef :document_id=
196
+ end
197
+
198
+ expect do
199
+ instance.document_attacher.store!
200
+ end.to raise_error(NoMethodError)
201
+ end
202
+
155
203
  it "does nothing when not cached" do
156
204
  file = Refile.store.upload(Refile::FileDouble.new("hello"))
157
205
  instance.document_id = file.id
@@ -348,12 +396,12 @@ describe Refile::Attachment do
348
396
  end
349
397
  end
350
398
 
351
- describe ":name_attacher.accept" do
399
+ describe ":name_definition.accept" do
352
400
  context "with `extension`" do
353
401
  let(:options) { { extension: %w[jpg png] } }
354
402
 
355
403
  it "returns an accept string" do
356
- expect(instance.document_attacher.accept).to eq(".jpg,.png")
404
+ expect(instance.document_attachment_definition.accept).to eq(".jpg,.png")
357
405
  end
358
406
  end
359
407
 
@@ -361,7 +409,7 @@ describe Refile::Attachment do
361
409
  let(:options) { { content_type: %w[image/jpeg image/png], extension: "zip" } }
362
410
 
363
411
  it "returns an accept string" do
364
- expect(instance.document_attacher.accept).to eq("image/jpeg,image/png")
412
+ expect(instance.document_attachment_definition.accept).to eq("image/jpeg,image/png")
365
413
  end
366
414
  end
367
415
  end