kt-paperclip 6.2.1 → 7.0.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/custom.md +10 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.hound.yml +364 -357
  7. data/.rubocop.yml +2 -0
  8. data/.travis.yml +12 -14
  9. data/Appraisals +6 -0
  10. data/Gemfile +3 -3
  11. data/NEWS +28 -0
  12. data/README.md +41 -19
  13. data/features/step_definitions/attachment_steps.rb +11 -1
  14. data/gemfiles/4.2.gemfile +1 -1
  15. data/gemfiles/5.0.gemfile +1 -1
  16. data/gemfiles/5.1.gemfile +1 -1
  17. data/gemfiles/5.2.gemfile +1 -1
  18. data/gemfiles/6.0.gemfile +1 -1
  19. data/gemfiles/6.1.gemfile +21 -0
  20. data/lib/paperclip.rb +3 -3
  21. data/lib/paperclip/content_type_detector.rb +4 -4
  22. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +2 -2
  23. data/lib/paperclip/io_adapters/uri_adapter.rb +13 -3
  24. data/lib/paperclip/schema.rb +2 -2
  25. data/lib/paperclip/storage/s3.rb +2 -2
  26. data/lib/paperclip/url_generator.rb +8 -1
  27. data/lib/paperclip/validators.rb +4 -4
  28. data/lib/paperclip/validators/attachment_content_type_validator.rb +9 -2
  29. data/lib/paperclip/validators/attachment_file_name_validator.rb +9 -2
  30. data/lib/paperclip/validators/attachment_presence_validator.rb +1 -1
  31. data/lib/paperclip/validators/attachment_size_validator.rb +18 -2
  32. data/lib/paperclip/version.rb +1 -1
  33. data/paperclip.gemspec +4 -4
  34. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +1 -1
  35. data/spec/paperclip/io_adapters/file_adapter_spec.rb +1 -1
  36. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +20 -15
  37. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +13 -3
  38. data/spec/paperclip/storage/s3_spec.rb +54 -3
  39. data/spec/paperclip/url_generator_spec.rb +10 -0
  40. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +88 -0
  41. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +90 -0
  42. data/spec/paperclip/validators/attachment_size_validator_spec.rb +90 -0
  43. data/spec/support/fixtures/aws_s3.yml +13 -0
  44. data/spec/support/model_reconstruction.rb +1 -1
  45. metadata +17 -12
  46. data/.github/issue_template.md +0 -3
@@ -26,7 +26,7 @@ module Paperclip
26
26
  attachment_names.each do |attachment_name|
27
27
  COLUMNS.each_pair do |column_name, column_type|
28
28
  column_options = options.merge(options[column_name.to_sym] || {})
29
- add_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
29
+ add_column(table_name, "#{attachment_name}_#{column_name}", column_type, **column_options)
30
30
  end
31
31
  end
32
32
  end
@@ -55,7 +55,7 @@ module Paperclip
55
55
  attachment_names.each do |attachment_name|
56
56
  COLUMNS.each_pair do |column_name, column_type|
57
57
  column_options = options.merge(options[column_name.to_sym] || {})
58
- column("#{attachment_name}_#{column_name}", column_type, column_options)
58
+ column("#{attachment_name}_#{column_name}", column_type, **column_options)
59
59
  end
60
60
  end
61
61
  end
@@ -427,9 +427,9 @@ module Paperclip
427
427
  def find_credentials(creds)
428
428
  case creds
429
429
  when File
430
- YAML::safe_load(ERB.new(File.read(creds.path)).result)
430
+ YAML::safe_load(ERB.new(File.read(creds.path)).result, [], [], true)
431
431
  when String, Pathname
432
- YAML::safe_load(ERB.new(File.read(creds)).result)
432
+ YAML::safe_load(ERB.new(File.read(creds)).result, [], [], true)
433
433
  when Hash
434
434
  creds
435
435
  when NilClass
@@ -3,6 +3,13 @@ require "active_support/core_ext/module/delegation"
3
3
 
4
4
  module Paperclip
5
5
  class UrlGenerator
6
+ class << self
7
+ def encoder
8
+ @encoder ||= URI::RFC2396_Parser.new
9
+ end
10
+ delegate :escape, :unescape, to: :encoder
11
+ end
12
+
6
13
  def initialize(attachment)
7
14
  @attachment = attachment
8
15
  end
@@ -65,7 +72,7 @@ module Paperclip
65
72
  if url.respond_to?(:escape)
66
73
  url.escape
67
74
  else
68
- URI.escape(url).gsub(escape_regex) { |m| "%#{m.ord.to_s(16).upcase}" }
75
+ self.class.escape(url).gsub(escape_regex) { |m| "%#{m.ord.to_s(16).upcase}" }
69
76
  end
70
77
  end
71
78
 
@@ -20,10 +20,10 @@ module Paperclip
20
20
  ::Paperclip::REQUIRED_VALIDATORS = [AttachmentFileNameValidator, AttachmentContentTypeValidator, AttachmentFileTypeIgnoranceValidator].freeze
21
21
 
22
22
  module ClassMethods
23
- # This method is a shortcut to validator classes that is in
24
- # "Attachment...Validator" format. It is almost the same thing as the
25
- # +validates+ method that shipped with Rails, but this is customized to
26
- # be using with attachment validators. This is helpful when you're using
23
+ # This method is a shortcut to the validator classes that are in
24
+ # "Attachment...Validator" format. It is almost the same as the
25
+ # +validates+ method that ships with Rails, but is customized for
26
+ # use with attachment validators. This is helpful when you're using
27
27
  # multiple attachment validators on a single attachment.
28
28
  #
29
29
  # Example of using the validator:
@@ -3,6 +3,9 @@ module Paperclip
3
3
  class AttachmentContentTypeValidator < ActiveModel::EachValidator
4
4
  def initialize(options)
5
5
  options[:allow_nil] = true unless options.key?(:allow_nil)
6
+ unless options.key?(:add_validation_errors_to)
7
+ options[:add_validation_errors_to] = Paperclip.options[:add_validation_errors_to]
8
+ end
6
9
  super
7
10
  end
8
11
 
@@ -20,10 +23,14 @@ module Paperclip
20
23
  validate_whitelist(record, attribute, value)
21
24
  validate_blacklist(record, attribute, value)
22
25
 
23
- if record.errors.include? attribute
26
+ if record.errors.include?(attribute) &&
27
+ [:both, :base].include?(options[:add_validation_errors_to])
28
+
24
29
  record.errors[attribute].each do |error|
25
30
  record.errors.add base_attribute, error
26
31
  end
32
+
33
+ record.errors.delete(attribute) if options[:add_validation_errors_to] == :base
27
34
  end
28
35
  end
29
36
 
@@ -40,7 +47,7 @@ module Paperclip
40
47
  end
41
48
 
42
49
  def mark_invalid(record, attribute, types)
43
- record.errors.add attribute, :invalid, options.merge(types: types.join(", "))
50
+ record.errors.add attribute, :invalid, **options.merge(types: types.join(", "))
44
51
  end
45
52
 
46
53
  def allowed_types
@@ -3,6 +3,9 @@ module Paperclip
3
3
  class AttachmentFileNameValidator < ActiveModel::EachValidator
4
4
  def initialize(options)
5
5
  options[:allow_nil] = true unless options.key?(:allow_nil)
6
+ unless options.key?(:add_validation_errors_to)
7
+ options[:add_validation_errors_to] = Paperclip.options[:add_validation_errors_to]
8
+ end
6
9
  super
7
10
  end
8
11
 
@@ -20,10 +23,14 @@ module Paperclip
20
23
  validate_whitelist(record, attribute, value)
21
24
  validate_blacklist(record, attribute, value)
22
25
 
23
- if record.errors.include? attribute
26
+ if record.errors.include?(attribute) &&
27
+ [:both, :base].include?(options[:add_validation_errors_to])
28
+
24
29
  record.errors[attribute].each do |error|
25
30
  record.errors.add base_attribute, error
26
31
  end
32
+
33
+ record.errors.delete(attribute) if options[:add_validation_errors_to] == :base
27
34
  end
28
35
  end
29
36
 
@@ -36,7 +43,7 @@ module Paperclip
36
43
  end
37
44
 
38
45
  def mark_invalid(record, attribute, patterns)
39
- record.errors.add attribute, :invalid, options.merge(names: patterns.join(", "))
46
+ record.errors.add attribute, :invalid, **options.merge(names: patterns.join(", "))
40
47
  end
41
48
 
42
49
  def allowed
@@ -4,7 +4,7 @@ module Paperclip
4
4
  module Validators
5
5
  class AttachmentPresenceValidator < ActiveModel::EachValidator
6
6
  def validate_each(record, attribute, _value)
7
- record.errors.add(attribute, :blank, options) if record.send("#{attribute}_file_name").blank?
7
+ record.errors.add(attribute, :blank, **options) if record.send("#{attribute}_file_name").blank?
8
8
  end
9
9
 
10
10
  def self.helper_method_name
@@ -17,6 +17,18 @@ module Paperclip
17
17
  def validate_each(record, attr_name, value)
18
18
  base_attr_name = attr_name
19
19
  attr_name = "#{attr_name}_file_size".to_sym
20
+
21
+ error_attrs = []
22
+ case options[:add_validation_errors_to]
23
+ when :base
24
+ error_attrs << base_attr_name
25
+ when :attribute
26
+ error_attrs << attr_name
27
+ else
28
+ error_attrs << base_attr_name
29
+ error_attrs << attr_name
30
+ end
31
+
20
32
  value = record.send(:read_attribute_for_validation, attr_name)
21
33
 
22
34
  unless value.blank?
@@ -26,8 +38,8 @@ module Paperclip
26
38
 
27
39
  unless value.send(CHECKS[option], option_value)
28
40
  error_message_key = options[:in] ? :in_between : option
29
- [attr_name, base_attr_name].each do |error_attr_name|
30
- record.errors.add(error_attr_name, error_message_key, filtered_options(value).merge(
41
+ error_attrs.each do |error_attr_name|
42
+ record.errors.add(error_attr_name, error_message_key, **filtered_options(value).merge(
31
43
  min: min_value_in_human_size(record),
32
44
  max: max_value_in_human_size(record),
33
45
  count: human_size(option_value)
@@ -56,6 +68,10 @@ module Paperclip
56
68
  options[:greater_than_or_equal_to] = range
57
69
  end
58
70
  end
71
+
72
+ unless options.key?(:add_validation_errors_to)
73
+ options[:add_validation_errors_to] = Paperclip.options[:add_validation_errors_to]
74
+ end
59
75
  end
60
76
 
61
77
  def extract_option_value(option, option_value)
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "6.2.1" unless defined?(Paperclip::VERSION)
2
+ VERSION = "7.0.0" unless defined?(Paperclip::VERSION)
3
3
  end
data/paperclip.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.author = "Surendra Singhi"
9
9
  s.email = ["ssinghi@kreeti.com"]
10
- s.homepage = "https://github.com/kreeti/paperclip"
10
+ s.homepage = "https://github.com/kreeti/kt-paperclip"
11
11
  s.summary = "File attachments as attributes for ActiveRecord"
12
12
  s.description = "Easy upload management for ActiveRecord"
13
13
  s.license = "MIT"
@@ -20,12 +20,12 @@ Gem::Specification.new do |s|
20
20
  s.post_install_message = File.read("UPGRADING") if File.exist?("UPGRADING")
21
21
 
22
22
  s.requirements << "ImageMagick"
23
- s.required_ruby_version = ">= 2.1.0"
23
+ s.required_ruby_version = ">= 2.3.0"
24
24
 
25
25
  s.add_dependency("activemodel", ">= 4.2.0")
26
26
  s.add_dependency("activesupport", ">= 4.2.0")
27
27
  s.add_dependency("mime-types")
28
- s.add_dependency("mimemagic", "~> 0.3.0")
28
+ s.add_dependency("marcel", "~> 1.0.1")
29
29
  s.add_dependency("terrapin", "~> 0.6.0")
30
30
 
31
31
  s.add_development_dependency("activerecord", ">= 4.2.0")
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
34
34
  s.add_development_dependency("aws-sdk-s3")
35
35
  s.add_development_dependency("bundler")
36
36
  s.add_development_dependency("capybara")
37
- s.add_development_dependency("cucumber-expressions", "4.0.3") # TODO: investigate failures on 4.0.4
37
+ s.add_development_dependency("cucumber-expressions")
38
38
  s.add_development_dependency("cucumber-rails")
39
39
  s.add_development_dependency("fakeweb")
40
40
  s.add_development_dependency("fog-aws")
@@ -15,7 +15,7 @@ describe Paperclip::AbstractAdapter do
15
15
  before do
16
16
  allow(subject).to receive(:path).and_return("image.png")
17
17
  allow(Paperclip).to receive(:run).and_return("image/png\n")
18
- allow_any_instance_of(Paperclip::ContentTypeDetector).to receive(:type_from_mime_magic).and_return("image/png")
18
+ allow_any_instance_of(Paperclip::ContentTypeDetector).to receive(:type_from_marcel).and_return("image/png")
19
19
  end
20
20
 
21
21
  it "returns the content type without newline" do
@@ -78,7 +78,7 @@ describe Paperclip::FileAdapter do
78
78
  allow(MIME::Types).to receive(:type_for).and_return([])
79
79
  allow(Paperclip).to receive(:run).and_return("application/vnd.ms-office\n")
80
80
  allow_any_instance_of(Paperclip::ContentTypeDetector).
81
- to receive(:type_from_mime_magic).and_return("application/vnd.ms-office")
81
+ to receive(:type_from_marcel).and_return("application/vnd.ms-office")
82
82
 
83
83
  @subject = Paperclip.io_adapters.for(@file)
84
84
  end
@@ -16,54 +16,59 @@ describe Paperclip::HttpUrlProxyAdapter do
16
16
  context "a new instance" do
17
17
  before do
18
18
  @url = "http://thoughtbot.com/images/thoughtbot-logo.png"
19
- @subject = Paperclip.io_adapters.for(@url, hash_digest: Digest::MD5)
20
19
  end
21
20
 
21
+ subject { Paperclip.io_adapters.for(@url, hash_digest: Digest::MD5) }
22
+
22
23
  after do
23
- @subject.close
24
+ subject.close
24
25
  end
25
26
 
26
27
  it "returns a file name" do
27
- assert_equal "thoughtbot-logo.png", @subject.original_filename
28
+ expect(subject.original_filename).to(eq("thoughtbot-logo.png"))
28
29
  end
29
30
 
30
31
  it "closes open handle after reading" do
31
- assert_equal true, @open_return.closed?
32
+ expect { subject }.to(change { @open_return.closed? }.from(false).to(true))
32
33
  end
33
34
 
34
35
  it "returns a content type" do
35
- assert_equal "image/png", @subject.content_type
36
+ expect(subject.content_type).to(eq("image/png"))
36
37
  end
37
38
 
38
39
  it "returns the size of the data" do
39
- assert_equal @open_return.size, @subject.size
40
+ expect(subject.size).to(eq(@open_return.size))
40
41
  end
41
42
 
42
43
  it "generates an MD5 hash of the contents" do
43
- assert_equal Digest::MD5.hexdigest("xxx"), @subject.fingerprint
44
+ expect(subject.fingerprint).to(eq(Digest::MD5.hexdigest("xxx")))
44
45
  end
45
46
 
46
47
  it "generates correct fingerprint after read" do
47
- fingerprint = Digest::MD5.hexdigest(@subject.read)
48
- assert_equal fingerprint, @subject.fingerprint
48
+ fingerprint = Digest::MD5.hexdigest(subject.read)
49
+ expect(subject.fingerprint).to(eq(fingerprint))
49
50
  end
50
51
 
51
52
  it "generates same fingerprint" do
52
- assert_equal @subject.fingerprint, @subject.fingerprint
53
+ expect(subject.fingerprint).to(eq(subject.fingerprint))
53
54
  end
54
55
 
55
56
  it "returns the data contained in the StringIO" do
56
- assert_equal "xxx", @subject.read
57
+ expect(subject.read).to(eq("xxx"))
57
58
  end
58
59
 
59
60
  it "accepts a content_type" do
60
- @subject.content_type = "image/png"
61
- assert_equal "image/png", @subject.content_type
61
+ subject.content_type = "image/png"
62
+ expect(subject.content_type).to(eq("image/png"))
62
63
  end
63
64
 
64
65
  it "accepts an original_filename" do
65
- @subject.original_filename = "image.png"
66
- assert_equal "image.png", @subject.original_filename
66
+ subject.original_filename = "image.png"
67
+ expect(subject.original_filename).to(eq("image.png"))
68
+ end
69
+
70
+ it "doesn't emit deprecation warnings" do
71
+ expect { subject }.to_not(output(/URI\.(un)?escape is obsolete/).to_stderr)
67
72
  end
68
73
  end
69
74
 
@@ -193,9 +193,18 @@ describe Paperclip::UriAdapter do
193
193
 
194
194
  describe "#download_content" do
195
195
  before do
196
- allow_any_instance_of(Paperclip::UriAdapter).to receive(:open).and_return(@open_return)
196
+ allowed_mock =
197
+ if RUBY_VERSION < '2.5'
198
+ allow_any_instance_of(Paperclip::UriAdapter)
199
+ else
200
+ allow(URI)
201
+ end
202
+
203
+ allowed_mock.to receive(:open).and_return(@open_return)
204
+
197
205
  @uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
198
206
  @subject = Paperclip.io_adapters.for(@uri)
207
+ @uri_opener = RUBY_VERSION < '2.5' ? @subject : URI
199
208
  end
200
209
 
201
210
  after do
@@ -204,7 +213,7 @@ describe Paperclip::UriAdapter do
204
213
 
205
214
  context "with default read_timeout" do
206
215
  it "calls open without options" do
207
- expect(@subject).to receive(:open).with(@uri, {}).at_least(1).times
216
+ expect(@uri_opener).to receive(:open).with(@uri, {}).at_least(1).times
208
217
  end
209
218
  end
210
219
 
@@ -214,7 +223,8 @@ describe Paperclip::UriAdapter do
214
223
  end
215
224
 
216
225
  it "calls open with read_timeout option" do
217
- expect(@subject).to receive(:open).with(@uri, read_timeout: 120).at_least(1).times
226
+ expect(@uri_opener)
227
+ .to receive(:open).with(@uri, read_timeout: 120).at_least(1).times
218
228
  end
219
229
  end
220
230
  end
@@ -805,9 +805,7 @@ describe Paperclip::Storage::S3 do
805
805
  s3_region: "ap-northeast-1",
806
806
  s3_host_name: "s3-ap-northeast-1.amazonaws.com"
807
807
  },
808
- test: {
809
- s3_region: ""
810
- }
808
+ test: {}
811
809
  }
812
810
  @dummy = Dummy.new
813
811
  end
@@ -1439,6 +1437,32 @@ describe Paperclip::Storage::S3 do
1439
1437
  end
1440
1438
  end
1441
1439
 
1440
+ context "with S3 credentials supplied as Pathname and aliases being set" do
1441
+ before do
1442
+ ENV["S3_KEY"] = "pathname_key"
1443
+ ENV["S3_BUCKET"] = "pathname_bucket"
1444
+ ENV["S3_SECRET"] = "pathname_secret"
1445
+
1446
+ rails_env("test") do
1447
+ rebuild_model aws2_add_region.merge storage: :s3,
1448
+ s3_credentials: Pathname.new(fixture_file("aws_s3.yml"))
1449
+
1450
+ Dummy.delete_all
1451
+ @dummy = Dummy.new
1452
+ end
1453
+ end
1454
+
1455
+ it "parses the credentials" do
1456
+ assert_equal "pathname_bucket", @dummy.avatar.bucket_name
1457
+
1458
+ assert_equal "pathname_key",
1459
+ @dummy.avatar.s3_bucket.client.config.access_key_id
1460
+
1461
+ assert_equal "pathname_secret",
1462
+ @dummy.avatar.s3_bucket.client.config.secret_access_key
1463
+ end
1464
+ end
1465
+
1442
1466
  context "with S3 credentials in a YAML file" do
1443
1467
  before do
1444
1468
  ENV["S3_KEY"] = "env_key"
@@ -1466,6 +1490,33 @@ describe Paperclip::Storage::S3 do
1466
1490
  end
1467
1491
  end
1468
1492
 
1493
+ context "with S3 credentials in a YAML file and aliases being set" do
1494
+ before do
1495
+ ENV["S3_KEY"] = "env_key"
1496
+ ENV["S3_BUCKET"] = "env_bucket"
1497
+ ENV["S3_SECRET"] = "env_secret"
1498
+
1499
+ rails_env("test") do
1500
+ rebuild_model aws2_add_region.merge storage: :s3,
1501
+ s3_credentials: File.new(fixture_file("aws_s3.yml"))
1502
+
1503
+ Dummy.delete_all
1504
+
1505
+ @dummy = Dummy.new
1506
+ end
1507
+ end
1508
+
1509
+ it "runs the file through ERB" do
1510
+ assert_equal "env_bucket", @dummy.avatar.bucket_name
1511
+
1512
+ assert_equal "env_key",
1513
+ @dummy.avatar.s3_bucket.client.config.access_key_id
1514
+
1515
+ assert_equal "env_secret",
1516
+ @dummy.avatar.s3_bucket.client.config.secret_access_key
1517
+ end
1518
+ end
1519
+
1469
1520
  context "S3 Permissions" do
1470
1521
  context "defaults to :public_read" do
1471
1522
  before do
@@ -194,6 +194,16 @@ describe Paperclip::UrlGenerator do
194
194
  "expected the interpolator to be passed #{expected.inspect} but it wasn't"
195
195
  end
196
196
 
197
+ it "doesn't emit deprecation warnings" do
198
+ expected = "the expected result"
199
+ mock_interpolator = MockInterpolator.new(result: expected)
200
+ options = { interpolator: mock_interpolator }
201
+ mock_attachment = MockAttachment.new(options)
202
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment)
203
+
204
+ expect { url_generator.for(:style_name, escape: true) }.to_not(output(/URI\.(un)?escape is obsolete/).to_stderr)
205
+ end
206
+
197
207
  describe "should be able to escape (, ), [, and ]." do
198
208
  def generate(expected, updated_at = nil)
199
209
  mock_interpolator = MockInterpolator.new(result: expected)