paperclip 2.8.0 → 3.0.2

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

Potentially problematic release.


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

Files changed (94) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +9 -7
  3. data/Appraisals +6 -12
  4. data/Gemfile +2 -0
  5. data/NEWS +24 -0
  6. data/README.md +53 -21
  7. data/Rakefile +7 -2
  8. data/UPGRADING +14 -0
  9. data/features/basic_integration.feature +8 -8
  10. data/features/rake_tasks.feature +1 -1
  11. data/features/step_definitions/attachment_steps.rb +11 -2
  12. data/features/step_definitions/rails_steps.rb +17 -79
  13. data/features/support/env.rb +3 -0
  14. data/features/support/file_helpers.rb +24 -0
  15. data/features/support/rails.rb +3 -3
  16. data/gemfiles/{rails3_1.gemfile → 3.0.gemfile} +3 -1
  17. data/gemfiles/{rails2.gemfile → 3.1.gemfile} +3 -1
  18. data/gemfiles/{rails3.gemfile → 3.2.gemfile} +3 -1
  19. data/images.rake +21 -0
  20. data/lib/generators/paperclip/paperclip_generator.rb +1 -2
  21. data/lib/paperclip.rb +48 -319
  22. data/lib/paperclip/attachment.rb +33 -81
  23. data/lib/paperclip/attachment_options.rb +0 -1
  24. data/lib/paperclip/callbacks.rb +30 -0
  25. data/lib/paperclip/errors.rb +27 -0
  26. data/lib/paperclip/geometry.rb +6 -4
  27. data/lib/paperclip/glue.rb +15 -0
  28. data/lib/paperclip/helpers.rb +71 -0
  29. data/lib/paperclip/instance_methods.rb +35 -0
  30. data/lib/paperclip/interpolations.rb +2 -2
  31. data/lib/paperclip/io_adapters/attachment_adapter.rb +62 -0
  32. data/lib/paperclip/io_adapters/file_adapter.rb +81 -0
  33. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  34. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  35. data/lib/paperclip/io_adapters/registry.rb +32 -0
  36. data/lib/paperclip/io_adapters/stringio_adapter.rb +64 -0
  37. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +63 -0
  38. data/lib/paperclip/locales/en.yml +17 -0
  39. data/lib/paperclip/logger.rb +21 -0
  40. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +1 -1
  41. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -2
  42. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +7 -7
  43. data/lib/paperclip/processor.rb +32 -17
  44. data/lib/paperclip/railtie.rb +10 -15
  45. data/lib/paperclip/storage/filesystem.rb +5 -14
  46. data/lib/paperclip/storage/fog.rb +2 -21
  47. data/lib/paperclip/storage/s3.rb +12 -29
  48. data/lib/paperclip/tempfile.rb +41 -0
  49. data/lib/paperclip/thumbnail.rb +2 -3
  50. data/lib/paperclip/validators.rb +45 -0
  51. data/lib/paperclip/validators/attachment_content_type_validator.rb +47 -0
  52. data/lib/paperclip/validators/attachment_presence_validator.rb +26 -0
  53. data/lib/paperclip/validators/attachment_size_validator.rb +102 -0
  54. data/lib/paperclip/version.rb +1 -1
  55. data/lib/tasks/paperclip.rake +3 -11
  56. data/paperclip.gemspec +15 -5
  57. data/test/adapter_registry_test.rb +32 -0
  58. data/test/attachment_adapter_test.rb +48 -0
  59. data/test/attachment_options_test.rb +0 -13
  60. data/test/attachment_test.rb +27 -55
  61. data/test/file_adapter_test.rb +43 -0
  62. data/test/generator_test.rb +78 -0
  63. data/test/geometry_test.rb +5 -5
  64. data/test/helper.rb +9 -11
  65. data/test/identity_adapter_test.rb +8 -0
  66. data/test/integration_test.rb +39 -94
  67. data/test/interpolations_test.rb +8 -1
  68. data/test/matchers/validate_attachment_size_matcher_test.rb +16 -2
  69. data/test/nil_adapter_test.rb +25 -0
  70. data/test/paperclip_test.rb +30 -189
  71. data/test/storage/filesystem_test.rb +0 -14
  72. data/test/storage/fog_test.rb +0 -14
  73. data/test/storage/s3_live_test.rb +22 -9
  74. data/test/storage/s3_test.rb +70 -34
  75. data/test/stringio_adapter_test.rb +42 -0
  76. data/test/style_test.rb +10 -16
  77. data/test/thumbnail_test.rb +16 -10
  78. data/test/uploaded_file_adapter_test.rb +98 -0
  79. data/test/validators/attachment_content_type_validator_test.rb +140 -0
  80. data/test/validators/attachment_presence_validator_test.rb +85 -0
  81. data/test/validators/attachment_size_validator_test.rb +207 -0
  82. data/test/validators_test.rb +25 -0
  83. metadata +152 -30
  84. data/gemfiles/rails3_2.gemfile +0 -9
  85. data/generators/paperclip/USAGE +0 -5
  86. data/generators/paperclip/paperclip_generator.rb +0 -27
  87. data/generators/paperclip/templates/paperclip_migration.rb.erb +0 -19
  88. data/init.rb +0 -4
  89. data/lib/paperclip/callback_compatibility.rb +0 -61
  90. data/lib/paperclip/iostream.rb +0 -45
  91. data/lib/paperclip/upfile.rb +0 -64
  92. data/rails/init.rb +0 -2
  93. data/test/iostream_test.rb +0 -71
  94. data/test/upfile_test.rb +0 -53
@@ -2,34 +2,29 @@ require 'paperclip'
2
2
  require 'paperclip/schema'
3
3
 
4
4
  module Paperclip
5
- if defined? Rails::Railtie
6
- require 'rails'
7
- class Railtie < Rails::Railtie
8
- initializer 'paperclip.insert_into_active_record' do
9
- ActiveSupport.on_load :active_record do
10
- Paperclip::Railtie.insert
11
- end
12
- end
13
- rake_tasks do
14
- load "tasks/paperclip.rake"
5
+ require 'rails'
6
+ class Railtie < Rails::Railtie
7
+ initializer 'paperclip.insert_into_active_record' do
8
+ ActiveSupport.on_load :active_record do
9
+ Paperclip::Railtie.insert
15
10
  end
16
11
  end
12
+ rake_tasks do
13
+ load "tasks/paperclip.rake"
14
+ end
17
15
  end
18
16
 
19
17
  class Railtie
20
18
  def self.insert
21
19
  Paperclip.options[:logger] = Rails.logger if defined?(Rails)
22
-
20
+
23
21
  if defined?(ActiveRecord)
24
- ActiveRecord::Base.send(:include, Paperclip::Glue)
25
22
  Paperclip.options[:logger] = ActiveRecord::Base.logger
26
-
23
+ ActiveRecord::Base.send(:include, Paperclip::Glue)
27
24
  ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, Paperclip::Schema)
28
25
  ActiveRecord::ConnectionAdapters::Table.send(:include, Paperclip::Schema)
29
26
  ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, Paperclip::Schema)
30
27
  end
31
-
32
- File.send(:include, Paperclip::Upfile)
33
28
  end
34
29
  end
35
30
  end
@@ -27,23 +27,14 @@ module Paperclip
27
27
  end
28
28
  end
29
29
 
30
- # Returns representation of the data of the file assigned to the given
31
- # style, in the format most representative of the current storage.
32
- def to_file style_name = default_style
33
- if @queued_for_write[style_name]
34
- @queued_for_write[style_name].rewind
35
- @queued_for_write[style_name]
36
- elsif exists?(style_name)
37
- File.new(path(style_name), 'rb')
38
- end
39
- end
40
-
41
30
  def flush_writes #:nodoc:
42
31
  @queued_for_write.each do |style_name, file|
43
- file.close
44
32
  FileUtils.mkdir_p(File.dirname(path(style_name)))
45
- log("saving #{path(style_name)}")
46
- FileUtils.cp(file.path, path(style_name))
33
+ File.open(path(style_name), "wb") do |new_file|
34
+ while chunk = file.read(16 * 1024)
35
+ new_file.write(chunk)
36
+ end
37
+ end
47
38
  FileUtils.chmod(0666&~File.umask, path(style_name))
48
39
  end
49
40
 
@@ -83,7 +83,7 @@ module Paperclip
83
83
  :body => file,
84
84
  :key => path(style),
85
85
  :public => fog_public,
86
- :content_type => file.content_type.to_s.strip
86
+ :content_type => file.content_type
87
87
  ))
88
88
  rescue Excon::Errors::NotFound
89
89
  raise if retried
@@ -106,25 +106,6 @@ module Paperclip
106
106
  @queued_for_delete = []
107
107
  end
108
108
 
109
- # Returns representation of the data of the file assigned to the given
110
- # style, in the format most representative of the current storage.
111
- def to_file(style = default_style)
112
- if @queued_for_write[style]
113
- @queued_for_write[style].rewind
114
- @queued_for_write[style]
115
- else
116
- body = directory.files.get(path(style)).body
117
- filename = path(style)
118
- extname = File.extname(filename)
119
- basename = File.basename(filename, extname)
120
- file = Tempfile.new([basename, extname])
121
- file.binmode
122
- file.write(body)
123
- file.rewind
124
- file
125
- end
126
- end
127
-
128
109
  def public_url(style = default_style)
129
110
  if @options[:fog_host]
130
111
  host = if @options[:fog_host].respond_to?(:call)
@@ -132,7 +113,7 @@ module Paperclip
132
113
  else
133
114
  (@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
134
115
  end
135
-
116
+
136
117
  "#{host}/#{path(style)}"
137
118
  else
138
119
  if fog_credentials[:provider] == 'AWS'
@@ -95,7 +95,7 @@ module Paperclip
95
95
  @s3_permissions = set_permissions(@options[:s3_permissions])
96
96
  @s3_protocol = @options[:s3_protocol] ||
97
97
  Proc.new do |style, attachment|
98
- permission = (@s3_permissions[style.to_sym] || @s3_permissions[:default])
98
+ permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
99
99
  permission = permission.call(attachment, style) if permission.is_a?(Proc)
100
100
  (permission == :public_read) ? 'http' : 'https'
101
101
  end
@@ -141,7 +141,8 @@ module Paperclip
141
141
 
142
142
  def expiring_url(time = 3600, style_name = default_style)
143
143
  if path
144
- s3_object(style_name).url_for(:read, :expires => time, :secure => use_secure_protocol?(style_name)).to_s
144
+ base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
145
+ s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
145
146
  end
146
147
  end
147
148
 
@@ -159,6 +160,12 @@ module Paperclip
159
160
  @s3_host_alias
160
161
  end
161
162
 
163
+ def s3_url_options
164
+ s3_url_options = @options[:s3_url_options] || {}
165
+ s3_url_options = s3_url_options.call(instance) if s3_url_options.is_a?(Proc)
166
+ s3_url_options
167
+ end
168
+
162
169
  def bucket_name
163
170
  @bucket = @options[:bucket] || s3_credentials[:bucket]
164
171
  @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
@@ -227,6 +234,7 @@ module Paperclip
227
234
  end
228
235
 
229
236
  def parse_credentials creds
237
+ creds = creds.respond_to?('call') ? creds.call(self) : creds
230
238
  creds = find_credentials(creds).stringify_keys
231
239
  env = Object.const_defined?(:Rails) ? Rails.env : nil
232
240
  (creds[env] || creds).symbolize_keys
@@ -256,23 +264,6 @@ module Paperclip
256
264
  end
257
265
  end
258
266
 
259
- # Returns representation of the data of the file assigned to the given
260
- # style, in the format most representative of the current storage.
261
- def to_file style = default_style
262
- if @queued_for_write[style]
263
- @queued_for_write[style].rewind
264
- return @queued_for_write[style]
265
- end
266
- filename = path(style)
267
- extname = File.extname(filename)
268
- basename = File.basename(filename, extname)
269
- file = Tempfile.new([basename, extname])
270
- file.binmode
271
- file.write(s3_object(style).read)
272
- file.rewind
273
- return file
274
- end
275
-
276
267
  def create_bucket
277
268
  s3_interface.buckets.create(bucket_name)
278
269
  end
@@ -284,7 +275,7 @@ module Paperclip
284
275
  acl = @s3_permissions[style] || @s3_permissions[:default]
285
276
  acl = acl.call(self, style) if acl.respond_to?(:call)
286
277
  write_options = {
287
- :content_type => file.content_type.to_s.strip,
278
+ :content_type => file.content_type,
288
279
  :acl => acl
289
280
  }
290
281
  write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
@@ -325,19 +316,11 @@ module Paperclip
325
316
  when Hash
326
317
  creds
327
318
  else
328
- raise ArgumentError, "Credentials are not a path, file, or hash."
319
+ raise ArgumentError, "Credentials are not a path, file, proc, or hash."
329
320
  end
330
321
  end
331
322
  private :find_credentials
332
323
 
333
- def establish_connection!
334
- @connection ||= AWS::S3::Base.establish_connection!( @s3_options.merge(
335
- :access_key_id => s3_credentials[:access_key_id],
336
- :secret_access_key => s3_credentials[:secret_access_key]
337
- ))
338
- end
339
- private :establish_connection!
340
-
341
324
  def use_secure_protocol?(style_name)
342
325
  s3_protocol(style_name) == "https"
343
326
  end
@@ -0,0 +1,41 @@
1
+ module Paperclip
2
+ # Overriding some implementation of Tempfile
3
+ class Tempfile < ::Tempfile
4
+ # Due to how ImageMagick handles its image format conversion and how
5
+ # Tempfile handles its naming scheme, it is necessary to override how
6
+ # Tempfile makes # its names so as to allow for file extensions. Idea
7
+ # taken from the comments on this blog post:
8
+ # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
9
+ #
10
+ # This is Ruby 1.8.7's implementation.
11
+ def make_tmpname(basename, n)
12
+ if RUBY_PLATFORM =~ /java/
13
+ case basename
14
+ when Array
15
+ prefix, suffix = *basename
16
+ else
17
+ prefix, suffix = basename, ''
18
+ end
19
+
20
+ t = Time.now.strftime("%y%m%d")
21
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
22
+ else
23
+ super
24
+ end
25
+ end
26
+ end
27
+
28
+ module TempfileEncoding
29
+ # This overrides Tempfile#binmode to make sure that the extenal encoding
30
+ # for binary mode is ASCII-8BIT. This behavior is what's in CRuby, but not
31
+ # in JRuby
32
+ def binmode
33
+ set_encoding('ASCII-8BIT')
34
+ super
35
+ end
36
+ end
37
+ end
38
+
39
+ if RUBY_PLATFORM =~ /java/
40
+ ::Tempfile.send :include, Paperclip::TempfileEncoding
41
+ end
@@ -44,7 +44,6 @@ module Paperclip
44
44
 
45
45
  @current_format = File.extname(@file.path)
46
46
  @basename = File.basename(@file.path, @current_format)
47
-
48
47
  end
49
48
 
50
49
  # Returns true if the +target_geometry+ is meant to crop.
@@ -76,9 +75,9 @@ module Paperclip
76
75
 
77
76
  success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path))
78
77
  rescue Cocaine::ExitStatusError => e
79
- raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
78
+ raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
80
79
  rescue Cocaine::CommandNotFoundError => e
81
- raise Paperclip::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
80
+ raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
82
81
  end
83
82
 
84
83
  dst
@@ -0,0 +1,45 @@
1
+ require 'active_support/concern'
2
+ require 'paperclip/validators/attachment_content_type_validator'
3
+ require 'paperclip/validators/attachment_presence_validator'
4
+ require 'paperclip/validators/attachment_size_validator'
5
+
6
+ module Paperclip
7
+ module Validators
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ extend HelperMethods
12
+ include HelperMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ # This method is a shortcut to validator classes that is in
17
+ # "Attachment...Validator" format. It is almost the same thing as the
18
+ # +validates+ method that shipped with Rails, but this is customized to
19
+ # be using with attachment validators. This is helpful when you're using
20
+ # multiple attachment validators on a single attachment.
21
+ #
22
+ # Example of using the validator:
23
+ #
24
+ # validates_attachment :avatar, :presence => true,
25
+ # :content_type => { :content_type => "image/jpg" },
26
+ # :size => { :in => 0..10.kilobytes }
27
+ #
28
+ def validates_attachment(*attributes)
29
+ options = attributes.extract_options!.dup
30
+
31
+ Paperclip::Validators.constants.each do |constant|
32
+ if constant.to_s =~ /^Attachment(.+)Validator$/
33
+ validator_kind = $1.underscore.to_sym
34
+
35
+ if options.has_key?(validator_kind)
36
+ options[:"attachment_#{validator_kind}"] = options.delete(validator_kind)
37
+ end
38
+ end
39
+ end
40
+
41
+ validates(*attributes + [options])
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ module Paperclip
2
+ module Validators
3
+ class AttachmentContentTypeValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ attribute = "#{attribute}_content_type".to_sym
6
+ value = record.send(:read_attribute_for_validation, attribute)
7
+ allowed_types = [options[:content_type]].flatten
8
+
9
+ if value.present? && allowed_types.none? { |type| type === value }
10
+ record.errors.add(attribute, :invalid, options.merge(
11
+ :types => allowed_types.join(', ')
12
+ ))
13
+ end
14
+ end
15
+
16
+ def check_validity!
17
+ unless options.has_key?(:content_type)
18
+ raise ArgumentError, "You must pass in :content_type to the validator"
19
+ end
20
+ end
21
+ end
22
+
23
+ module HelperMethods
24
+ # Places ActiveRecord-style validations on the content type of the file
25
+ # assigned. The possible options are:
26
+ # * +content_type+: Allowed content types. Can be a single content type
27
+ # or an array. Each type can be a String or a Regexp. It should be
28
+ # noted that Internet Explorer uploads files with content_types that you
29
+ # may not expect. For example, JPEG images are given image/pjpeg and
30
+ # PNGs are image/x-png, so keep that in mind when determining how you
31
+ # match. Allows all by default.
32
+ # * +message+: The message to display when the uploaded file has an invalid
33
+ # content type.
34
+ # * +if+: A lambda or name of an instance method. Validation will only
35
+ # be run is this lambda or method returns true.
36
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
37
+ # NOTE: If you do not specify an [attachment]_content_type field on your
38
+ # model, content_type validation will work _ONLY upon assignment_ and
39
+ # re-validation after the instance has been reloaded will always succeed.
40
+ # You'll still need to have a virtual attribute (created by +attr_accessor+)
41
+ # name +[attachment]_content_type+ to be able to use this validator.
42
+ def validates_attachment_content_type(*attr_names)
43
+ validates_with AttachmentContentTypeValidator, _merge_attributes(attr_names)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ require 'active_model/validations/presence'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class AttachmentPresenceValidator < ActiveModel::Validations::PresenceValidator
6
+ def validate(record)
7
+ [attributes].flatten.map do |attribute|
8
+ if record.send(:read_attribute_for_validation, "#{attribute}_file_name").blank?
9
+ record.errors.add(attribute, :blank, options)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ module HelperMethods
16
+ # Places ActiveRecord-style validations on the presence of a file.
17
+ # Options:
18
+ # * +if+: A lambda or name of an instance method. Validation will only
19
+ # be run if this lambda or method returns true.
20
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
21
+ def validates_attachment_presence(*attr_names)
22
+ validates_with AttachmentPresenceValidator, _merge_attributes(attr_names)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,102 @@
1
+ require 'active_model/validations/numericality'
2
+
3
+ module Paperclip
4
+ module Validators
5
+ class AttachmentSizeValidator < ActiveModel::Validations::NumericalityValidator
6
+ AVAILABLE_CHECKS = [:less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to]
7
+
8
+ def initialize(options)
9
+ extract_options(options)
10
+ super
11
+ end
12
+
13
+ def validate_each(record, attr_name, value)
14
+ attr_name = "#{attr_name}_file_size".to_sym
15
+ value = record.send(:read_attribute_for_validation, attr_name)
16
+
17
+ unless value.blank?
18
+ options.slice(*AVAILABLE_CHECKS).each do |option, option_value|
19
+ option_value = option_value.call(record) if option_value.is_a?(Proc)
20
+ option_value = extract_option_value(option, option_value)
21
+
22
+ unless value.send(CHECKS[option], option_value)
23
+ error_message_key = options[:in] ? :in_between : option
24
+ record.errors.add(attr_name, error_message_key, filtered_options(value).merge(
25
+ :min => min_value_in_human_size(record),
26
+ :max => max_value_in_human_size(record),
27
+ :count => human_size(option_value)
28
+ ))
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def check_validity!
35
+ unless (AVAILABLE_CHECKS + [:in]).any? { |argument| options.has_key?(argument) }
36
+ raise ArgumentError, "You must pass either :less_than, :greater_than, or :in to the validator"
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def extract_options(options)
43
+ if range = options[:in]
44
+ if !options[:in].respond_to?(:call)
45
+ options[:less_than_or_equal_to] = range.max
46
+ options[:greater_than_or_equal_to] = range.min
47
+ else
48
+ options[:less_than_or_equal_to] = range
49
+ options[:greater_than_or_equal_to] = range
50
+ end
51
+ end
52
+ end
53
+
54
+ def extract_option_value(option, option_value)
55
+ if option_value.is_a?(Range)
56
+ if [:less_than, :less_than_or_equal_to].include?(option)
57
+ option_value.max
58
+ else
59
+ option_value.min
60
+ end
61
+ else
62
+ option_value
63
+ end
64
+ end
65
+
66
+ def human_size(size)
67
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
68
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
69
+ storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
70
+ end
71
+
72
+ def min_value_in_human_size(record)
73
+ value = options[:greater_than_or_equal_to] || options[:greater_than]
74
+ value = value.call(record) if value.respond_to?(:call)
75
+ value = value.min if value.respond_to?(:min)
76
+ human_size(value)
77
+ end
78
+
79
+ def max_value_in_human_size(record)
80
+ value = options[:less_than_or_equal_to] || options[:less_than]
81
+ value = value.call(record) if value.respond_to?(:call)
82
+ value = value.max if value.respond_to?(:max)
83
+ human_size(value)
84
+ end
85
+ end
86
+
87
+ module HelperMethods
88
+ # Places ActiveRecord-style validations on the size of the file assigned. The
89
+ # possible options are:
90
+ # * +in+: a Range of bytes (i.e. +1..1.megabyte+),
91
+ # * +less_than+: equivalent to :in => 0..options[:less_than]
92
+ # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
93
+ # * +message+: error message to display, use :min and :max as replacements
94
+ # * +if+: A lambda or name of an instance method. Validation will only
95
+ # be run if this lambda or method returns true.
96
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
97
+ def validates_attachment_size(*attr_names)
98
+ validates_with AttachmentSizeValidator, _merge_attributes(attr_names)
99
+ end
100
+ end
101
+ end
102
+ end