paperclip_jk 5.0.0.beta2

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 (190) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +26 -0
  6. data/Appraisals +27 -0
  7. data/CONTRIBUTING.md +86 -0
  8. data/Gemfile +15 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +424 -0
  11. data/README.md +999 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +17 -0
  15. data/features/basic_integration.feature +81 -0
  16. data/features/migration.feature +70 -0
  17. data/features/rake_tasks.feature +62 -0
  18. data/features/step_definitions/attachment_steps.rb +110 -0
  19. data/features/step_definitions/html_steps.rb +15 -0
  20. data/features/step_definitions/rails_steps.rb +230 -0
  21. data/features/step_definitions/s3_steps.rb +14 -0
  22. data/features/step_definitions/web_steps.rb +107 -0
  23. data/features/support/env.rb +11 -0
  24. data/features/support/fakeweb.rb +13 -0
  25. data/features/support/file_helpers.rb +34 -0
  26. data/features/support/fixtures/boot_config.txt +15 -0
  27. data/features/support/fixtures/gemfile.txt +5 -0
  28. data/features/support/fixtures/preinitializer.txt +20 -0
  29. data/features/support/paths.rb +28 -0
  30. data/features/support/rails.rb +63 -0
  31. data/features/support/selectors.rb +19 -0
  32. data/gemfiles/4.2.awsv2.0.gemfile +17 -0
  33. data/gemfiles/4.2.awsv2.1.gemfile +17 -0
  34. data/gemfiles/4.2.awsv2.gemfile +20 -0
  35. data/gemfiles/5.0.awsv2.0.gemfile +17 -0
  36. data/gemfiles/5.0.awsv2.1.gemfile +17 -0
  37. data/gemfiles/5.0.awsv2.gemfile +25 -0
  38. data/lib/generators/paperclip/USAGE +8 -0
  39. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  40. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  41. data/lib/paperclip.rb +211 -0
  42. data/lib/paperclip/attachment.rb +610 -0
  43. data/lib/paperclip/attachment_registry.rb +60 -0
  44. data/lib/paperclip/callbacks.rb +42 -0
  45. data/lib/paperclip/content_type_detector.rb +80 -0
  46. data/lib/paperclip/errors.rb +34 -0
  47. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  48. data/lib/paperclip/filename_cleaner.rb +16 -0
  49. data/lib/paperclip/geometry.rb +158 -0
  50. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  51. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  52. data/lib/paperclip/glue.rb +17 -0
  53. data/lib/paperclip/has_attached_file.rb +115 -0
  54. data/lib/paperclip/helpers.rb +60 -0
  55. data/lib/paperclip/interpolations.rb +197 -0
  56. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  57. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  58. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  59. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  60. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  61. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  62. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
  63. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  64. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  65. data/lib/paperclip/io_adapters/registry.rb +32 -0
  66. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  67. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  68. data/lib/paperclip/io_adapters/uri_adapter.rb +44 -0
  69. data/lib/paperclip/locales/en.yml +18 -0
  70. data/lib/paperclip/logger.rb +21 -0
  71. data/lib/paperclip/matchers.rb +64 -0
  72. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  73. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  74. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  75. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  76. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  77. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  78. data/lib/paperclip/processor.rb +48 -0
  79. data/lib/paperclip/processor_helpers.rb +50 -0
  80. data/lib/paperclip/rails_environment.rb +25 -0
  81. data/lib/paperclip/railtie.rb +31 -0
  82. data/lib/paperclip/schema.rb +77 -0
  83. data/lib/paperclip/storage.rb +4 -0
  84. data/lib/paperclip/storage/database.rb +140 -0
  85. data/lib/paperclip/storage/filesystem.rb +90 -0
  86. data/lib/paperclip/storage/fog.rb +244 -0
  87. data/lib/paperclip/storage/s3.rb +442 -0
  88. data/lib/paperclip/style.rb +109 -0
  89. data/lib/paperclip/tempfile.rb +43 -0
  90. data/lib/paperclip/tempfile_factory.rb +23 -0
  91. data/lib/paperclip/thumbnail.rb +121 -0
  92. data/lib/paperclip/url_generator.rb +72 -0
  93. data/lib/paperclip/validators.rb +74 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +127 -0
  102. data/paperclip.gemspec +54 -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 +80 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1517 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +39 -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 +44 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +158 -0
  117. data/spec/paperclip/integration_spec.rb +668 -0
  118. data/spec/paperclip/interpolations_spec.rb +262 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -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 +113 -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 +102 -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 +109 -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 +70 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  138. data/spec/paperclip/paperclip_spec.rb +192 -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 +33 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +248 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +545 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +186 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1583 -0
  149. data/spec/paperclip/style_spec.rb +255 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/thumbnail_spec.rb +500 -0
  152. data/spec/paperclip/url_generator_spec.rb +211 -0
  153. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  154. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  155. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  156. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  157. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  158. data/spec/paperclip/validators_spec.rb +164 -0
  159. data/spec/spec_helper.rb +45 -0
  160. data/spec/support/assertions.rb +78 -0
  161. data/spec/support/fake_model.rb +25 -0
  162. data/spec/support/fake_rails.rb +12 -0
  163. data/spec/support/fixtures/12k.png +0 -0
  164. data/spec/support/fixtures/50x50.png +0 -0
  165. data/spec/support/fixtures/5k.png +0 -0
  166. data/spec/support/fixtures/animated +0 -0
  167. data/spec/support/fixtures/animated.gif +0 -0
  168. data/spec/support/fixtures/animated.unknown +0 -0
  169. data/spec/support/fixtures/bad.png +1 -0
  170. data/spec/support/fixtures/empty.html +1 -0
  171. data/spec/support/fixtures/empty.xlsx +0 -0
  172. data/spec/support/fixtures/fog.yml +8 -0
  173. data/spec/support/fixtures/rotated.jpg +0 -0
  174. data/spec/support/fixtures/s3.yml +8 -0
  175. data/spec/support/fixtures/spaced file.jpg +0 -0
  176. data/spec/support/fixtures/spaced file.png +0 -0
  177. data/spec/support/fixtures/text.txt +1 -0
  178. data/spec/support/fixtures/twopage.pdf +0 -0
  179. data/spec/support/fixtures/uppercase.PNG +0 -0
  180. data/spec/support/matchers/accept.rb +5 -0
  181. data/spec/support/matchers/exist.rb +5 -0
  182. data/spec/support/matchers/have_column.rb +23 -0
  183. data/spec/support/mock_attachment.rb +22 -0
  184. data/spec/support/mock_interpolator.rb +24 -0
  185. data/spec/support/mock_url_generator_builder.rb +27 -0
  186. data/spec/support/model_reconstruction.rb +68 -0
  187. data/spec/support/reporting.rb +11 -0
  188. data/spec/support/test_data.rb +13 -0
  189. data/spec/support/version_helper.rb +9 -0
  190. metadata +713 -0
@@ -0,0 +1,31 @@
1
+ module Paperclip
2
+ class GeometryParser
3
+ FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?(\@\>|\>\@|[\>\<\#\@\%^!])?/i
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,115 @@
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.length > 0
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")
107
+ end
108
+
109
+ module ClassMethods
110
+ def attachment_definitions
111
+ Paperclip::AttachmentRegistry.definitions_for(self)
112
+ end
113
+ end
114
+ end
115
+ 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
+ cocaine_path_array = Cocaine::CommandLine.path.try(:split, Cocaine::OS.path_separator)
31
+ Cocaine::CommandLine.path = [cocaine_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
+ Cocaine::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,197 @@
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
+
9
+ # Hash assignment of interpolations. Included only for compatibility,
10
+ # and is not intended for normal use.
11
+ def self.[]= name, block
12
+ define_method(name, &block)
13
+ @interpolators_cache = nil
14
+ end
15
+
16
+ # Hash access of interpolations. Included only for compatibility,
17
+ # and is not intended for normal use.
18
+ def self.[] name
19
+ method(name)
20
+ end
21
+
22
+ # Returns a sorted list of all interpolations.
23
+ def self.all
24
+ self.instance_methods(false).sort!
25
+ end
26
+
27
+ # Perform the actual interpolation. Takes the pattern to interpolate
28
+ # and the arguments to pass, which are the attachment and style name.
29
+ # You can pass a method name on your record as a symbol, which should turn
30
+ # an interpolation pattern for Paperclip to use.
31
+ def self.interpolate pattern, *args
32
+ pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol
33
+ result = pattern.dup
34
+ interpolators_cache.each do |method, token|
35
+ result.gsub!(token) { send(method, *args) } if result.include?(token)
36
+ end
37
+ result
38
+ end
39
+
40
+ def self.interpolators_cache
41
+ @interpolators_cache ||= all.reverse!.map! { |method| [method, ":#{method}"] }
42
+ end
43
+
44
+ def self.plural_cache
45
+ @plural_cache ||= PluralCache.new
46
+ end
47
+
48
+ # Returns the filename, the same way as ":basename.:extension" would.
49
+ def filename attachment, style_name
50
+ [ basename(attachment, style_name), extension(attachment, style_name) ].delete_if(&:empty?).join(".".freeze)
51
+ end
52
+
53
+ # Returns the interpolated URL. Will raise an error if the url itself
54
+ # contains ":url" to prevent infinite recursion. This interpolation
55
+ # is used in the default :path to ease default specifications.
56
+ RIGHT_HERE = "#{__FILE__.gsub(%r{\A\./}, "")}:#{__LINE__ + 3}"
57
+ def url attachment, style_name
58
+ raise Errors::InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }
59
+ attachment.url(style_name, :timestamp => false, :escape => false)
60
+ end
61
+
62
+ # Returns the timestamp as defined by the <attachment>_updated_at field
63
+ # in the server default time zone unless :use_global_time_zone is set
64
+ # to false. Note that a Rails.config.time_zone change will still
65
+ # invalidate any path or URL that uses :timestamp. For a
66
+ # time_zone-agnostic timestamp, use #updated_at.
67
+ def timestamp attachment, style_name
68
+ attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s
69
+ end
70
+
71
+ # Returns an integer timestamp that is time zone-neutral, so that paths
72
+ # remain valid even if a server's time zone changes.
73
+ def updated_at attachment, style_name
74
+ attachment.updated_at
75
+ end
76
+
77
+ # Returns the Rails.root constant.
78
+ def rails_root attachment, style_name
79
+ Rails.root
80
+ end
81
+
82
+ # Returns the Rails.env constant.
83
+ def rails_env attachment, style_name
84
+ Rails.env
85
+ end
86
+
87
+ # Returns the underscored, pluralized version of the class name.
88
+ # e.g. "users" for the User class.
89
+ # NOTE: The arguments need to be optional, because some tools fetch
90
+ # all class names. Calling #class will return the expected class.
91
+ def class attachment = nil, style_name = nil
92
+ return super() if attachment.nil? && style_name.nil?
93
+ plural_cache.underscore_and_pluralize_class(attachment.instance.class)
94
+ end
95
+
96
+ # Returns the basename of the file. e.g. "file" for "file.jpg"
97
+ def basename attachment, style_name
98
+ File.basename(attachment.original_filename, ".*".freeze)
99
+ end
100
+
101
+ # Returns the extension of the file. e.g. "jpg" for "file.jpg"
102
+ # If the style has a format defined, it will return the format instead
103
+ # of the actual extension.
104
+ def extension attachment, style_name
105
+ ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||
106
+ File.extname(attachment.original_filename).sub(/\A\.+/, "".freeze)
107
+ end
108
+
109
+ # Returns the dot+extension of the file. e.g. ".jpg" for "file.jpg"
110
+ # If the style has a format defined, it will return the format instead
111
+ # of the actual extension. If the extension is empty, no dot is added.
112
+ def dotextension attachment, style_name
113
+ ext = extension(attachment, style_name)
114
+ ext.empty? ? ext : ".#{ext}"
115
+ end
116
+
117
+ # Returns an extension based on the content type. e.g. "jpeg" for
118
+ # "image/jpeg". If the style has a specified format, it will override the
119
+ # content-type detection.
120
+ #
121
+ # Each mime type generally has multiple extensions associated with it, so
122
+ # if the extension from the original filename is one of these extensions,
123
+ # that extension is used, otherwise, the first in the list is used.
124
+ def content_type_extension attachment, style_name
125
+ mime_type = MIME::Types[attachment.content_type]
126
+ extensions_for_mime_type = unless mime_type.empty?
127
+ mime_type.first.extensions
128
+ else
129
+ []
130
+ end
131
+
132
+ original_extension = extension(attachment, style_name)
133
+ style = attachment.styles[style_name.to_s.to_sym]
134
+ if style && style[:format]
135
+ style[:format].to_s
136
+ elsif extensions_for_mime_type.include? original_extension
137
+ original_extension
138
+ elsif !extensions_for_mime_type.empty?
139
+ extensions_for_mime_type.first
140
+ else
141
+ # It's possible, though unlikely, that the mime type is not in the
142
+ # database, so just use the part after the '/' in the mime type as the
143
+ # extension.
144
+ %r{/([^/]*)\Z}.match(attachment.content_type)[1]
145
+ end
146
+ end
147
+
148
+ # Returns the id of the instance.
149
+ def id attachment, style_name
150
+ attachment.instance.id
151
+ end
152
+
153
+ # Returns the #to_param of the instance.
154
+ def param attachment, style_name
155
+ attachment.instance.to_param
156
+ end
157
+
158
+ # Returns the fingerprint of the instance.
159
+ def fingerprint attachment, style_name
160
+ attachment.fingerprint
161
+ end
162
+
163
+ # Returns a the attachment hash. See Paperclip::Attachment#hash_key for
164
+ # more details.
165
+ def hash attachment=nil, style_name=nil
166
+ if attachment && style_name
167
+ attachment.hash_key(style_name)
168
+ else
169
+ super()
170
+ end
171
+ end
172
+
173
+ # Returns the id of the instance in a split path form. e.g. returns
174
+ # 000/001/234 for an id of 1234.
175
+ def id_partition attachment, style_name
176
+ case id = attachment.instance.id
177
+ when Integer
178
+ ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
179
+ when String
180
+ id.scan(/.{3}/).first(3).join("/".freeze)
181
+ else
182
+ nil
183
+ end
184
+ end
185
+
186
+ # Returns the pluralized form of the attachment name. e.g.
187
+ # "avatars" for an attachment of :avatar
188
+ def attachment attachment, style_name
189
+ plural_cache.pluralize_symbol(attachment.name)
190
+ end
191
+
192
+ # Returns the style, or the default style if nil is supplied.
193
+ def style attachment, style_name
194
+ style_name || attachment.default_style
195
+ end
196
+ end
197
+ 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
@@ -0,0 +1,47 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Paperclip
4
+ class AbstractAdapter
5
+ OS_RESTRICTED_CHARACTERS = %r{[/:]}
6
+
7
+ attr_reader :content_type, :original_filename, :size
8
+ delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
9
+ alias :length :size
10
+
11
+ def fingerprint
12
+ @fingerprint ||= Digest::MD5.file(path).to_s
13
+ end
14
+
15
+ def read(length = nil, buffer = nil)
16
+ @tempfile.read(length, buffer)
17
+ end
18
+
19
+ def inspect
20
+ "#{self.class}: #{self.original_filename}"
21
+ end
22
+
23
+ def original_filename=(new_filename)
24
+ return unless new_filename
25
+ @original_filename = new_filename.gsub(OS_RESTRICTED_CHARACTERS, "_")
26
+ end
27
+
28
+ def nil?
29
+ false
30
+ end
31
+
32
+ def assignment?
33
+ true
34
+ end
35
+
36
+ private
37
+
38
+ def destination
39
+ @destination ||= TempfileFactory.new.generate(@original_filename.to_s)
40
+ end
41
+
42
+ def copy_to_tempfile(src)
43
+ FileUtils.cp(src.path, destination.path)
44
+ destination
45
+ end
46
+ end
47
+ end