kt-paperclip 6.2.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 (191) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.gitignore +19 -0
  5. data/.hound.yml +1050 -0
  6. data/.rubocop.yml +1 -0
  7. data/.travis.yml +47 -0
  8. data/Appraisals +24 -0
  9. data/CONTRIBUTING.md +86 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE +24 -0
  12. data/NEWS +515 -0
  13. data/README.md +1053 -0
  14. data/RELEASING.md +17 -0
  15. data/Rakefile +52 -0
  16. data/UPGRADING +17 -0
  17. data/features/basic_integration.feature +85 -0
  18. data/features/migration.feature +29 -0
  19. data/features/rake_tasks.feature +62 -0
  20. data/features/step_definitions/attachment_steps.rb +110 -0
  21. data/features/step_definitions/html_steps.rb +15 -0
  22. data/features/step_definitions/rails_steps.rb +257 -0
  23. data/features/step_definitions/s3_steps.rb +14 -0
  24. data/features/step_definitions/web_steps.rb +106 -0
  25. data/features/support/env.rb +12 -0
  26. data/features/support/fakeweb.rb +11 -0
  27. data/features/support/file_helpers.rb +34 -0
  28. data/features/support/fixtures/boot_config.txt +15 -0
  29. data/features/support/fixtures/gemfile.txt +5 -0
  30. data/features/support/fixtures/preinitializer.txt +20 -0
  31. data/features/support/paths.rb +28 -0
  32. data/features/support/rails.rb +39 -0
  33. data/features/support/selectors.rb +19 -0
  34. data/gemfiles/4.2.gemfile +20 -0
  35. data/gemfiles/5.0.gemfile +20 -0
  36. data/gemfiles/5.1.gemfile +20 -0
  37. data/gemfiles/5.2.gemfile +20 -0
  38. data/gemfiles/6.0.gemfile +20 -0
  39. data/lib/generators/paperclip/USAGE +8 -0
  40. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  41. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  42. data/lib/paperclip.rb +215 -0
  43. data/lib/paperclip/attachment.rb +617 -0
  44. data/lib/paperclip/attachment_registry.rb +60 -0
  45. data/lib/paperclip/callbacks.rb +42 -0
  46. data/lib/paperclip/content_type_detector.rb +80 -0
  47. data/lib/paperclip/errors.rb +34 -0
  48. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  49. data/lib/paperclip/filename_cleaner.rb +15 -0
  50. data/lib/paperclip/geometry.rb +157 -0
  51. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  52. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  53. data/lib/paperclip/glue.rb +17 -0
  54. data/lib/paperclip/has_attached_file.rb +116 -0
  55. data/lib/paperclip/helpers.rb +60 -0
  56. data/lib/paperclip/interpolations.rb +201 -0
  57. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  58. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  59. data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
  60. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  62. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  63. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  64. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  65. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  66. data/lib/paperclip/io_adapters/registry.rb +36 -0
  67. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  68. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  69. data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
  70. data/lib/paperclip/locales/en.yml +18 -0
  71. data/lib/paperclip/logger.rb +21 -0
  72. data/lib/paperclip/matchers.rb +64 -0
  73. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  74. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  75. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  76. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  77. data/lib/paperclip/media_type_spoof_detector.rb +90 -0
  78. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  79. data/lib/paperclip/processor.rb +56 -0
  80. data/lib/paperclip/processor_helpers.rb +52 -0
  81. data/lib/paperclip/rails_environment.rb +21 -0
  82. data/lib/paperclip/railtie.rb +31 -0
  83. data/lib/paperclip/schema.rb +81 -0
  84. data/lib/paperclip/storage.rb +3 -0
  85. data/lib/paperclip/storage/filesystem.rb +99 -0
  86. data/lib/paperclip/storage/fog.rb +252 -0
  87. data/lib/paperclip/storage/s3.rb +461 -0
  88. data/lib/paperclip/style.rb +106 -0
  89. data/lib/paperclip/tempfile.rb +42 -0
  90. data/lib/paperclip/tempfile_factory.rb +22 -0
  91. data/lib/paperclip/thumbnail.rb +131 -0
  92. data/lib/paperclip/url_generator.rb +76 -0
  93. data/lib/paperclip/validators.rb +73 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +140 -0
  102. data/paperclip.gemspec +50 -0
  103. data/shoulda_macros/paperclip.rb +134 -0
  104. data/spec/database.yml +4 -0
  105. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  106. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1590 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +47 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +38 -0
  113. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  114. data/spec/paperclip/geometry_spec.rb +255 -0
  115. data/spec/paperclip/glue_spec.rb +42 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  117. data/spec/paperclip/integration_spec.rb +702 -0
  118. data/spec/paperclip/interpolations_spec.rb +270 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  122. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  123. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  124. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +137 -0
  125. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  126. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  127. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  128. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  129. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  130. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +221 -0
  131. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  132. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  133. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  134. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  135. data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  138. data/spec/paperclip/paperclip_spec.rb +196 -0
  139. data/spec/paperclip/plural_cache_spec.rb +37 -0
  140. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  141. data/spec/paperclip/processor_spec.rb +26 -0
  142. data/spec/paperclip/rails_environment_spec.rb +30 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +252 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +560 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1695 -0
  149. data/spec/paperclip/style_spec.rb +251 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/tempfile_spec.rb +35 -0
  152. data/spec/paperclip/thumbnail_spec.rb +504 -0
  153. data/spec/paperclip/url_generator_spec.rb +221 -0
  154. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  155. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
  156. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  157. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  158. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  159. data/spec/paperclip/validators_spec.rb +164 -0
  160. data/spec/spec_helper.rb +45 -0
  161. data/spec/support/assertions.rb +84 -0
  162. data/spec/support/fake_model.rb +24 -0
  163. data/spec/support/fake_rails.rb +12 -0
  164. data/spec/support/fixtures/12k.png +0 -0
  165. data/spec/support/fixtures/50x50.png +0 -0
  166. data/spec/support/fixtures/5k.png +0 -0
  167. data/spec/support/fixtures/animated +0 -0
  168. data/spec/support/fixtures/animated.gif +0 -0
  169. data/spec/support/fixtures/animated.unknown +0 -0
  170. data/spec/support/fixtures/bad.png +1 -0
  171. data/spec/support/fixtures/empty.html +1 -0
  172. data/spec/support/fixtures/empty.xlsx +0 -0
  173. data/spec/support/fixtures/fog.yml +8 -0
  174. data/spec/support/fixtures/rotated.jpg +0 -0
  175. data/spec/support/fixtures/s3.yml +8 -0
  176. data/spec/support/fixtures/spaced file.jpg +0 -0
  177. data/spec/support/fixtures/spaced file.png +0 -0
  178. data/spec/support/fixtures/text.txt +1 -0
  179. data/spec/support/fixtures/twopage.pdf +0 -0
  180. data/spec/support/fixtures/uppercase.PNG +0 -0
  181. data/spec/support/matchers/accept.rb +5 -0
  182. data/spec/support/matchers/exist.rb +5 -0
  183. data/spec/support/matchers/have_column.rb +23 -0
  184. data/spec/support/mock_attachment.rb +24 -0
  185. data/spec/support/mock_interpolator.rb +24 -0
  186. data/spec/support/mock_url_generator_builder.rb +26 -0
  187. data/spec/support/model_reconstruction.rb +72 -0
  188. data/spec/support/reporting.rb +11 -0
  189. data/spec/support/test_data.rb +13 -0
  190. data/spec/support/version_helper.rb +9 -0
  191. metadata +586 -0
@@ -0,0 +1,31 @@
1
+ module Paperclip
2
+ class GeometryParser
3
+ FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?(\@\>|\>\@|[\>\<\#\@\%^!])?/i.freeze
4
+ def initialize(string)
5
+ @string = string
6
+ end
7
+
8
+ def make
9
+ if match
10
+ Geometry.new(
11
+ height: @height,
12
+ width: @width,
13
+ modifier: @modifier,
14
+ orientation: @orientation
15
+ )
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def match
22
+ if actual_match = @string && @string.match(FORMAT)
23
+ @width = actual_match[1]
24
+ @height = actual_match[2]
25
+ @orientation = actual_match[3]
26
+ @modifier = actual_match[4]
27
+ end
28
+ actual_match
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require "paperclip/callbacks"
2
+ require "paperclip/validators"
3
+ require "paperclip/schema"
4
+
5
+ module Paperclip
6
+ module Glue
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.send :include, Callbacks
10
+ base.send :include, Validators
11
+ base.send :include, Schema if defined? ActiveRecord::Base
12
+
13
+ locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}")
14
+ I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,116 @@
1
+ module Paperclip
2
+ class HasAttachedFile
3
+ def self.define_on(klass, name, options)
4
+ new(klass, name, options).define
5
+ end
6
+
7
+ def initialize(klass, name, options)
8
+ @klass = klass
9
+ @name = name
10
+ @options = options
11
+ end
12
+
13
+ def define
14
+ define_flush_errors
15
+ define_getters
16
+ define_setter
17
+ define_query
18
+ register_new_attachment
19
+ add_active_record_callbacks
20
+ add_paperclip_callbacks
21
+ add_required_validations
22
+ end
23
+
24
+ private
25
+
26
+ def define_flush_errors
27
+ @klass.send(:validates_each, @name) do |record, _attr, _value|
28
+ attachment = record.send(@name)
29
+ attachment.send(:flush_errors)
30
+ end
31
+ end
32
+
33
+ def define_getters
34
+ define_instance_getter
35
+ define_class_getter
36
+ end
37
+
38
+ def define_instance_getter
39
+ name = @name
40
+ options = @options
41
+
42
+ @klass.send :define_method, @name do |*args|
43
+ ivar = "@attachment_#{name}"
44
+ attachment = instance_variable_get(ivar)
45
+
46
+ if attachment.nil?
47
+ attachment = Attachment.new(name, self, options)
48
+ instance_variable_set(ivar, attachment)
49
+ end
50
+
51
+ if !args.empty?
52
+ attachment.to_s(args.first)
53
+ else
54
+ attachment
55
+ end
56
+ end
57
+ end
58
+
59
+ def define_class_getter
60
+ @klass.extend(ClassMethods)
61
+ end
62
+
63
+ def define_setter
64
+ name = @name
65
+ @klass.send :define_method, "#{@name}=" do |file|
66
+ send(name).assign(file)
67
+ end
68
+ end
69
+
70
+ def define_query
71
+ name = @name
72
+ @klass.send :define_method, "#{@name}?" do
73
+ send(name).file?
74
+ end
75
+ end
76
+
77
+ def register_new_attachment
78
+ Paperclip::AttachmentRegistry.register(@klass, @name, @options)
79
+ end
80
+
81
+ def add_required_validations
82
+ options = Paperclip::Attachment.default_options.deep_merge(@options)
83
+ if options[:validate_media_type] != false
84
+ name = @name
85
+ @klass.validates_media_type_spoof_detection name,
86
+ if: ->(instance) { instance.send(name).dirty? }
87
+ end
88
+ end
89
+
90
+ def add_active_record_callbacks
91
+ name = @name
92
+ @klass.send(:after_save) { send(name).send(:save) }
93
+ @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }
94
+ if @klass.respond_to?(:after_commit)
95
+ @klass.send(:after_commit, on: :destroy) do
96
+ send(name).send(:flush_deletes)
97
+ end
98
+ else
99
+ @klass.send(:after_destroy) { send(name).send(:flush_deletes) }
100
+ end
101
+ end
102
+
103
+ def add_paperclip_callbacks
104
+ @klass.send(
105
+ :define_paperclip_callbacks,
106
+ :post_process, :"#{@name}_post_process", :"#{@name}_validate"
107
+ )
108
+ end
109
+
110
+ module ClassMethods
111
+ def attachment_definitions
112
+ Paperclip::AttachmentRegistry.definitions_for(self)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,60 @@
1
+ module Paperclip
2
+ module Helpers
3
+ def configure
4
+ yield(self) if block_given?
5
+ end
6
+
7
+ def interpolates(key, &block)
8
+ Paperclip::Interpolations[key] = block
9
+ end
10
+
11
+ # The run method takes the name of a binary to run, the arguments
12
+ # to that binary, the values to interpolate and some local options.
13
+ #
14
+ # :cmd -> The name of a binary to run.
15
+ #
16
+ # :arguments -> The command line arguments to that binary.
17
+ #
18
+ # :interpolation_values -> Values to be interpolated into the arguments.
19
+ #
20
+ # :local_options -> The options to be used by Cocain::CommandLine.
21
+ # These could be: runner
22
+ # logger
23
+ # swallow_stderr
24
+ # expected_outcodes
25
+ # environment
26
+ # runner_options
27
+ #
28
+ def run(cmd, arguments = "", interpolation_values = {}, local_options = {})
29
+ command_path = options[:command_path]
30
+ terrapin_path_array = Terrapin::CommandLine.path.try(:split, Terrapin::OS.path_separator)
31
+ Terrapin::CommandLine.path = [terrapin_path_array, command_path].flatten.compact.uniq
32
+ if logging? && (options[:log_command] || local_options[:log_command])
33
+ local_options = local_options.merge(logger: logger)
34
+ end
35
+ Terrapin::CommandLine.new(cmd, arguments, local_options).run(interpolation_values)
36
+ end
37
+
38
+ # Find all instances of the given Active Record model +klass+ with attachment +name+.
39
+ # This method is used by the refresh rake tasks.
40
+ def each_instance_with_attachment(klass, name)
41
+ class_for(klass).unscoped.where("#{name}_file_name IS NOT NULL").find_each do |instance|
42
+ yield(instance)
43
+ end
44
+ end
45
+
46
+ def class_for(class_name)
47
+ class_name.split("::").inject(Object) do |klass, partial_class_name|
48
+ if klass.const_defined?(partial_class_name)
49
+ klass.const_get(partial_class_name, false)
50
+ else
51
+ klass.const_missing(partial_class_name)
52
+ end
53
+ end
54
+ end
55
+
56
+ def reset_duplicate_clash_check!
57
+ @names_url = nil
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,201 @@
1
+ module Paperclip
2
+ # This module contains all the methods that are available for interpolation
3
+ # in paths and urls. To add your own (or override an existing one), you
4
+ # can either open this module and define it, or call the
5
+ # Paperclip.interpolates method.
6
+ module Interpolations
7
+ extend self
8
+ ID_PARTITION_LIMIT = 1_000_000_000
9
+
10
+ # Hash assignment of interpolations. Included only for compatibility,
11
+ # and is not intended for normal use.
12
+ def self.[]=(name, block)
13
+ define_method(name, &block)
14
+ @interpolators_cache = nil
15
+ end
16
+
17
+ # Hash access of interpolations. Included only for compatibility,
18
+ # and is not intended for normal use.
19
+ def self.[](name)
20
+ method(name)
21
+ end
22
+
23
+ # Returns a sorted list of all interpolations.
24
+ def self.all
25
+ instance_methods(false).sort!
26
+ end
27
+
28
+ # Perform the actual interpolation. Takes the pattern to interpolate
29
+ # and the arguments to pass, which are the attachment and style name.
30
+ # You can pass a method name on your record as a symbol, which should turn
31
+ # an interpolation pattern for Paperclip to use.
32
+ def self.interpolate(pattern, *args)
33
+ pattern = args.first.instance.send(pattern) if pattern.is_a? Symbol
34
+ result = pattern.dup
35
+ interpolators_cache.each do |method, token|
36
+ result.gsub!(token) { send(method, *args) } if result.include?(token)
37
+ end
38
+ result
39
+ end
40
+
41
+ def self.interpolators_cache
42
+ @interpolators_cache ||= all.reverse!.map! { |method| [method, ":#{method}"] }
43
+ end
44
+
45
+ def self.plural_cache
46
+ @plural_cache ||= PluralCache.new
47
+ end
48
+
49
+ # Returns the filename, the same way as ":basename.:extension" would.
50
+ def filename(attachment, style_name)
51
+ [basename(attachment, style_name), extension(attachment, style_name)].delete_if(&:empty?).join(".")
52
+ end
53
+
54
+ # Returns the interpolated URL. Will raise an error if the url itself
55
+ # contains ":url" to prevent infinite recursion. This interpolation
56
+ # is used in the default :path to ease default specifications.
57
+ RIGHT_HERE = "#{__FILE__.gsub(%r{\A\./}, '')}:#{__LINE__ + 3}"
58
+ def url(attachment, style_name)
59
+ raise Errors::InfiniteInterpolationError if caller.any? { |b| b.index(RIGHT_HERE) }
60
+ attachment.url(style_name, timestamp: false, escape: false)
61
+ end
62
+
63
+ # Returns the timestamp as defined by the <attachment>_updated_at field
64
+ # in the server default time zone unless :use_global_time_zone is set
65
+ # to false. Note that a Rails.config.time_zone change will still
66
+ # invalidate any path or URL that uses :timestamp. For a
67
+ # time_zone-agnostic timestamp, use #updated_at.
68
+ def timestamp(attachment, _style_name)
69
+ attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s
70
+ end
71
+
72
+ # Returns an integer timestamp that is time zone-neutral, so that paths
73
+ # remain valid even if a server's time zone changes.
74
+ def updated_at(attachment, _style_name)
75
+ attachment.updated_at
76
+ end
77
+
78
+ # Returns the Rails.root constant.
79
+ def rails_root(_attachment, _style_name)
80
+ Rails.root
81
+ end
82
+
83
+ # Returns the Rails.env constant.
84
+ def rails_env(_attachment, _style_name)
85
+ Rails.env
86
+ end
87
+
88
+ # Returns the underscored, pluralized version of the class name.
89
+ # e.g. "users" for the User class.
90
+ # NOTE: The arguments need to be optional, because some tools fetch
91
+ # all class names. Calling #class will return the expected class.
92
+ def class(attachment = nil, style_name = nil)
93
+ return super() if attachment.nil? && style_name.nil?
94
+
95
+ plural_cache.underscore_and_pluralize_class(attachment.instance.class)
96
+ end
97
+
98
+ # Returns the basename of the file. e.g. "file" for "file.jpg"
99
+ def basename(attachment, _style_name)
100
+ File.basename(attachment.original_filename, ".*")
101
+ end
102
+
103
+ # Returns the extension of the file. e.g. "jpg" for "file.jpg"
104
+ # If the style has a format defined, it will return the format instead
105
+ # of the actual extension.
106
+ def extension(attachment, style_name)
107
+ ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||
108
+ File.extname(attachment.original_filename).sub(/\A\.+/, "")
109
+ end
110
+
111
+ # Returns the dot+extension of the file. e.g. ".jpg" for "file.jpg"
112
+ # If the style has a format defined, it will return the format instead
113
+ # of the actual extension. If the extension is empty, no dot is added.
114
+ def dotextension(attachment, style_name)
115
+ ext = extension(attachment, style_name)
116
+ ext.empty? ? ext : ".#{ext}"
117
+ end
118
+
119
+ # Returns an extension based on the content type. e.g. "jpeg" for
120
+ # "image/jpeg". If the style has a specified format, it will override the
121
+ # content-type detection.
122
+ #
123
+ # Each mime type generally has multiple extensions associated with it, so
124
+ # if the extension from the original filename is one of these extensions,
125
+ # that extension is used, otherwise, the first in the list is used.
126
+ def content_type_extension(attachment, style_name)
127
+ mime_type = MIME::Types[attachment.content_type]
128
+ extensions_for_mime_type = if mime_type.empty?
129
+ []
130
+ else
131
+ mime_type.first.extensions
132
+ end
133
+
134
+ original_extension = extension(attachment, style_name)
135
+ style = attachment.styles[style_name.to_s.to_sym]
136
+ if style && style[:format]
137
+ style[:format].to_s
138
+ elsif extensions_for_mime_type.include? original_extension
139
+ original_extension
140
+ elsif !extensions_for_mime_type.empty?
141
+ extensions_for_mime_type.first
142
+ else
143
+ # It's possible, though unlikely, that the mime type is not in the
144
+ # database, so just use the part after the '/' in the mime type as the
145
+ # extension.
146
+ %r{/([^/]*)\z}.match(attachment.content_type)[1]
147
+ end
148
+ end
149
+
150
+ # Returns the id of the instance.
151
+ def id(attachment, _style_name)
152
+ attachment.instance.id
153
+ end
154
+
155
+ # Returns the #to_param of the instance.
156
+ def param(attachment, _style_name)
157
+ attachment.instance.to_param
158
+ end
159
+
160
+ # Returns the fingerprint of the instance.
161
+ def fingerprint(attachment, _style_name)
162
+ attachment.fingerprint
163
+ end
164
+
165
+ # Returns a the attachment hash. See Paperclip::Attachment#hash_key for
166
+ # more details.
167
+ def hash(attachment = nil, style_name = nil)
168
+ if attachment && style_name
169
+ attachment.hash_key(style_name)
170
+ else
171
+ super()
172
+ end
173
+ end
174
+
175
+ # Returns the id of the instance in a split path form. e.g. returns
176
+ # 000/001/234 for an id of 1234.
177
+ def id_partition(attachment, _style_name)
178
+ case id = attachment.instance.id
179
+ when Integer
180
+ if id < ID_PARTITION_LIMIT
181
+ ("%09d" % id).scan(/\d{3}/).join("/")
182
+ else
183
+ ("%012d" % id).scan(/\d{3}/).join("/")
184
+ end
185
+ when String
186
+ id.scan(/.{3}/).first(3).join("/")
187
+ end
188
+ end
189
+
190
+ # Returns the pluralized form of the attachment name. e.g.
191
+ # "avatars" for an attachment of :avatar
192
+ def attachment(attachment, _style_name)
193
+ plural_cache.pluralize_symbol(attachment.name)
194
+ end
195
+
196
+ # Returns the style, or the default style if nil is supplied.
197
+ def style(attachment, style_name)
198
+ style_name || attachment.default_style
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,18 @@
1
+ module Paperclip
2
+ module Interpolations
3
+ class PluralCache
4
+ def initialize
5
+ @symbol_cache = {}.compare_by_identity
6
+ @klass_cache = {}.compare_by_identity
7
+ end
8
+
9
+ def pluralize_symbol(symbol)
10
+ @symbol_cache[symbol] ||= symbol.to_s.downcase.pluralize
11
+ end
12
+
13
+ def underscore_and_pluralize_class(klass)
14
+ @klass_cache[klass] ||= klass.name.underscore.pluralize
15
+ end
16
+ end
17
+ end
18
+ end