kt-paperclip 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +23 -0
  6. data/Appraisals +11 -0
  7. data/CONTRIBUTING.md +75 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +420 -0
  11. data/README.md +977 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +14 -0
  15. data/cucumber/paperclip_steps.rb +6 -0
  16. data/features/basic_integration.feature +80 -0
  17. data/features/migration.feature +94 -0
  18. data/features/rake_tasks.feature +62 -0
  19. data/features/step_definitions/attachment_steps.rb +110 -0
  20. data/features/step_definitions/html_steps.rb +15 -0
  21. data/features/step_definitions/rails_steps.rb +236 -0
  22. data/features/step_definitions/s3_steps.rb +14 -0
  23. data/features/step_definitions/web_steps.rb +107 -0
  24. data/features/support/env.rb +11 -0
  25. data/features/support/fakeweb.rb +13 -0
  26. data/features/support/file_helpers.rb +34 -0
  27. data/features/support/fixtures/boot_config.txt +15 -0
  28. data/features/support/fixtures/gemfile.txt +5 -0
  29. data/features/support/fixtures/preinitializer.txt +20 -0
  30. data/features/support/paths.rb +28 -0
  31. data/features/support/rails.rb +63 -0
  32. data/features/support/selectors.rb +19 -0
  33. data/gemfiles/3.2.gemfile +19 -0
  34. data/gemfiles/4.1.gemfile +19 -0
  35. data/gemfiles/4.2.gemfile +19 -0
  36. data/lib/generators/paperclip/USAGE +8 -0
  37. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  38. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  39. data/lib/kt-paperclip.rb +1 -0
  40. data/lib/paperclip/attachment.rb +608 -0
  41. data/lib/paperclip/attachment_registry.rb +59 -0
  42. data/lib/paperclip/callbacks.rb +40 -0
  43. data/lib/paperclip/content_type_detector.rb +79 -0
  44. data/lib/paperclip/deprecations.rb +42 -0
  45. data/lib/paperclip/errors.rb +32 -0
  46. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  47. data/lib/paperclip/filename_cleaner.rb +16 -0
  48. data/lib/paperclip/geometry.rb +158 -0
  49. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  50. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  51. data/lib/paperclip/glue.rb +17 -0
  52. data/lib/paperclip/has_attached_file.rb +109 -0
  53. data/lib/paperclip/helpers.rb +56 -0
  54. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  55. data/lib/paperclip/interpolations.rb +197 -0
  56. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  57. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  58. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  59. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  60. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  62. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  63. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  64. data/lib/paperclip/io_adapters/registry.rb +32 -0
  65. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  66. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  67. data/lib/paperclip/io_adapters/uri_adapter.rb +63 -0
  68. data/lib/paperclip/locales/de.yml +18 -0
  69. data/lib/paperclip/locales/en.yml +18 -0
  70. data/lib/paperclip/locales/es.yml +18 -0
  71. data/lib/paperclip/locales/ja.yml +18 -0
  72. data/lib/paperclip/locales/pt-BR.yml +18 -0
  73. data/lib/paperclip/locales/zh-CN.yml +18 -0
  74. data/lib/paperclip/locales/zh-HK.yml +18 -0
  75. data/lib/paperclip/locales/zh-TW.yml +18 -0
  76. data/lib/paperclip/logger.rb +21 -0
  77. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  78. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  79. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  80. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  81. data/lib/paperclip/matchers.rb +64 -0
  82. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  83. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  84. data/lib/paperclip/processor.rb +48 -0
  85. data/lib/paperclip/processor_helpers.rb +50 -0
  86. data/lib/paperclip/rails_environment.rb +25 -0
  87. data/lib/paperclip/railtie.rb +31 -0
  88. data/lib/paperclip/schema.rb +83 -0
  89. data/lib/paperclip/storage/filesystem.rb +90 -0
  90. data/lib/paperclip/storage/fog.rb +242 -0
  91. data/lib/paperclip/storage/s3.rb +440 -0
  92. data/lib/paperclip/storage.rb +3 -0
  93. data/lib/paperclip/style.rb +109 -0
  94. data/lib/paperclip/tempfile.rb +43 -0
  95. data/lib/paperclip/tempfile_factory.rb +23 -0
  96. data/lib/paperclip/thumbnail.rb +121 -0
  97. data/lib/paperclip/url_generator.rb +79 -0
  98. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  99. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  100. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  101. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  102. data/lib/paperclip/validators/attachment_size_validator.rb +115 -0
  103. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  104. data/lib/paperclip/validators.rb +74 -0
  105. data/lib/paperclip/version.rb +3 -0
  106. data/lib/paperclip.rb +213 -0
  107. data/lib/tasks/paperclip.rake +127 -0
  108. data/paperclip.gemspec +52 -0
  109. data/shoulda_macros/paperclip.rb +134 -0
  110. data/spec/database.yml +4 -0
  111. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  112. data/spec/paperclip/attachment_processing_spec.rb +82 -0
  113. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  114. data/spec/paperclip/attachment_spec.rb +1494 -0
  115. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  116. data/spec/paperclip/deprecations_spec.rb +65 -0
  117. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  118. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  119. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  120. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  121. data/spec/paperclip/geometry_spec.rb +255 -0
  122. data/spec/paperclip/glue_spec.rb +44 -0
  123. data/spec/paperclip/has_attached_file_spec.rb +142 -0
  124. data/spec/paperclip/integration_spec.rb +667 -0
  125. data/spec/paperclip/interpolations_spec.rb +262 -0
  126. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  127. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  128. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
  129. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  130. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  131. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +104 -0
  132. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  133. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  134. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  135. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  136. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  137. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +127 -0
  138. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  139. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  140. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  141. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  142. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  143. data/spec/paperclip/meta_class_spec.rb +30 -0
  144. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  145. data/spec/paperclip/paperclip_spec.rb +222 -0
  146. data/spec/paperclip/plural_cache_spec.rb +37 -0
  147. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  148. data/spec/paperclip/processor_spec.rb +26 -0
  149. data/spec/paperclip/rails_environment_spec.rb +33 -0
  150. data/spec/paperclip/rake_spec.rb +103 -0
  151. data/spec/paperclip/schema_spec.rb +248 -0
  152. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  153. data/spec/paperclip/storage/fog_spec.rb +540 -0
  154. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  155. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  156. data/spec/paperclip/style_spec.rb +255 -0
  157. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  158. data/spec/paperclip/thumbnail_spec.rb +500 -0
  159. data/spec/paperclip/url_generator_spec.rb +221 -0
  160. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  161. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  162. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  163. data/spec/paperclip/validators/attachment_size_validator_spec.rb +229 -0
  164. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  165. data/spec/paperclip/validators_spec.rb +164 -0
  166. data/spec/spec_helper.rb +43 -0
  167. data/spec/support/assertions.rb +71 -0
  168. data/spec/support/deprecations.rb +9 -0
  169. data/spec/support/fake_model.rb +25 -0
  170. data/spec/support/fake_rails.rb +12 -0
  171. data/spec/support/fixtures/12k.png +0 -0
  172. data/spec/support/fixtures/50x50.png +0 -0
  173. data/spec/support/fixtures/5k.png +0 -0
  174. data/spec/support/fixtures/animated +0 -0
  175. data/spec/support/fixtures/animated.gif +0 -0
  176. data/spec/support/fixtures/animated.unknown +0 -0
  177. data/spec/support/fixtures/bad.png +1 -0
  178. data/spec/support/fixtures/empty.html +1 -0
  179. data/spec/support/fixtures/empty.xlsx +0 -0
  180. data/spec/support/fixtures/fog.yml +8 -0
  181. data/spec/support/fixtures/rotated.jpg +0 -0
  182. data/spec/support/fixtures/s3.yml +8 -0
  183. data/spec/support/fixtures/spaced file.jpg +0 -0
  184. data/spec/support/fixtures/spaced file.png +0 -0
  185. data/spec/support/fixtures/text.txt +1 -0
  186. data/spec/support/fixtures/twopage.pdf +0 -0
  187. data/spec/support/fixtures/uppercase.PNG +0 -0
  188. data/spec/support/matchers/accept.rb +5 -0
  189. data/spec/support/matchers/exist.rb +5 -0
  190. data/spec/support/matchers/have_column.rb +23 -0
  191. data/spec/support/mock_attachment.rb +22 -0
  192. data/spec/support/mock_interpolator.rb +24 -0
  193. data/spec/support/mock_url_generator_builder.rb +27 -0
  194. data/spec/support/model_reconstruction.rb +60 -0
  195. data/spec/support/rails_helpers.rb +7 -0
  196. data/spec/support/test_data.rb +13 -0
  197. data/spec/support/version_helper.rb +9 -0
  198. metadata +648 -0
data/lib/paperclip.rb ADDED
@@ -0,0 +1,213 @@
1
+ # Paperclip allows file attachments that are stored in the filesystem. All graphical
2
+ # transformations are done using the Graphics/ImageMagick command line utilities and
3
+ # are stored in Tempfiles until the record is saved. Paperclip does not require a
4
+ # separate model for storing the attachment's information, instead adding a few simple
5
+ # columns to your table.
6
+ #
7
+ # Author:: Jon Yurek
8
+ # Copyright:: Copyright (c) 2008-2011 thoughtbot, inc.
9
+ # License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
10
+ #
11
+ # Paperclip defines an attachment as any file, though it makes special considerations
12
+ # for image files. You can declare that a model has an attached file with the
13
+ # +has_attached_file+ method:
14
+ #
15
+ # class User < ActiveRecord::Base
16
+ # has_attached_file :avatar, :styles => { :thumb => "100x100" }
17
+ # end
18
+ #
19
+ # user = User.new
20
+ # user.avatar = params[:user][:avatar]
21
+ # user.avatar.url
22
+ # # => "/users/avatars/4/original_me.jpg"
23
+ # user.avatar.url(:thumb)
24
+ # # => "/users/avatars/4/thumb_me.jpg"
25
+ #
26
+ # See the +has_attached_file+ documentation for more details.
27
+
28
+ require 'erb'
29
+ require 'digest'
30
+ require 'tempfile'
31
+ require 'paperclip/version'
32
+ require 'paperclip/geometry_parser_factory'
33
+ require 'paperclip/geometry_detector_factory'
34
+ require 'paperclip/geometry'
35
+ require 'paperclip/processor'
36
+ require 'paperclip/processor_helpers'
37
+ require 'paperclip/tempfile'
38
+ require 'paperclip/thumbnail'
39
+ require 'paperclip/interpolations/plural_cache'
40
+ require 'paperclip/interpolations'
41
+ require 'paperclip/tempfile_factory'
42
+ require 'paperclip/style'
43
+ require 'paperclip/attachment'
44
+ require 'paperclip/storage'
45
+ require 'paperclip/callbacks'
46
+ require 'paperclip/file_command_content_type_detector'
47
+ require 'paperclip/media_type_spoof_detector'
48
+ require 'paperclip/content_type_detector'
49
+ require 'paperclip/glue'
50
+ require 'paperclip/errors'
51
+ require 'paperclip/missing_attachment_styles'
52
+ require 'paperclip/validators'
53
+ require 'paperclip/logger'
54
+ require 'paperclip/helpers'
55
+ require 'paperclip/has_attached_file'
56
+ require 'paperclip/attachment_registry'
57
+ require 'paperclip/filename_cleaner'
58
+ require 'paperclip/rails_environment'
59
+ require "paperclip/deprecations"
60
+
61
+ begin
62
+ # Use mime/types/columnar if available, for reduced memory usage
63
+ require "mime/types/columnar"
64
+ rescue LoadError
65
+ require "mime/types"
66
+ end
67
+
68
+ require 'mimemagic'
69
+ require 'mimemagic/overlay'
70
+ require 'logger'
71
+ require 'cocaine'
72
+
73
+ require 'paperclip/railtie' if defined?(Rails::Railtie)
74
+
75
+ # The base module that gets included in ActiveRecord::Base. See the
76
+ # documentation for Paperclip::ClassMethods for more useful information.
77
+ module Paperclip
78
+ extend Helpers
79
+ extend Logger
80
+ extend ProcessorHelpers
81
+
82
+ # Provides configurability to Paperclip. The options available are:
83
+ # * whiny: Will raise an error if Paperclip cannot process thumbnails of
84
+ # an uploaded image. Defaults to true.
85
+ # * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
86
+ # log levels, etc. Defaults to true.
87
+ # * command_path: Defines the path at which to find the command line
88
+ # programs if they are not visible to Rails the system's search path. Defaults to
89
+ # nil, which uses the first executable found in the user's search path.
90
+ # * use_exif_orientation: Whether to inspect EXIF data to determine an
91
+ # image's orientation. Defaults to true.
92
+ def self.options
93
+ @options ||= {
94
+ :whiny => true,
95
+ :image_magick_path => nil,
96
+ :command_path => nil,
97
+ :log => true,
98
+ :log_command => true,
99
+ :swallow_stderr => true,
100
+ :content_type_mappings => {},
101
+ :use_exif_orientation => true
102
+ }
103
+ end
104
+
105
+ def self.io_adapters=(new_registry)
106
+ @io_adapters = new_registry
107
+ end
108
+
109
+ def self.io_adapters
110
+ @io_adapters ||= Paperclip::AdapterRegistry.new
111
+ end
112
+
113
+ module ClassMethods
114
+ # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
115
+ # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
116
+ # The attribute returns a Paperclip::Attachment object which handles the management of
117
+ # that file. The intent is to make the attachment as much like a normal attribute. The
118
+ # thumbnails will be created when the new file is assigned, but they will *not* be saved
119
+ # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
120
+ # called on it, the attachment will *not* be deleted until +save+ is called. See the
121
+ # Paperclip::Attachment documentation for more specifics. There are a number of options
122
+ # you can set to change the behavior of a Paperclip attachment:
123
+ # * +url+: The full URL of where the attachment is publically accessible. This can just
124
+ # as easily point to a directory served directly through Apache as it can to an action
125
+ # that can control permissions. You can specify the full domain and path, but usually
126
+ # just an absolute path is sufficient. The leading slash *must* be included manually for
127
+ # absolute paths. The default value is
128
+ # "/system/:class/:attachment/:id_partition/:style/:filename". See
129
+ # Paperclip::Attachment#interpolate for more information on variable interpolaton.
130
+ # :url => "/:class/:attachment/:id/:style_:filename"
131
+ # :url => "http://some.other.host/stuff/:class/:id_:extension"
132
+ # * +default_url+: The URL that will be returned if there is no attachment assigned.
133
+ # This field is interpolated just as the url is. The default value is
134
+ # "/:attachment/:style/missing.png"
135
+ # has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
136
+ # User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
137
+ # * +styles+: A hash of thumbnail styles and their geometries. You can find more about
138
+ # geometry strings at the ImageMagick website
139
+ # (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
140
+ # also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
141
+ # inside the dimensions and then crop the rest off (weighted at the center). The
142
+ # default value is to generate no thumbnails.
143
+ # * +default_style+: The thumbnail style that will be used by default URLs.
144
+ # Defaults to +original+.
145
+ # has_attached_file :avatar, :styles => { :normal => "100x100#" },
146
+ # :default_style => :normal
147
+ # user.avatar.url # => "/avatars/23/normal_me.png"
148
+ # * +keep_old_files+: Keep the existing attachment files (original + resized) from
149
+ # being automatically deleted when an attachment is cleared or updated. Defaults to +false+.
150
+ # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
151
+ # record is destroyed. Defaults to +false+.
152
+ # * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
153
+ # to a command line error. This will override the global setting for this attachment.
154
+ # Defaults to true.
155
+ # * +convert_options+: When creating thumbnails, use this free-form options
156
+ # array to pass in various convert command options. Typical options are "-strip" to
157
+ # remove all Exif data from the image (save space for thumbnails and avatars) or
158
+ # "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
159
+ # convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
160
+ # Note that this option takes a hash of options, each of which correspond to the style
161
+ # of thumbnail being generated. You can also specify :all as a key, which will apply
162
+ # to all of the thumbnails being generated. If you specify options for the :original,
163
+ # it would be best if you did not specify destructive options, as the intent of keeping
164
+ # the original around is to regenerate all the thumbnails when requirements change.
165
+ # has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
166
+ # :convert_options => {
167
+ # :all => "-strip",
168
+ # :negative => "-negate"
169
+ # }
170
+ # NOTE: While not deprecated yet, it is not recommended to specify options this way.
171
+ # It is recommended that :convert_options option be included in the hash passed to each
172
+ # :styles for compatibility with future versions.
173
+ # NOTE: Strings supplied to :convert_options are split on space in order to undergo
174
+ # shell quoting for safety. If your options require a space, please pre-split them
175
+ # and pass an array to :convert_options instead.
176
+ # * +storage+: Chooses the storage backend where the files will be stored. The current
177
+ # choices are :filesystem, :fog and :s3. The default is :filesystem. Make sure you read the
178
+ # documentation for Paperclip::Storage::Filesystem, Paperclip::Storage::Fog and Paperclip::Storage::S3
179
+ # for backend-specific options.
180
+ #
181
+ # It's also possible for you to dynamically define your interpolation string for :url,
182
+ # :default_url, and :path in your model by passing a method name as a symbol as a argument
183
+ # for your has_attached_file definition:
184
+ #
185
+ # class Person
186
+ # has_attached_file :avatar, :default_url => :default_url_by_gender
187
+ #
188
+ # private
189
+ #
190
+ # def default_url_by_gender
191
+ # "/assets/avatars/default_#{gender}.png"
192
+ # end
193
+ # end
194
+ def has_attached_file(name, options = {})
195
+ Paperclip::Deprecations.check
196
+ HasAttachedFile.define_on(self, name, options)
197
+ end
198
+ end
199
+ end
200
+
201
+ # This stuff needs to be run after Paperclip is defined.
202
+ require 'paperclip/io_adapters/registry'
203
+ require 'paperclip/io_adapters/abstract_adapter'
204
+ require 'paperclip/io_adapters/empty_string_adapter'
205
+ require 'paperclip/io_adapters/identity_adapter'
206
+ require 'paperclip/io_adapters/file_adapter'
207
+ require 'paperclip/io_adapters/stringio_adapter'
208
+ require 'paperclip/io_adapters/data_uri_adapter'
209
+ require 'paperclip/io_adapters/nil_adapter'
210
+ require 'paperclip/io_adapters/attachment_adapter'
211
+ require 'paperclip/io_adapters/uploaded_file_adapter'
212
+ require 'paperclip/io_adapters/uri_adapter'
213
+ require 'paperclip/io_adapters/http_url_proxy_adapter'
@@ -0,0 +1,127 @@
1
+ require 'paperclip/attachment_registry'
2
+
3
+ module Paperclip
4
+ module Task
5
+ def self.obtain_class
6
+ class_name = ENV['CLASS'] || ENV['class']
7
+ raise "Must specify CLASS" unless class_name
8
+ class_name
9
+ end
10
+
11
+ def self.obtain_attachments(klass)
12
+ klass = Paperclip.class_for(klass.to_s)
13
+ name = ENV['ATTACHMENT'] || ENV['attachment']
14
+
15
+ attachment_names = Paperclip::AttachmentRegistry.names_for(klass)
16
+
17
+ if attachment_names.empty?
18
+ raise "Class #{klass.name} has no attachments specified"
19
+ end
20
+
21
+ if !name.blank? && attachment_names.map(&:to_s).include?(name.to_s)
22
+ [ name ]
23
+ else
24
+ attachment_names
25
+ end
26
+ end
27
+
28
+ def self.log_error(error)
29
+ $stderr.puts error
30
+ end
31
+ end
32
+ end
33
+
34
+ namespace :paperclip do
35
+ desc "Refreshes both metadata and thumbnails."
36
+ task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
37
+
38
+ namespace :refresh do
39
+ desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT and STYLES splitted by comma)."
40
+ task :thumbnails => :environment do
41
+ klass = Paperclip::Task.obtain_class
42
+ names = Paperclip::Task.obtain_attachments(klass)
43
+ styles = (ENV['STYLES'] || ENV['styles'] || '').split(',').map(&:to_sym)
44
+ names.each do |name|
45
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
46
+ attachment = instance.send(name)
47
+ begin
48
+ attachment.reprocess!(*styles)
49
+ rescue Exception => e
50
+ Paperclip::Task.log_error("exception while processing #{klass} ID #{instance.id}:")
51
+ Paperclip::Task.log_error(" " + e.message + "\n")
52
+ end
53
+ unless instance.errors.blank?
54
+ Paperclip::Task.log_error("errors while processing #{klass} ID #{instance.id}:")
55
+ Paperclip::Task.log_error(" " + instance.errors.full_messages.join("\n ") + "\n")
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
62
+ task :metadata => :environment do
63
+ klass = Paperclip::Task.obtain_class
64
+ names = Paperclip::Task.obtain_attachments(klass)
65
+ names.each do |name|
66
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
67
+ if file = Paperclip.io_adapters.for(instance.send(name))
68
+ instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
69
+ instance.send("#{name}_content_type=", file.content_type.to_s.strip)
70
+ instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
71
+ instance.save(:validate => false)
72
+ else
73
+ true
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ desc "Regenerates missing thumbnail styles for all classes using Paperclip."
80
+ task :missing_styles => :environment do
81
+ Rails.application.eager_load!
82
+ Paperclip.missing_attachments_styles.each do |klass, attachment_definitions|
83
+ attachment_definitions.each do |attachment_name, missing_styles|
84
+ puts "Regenerating #{klass} -> #{attachment_name} -> #{missing_styles.inspect}"
85
+ ENV['CLASS'] = klass.to_s
86
+ ENV['ATTACHMENT'] = attachment_name.to_s
87
+ ENV['STYLES'] = missing_styles.join(',')
88
+ Rake::Task['paperclip:refresh:thumbnails'].execute
89
+ end
90
+ end
91
+ Paperclip.save_current_attachments_styles!
92
+ end
93
+ end
94
+
95
+ desc "Cleans out invalid attachments. Useful after you've added new validations."
96
+ task :clean => :environment do
97
+ klass = Paperclip::Task.obtain_class
98
+ names = Paperclip::Task.obtain_attachments(klass)
99
+ names.each do |name|
100
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
101
+ unless instance.valid?
102
+ attributes = %w(file_size file_name content_type).map{ |suffix| "#{name}_#{suffix}".to_sym }
103
+ if attributes.any?{ |attribute| instance.errors[attribute].present? }
104
+ instance.send("#{name}=", nil)
105
+ instance.save(:validate => false)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ desc "find missing attachments. Useful to know which attachments are broken"
113
+ task :find_broken_attachments => :environment do
114
+ klass = Paperclip::Task.obtain_class
115
+ names = Paperclip::Task.obtain_attachments(klass)
116
+ names.each do |name|
117
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
118
+ attachment = instance.send(name)
119
+ if attachment.exists?
120
+ print "."
121
+ else
122
+ Paperclip::Task.log_error("#{instance.class}##{attachment.name}, #{instance.id}, #{attachment.url}")
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
data/paperclip.gemspec ADDED
@@ -0,0 +1,52 @@
1
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
2
+ require 'paperclip/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "kt-paperclip"
6
+ s.version = Paperclip::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.author = "Surendra Singhi"
9
+ s.email = ["ssinghi@kreeti.com"]
10
+ s.homepage = "https://github.com/kreeti/kt-paperclip"
11
+ s.summary = "File attachments as attributes for ActiveRecord"
12
+ s.description = "Easy upload management for ActiveRecord"
13
+ s.license = "MIT"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.requirements << "ImageMagick"
21
+ s.required_ruby_version = ">= 1.9.2"
22
+
23
+ s.add_dependency('activemodel', '>= 3.2.0', '< 5.0')
24
+ s.add_dependency('activesupport', '>= 3.2.0', '< 5.0')
25
+ s.add_dependency('cocaine', '~> 0.5.5')
26
+ s.add_dependency('mime-types')
27
+ s.add_dependency('mimemagic', '~> 0.3.8')
28
+ s.add_dependency('bigdecimal', '1.3.5')
29
+
30
+ s.add_development_dependency('activerecord', '>= 3.2.0', '< 5.0')
31
+ s.add_development_dependency('shoulda')
32
+ s.add_development_dependency('rspec', '~> 3.0')
33
+ s.add_development_dependency('appraisal')
34
+ s.add_development_dependency('mocha')
35
+ s.add_development_dependency('aws-sdk', '>= 1.5.7', "<= 2.0")
36
+ s.add_development_dependency('bourne', '< 1.6')
37
+ s.add_development_dependency('cucumber', '~> 1.3.18')
38
+ s.add_development_dependency('aruba', '~> 0.9.0')
39
+ s.add_development_dependency('nokogiri')
40
+ # Ruby version < 1.9.3 can't install capybara > 2.0.3.
41
+ s.add_development_dependency('capybara')
42
+ s.add_development_dependency('bundler')
43
+ s.add_development_dependency('fog-aws')
44
+ s.add_development_dependency('fog-local')
45
+ s.add_development_dependency('launchy')
46
+ s.add_development_dependency('rake')
47
+ s.add_development_dependency('fakeweb')
48
+ s.add_development_dependency('railties')
49
+ s.add_development_dependency('actionmailer', '>= 3.2.0', '< 5.0')
50
+ s.add_development_dependency('generator_spec')
51
+ s.add_development_dependency('timecop')
52
+ end
@@ -0,0 +1,134 @@
1
+ require 'paperclip/matchers'
2
+
3
+ module Paperclip
4
+ # =Paperclip Shoulda Macros
5
+ #
6
+ # These macros are intended for use with shoulda, and will be included into
7
+ # your tests automatically. All of the macros use the standard shoulda
8
+ # assumption that the name of the test is based on the name of the model
9
+ # you're testing (that is, UserTest is the test for the User model), and
10
+ # will load that class for testing purposes.
11
+ module Shoulda
12
+ include Matchers
13
+ # This will test whether you have defined your attachment correctly by
14
+ # checking for all the required fields exist after the definition of the
15
+ # attachment.
16
+ def should_have_attached_file name
17
+ klass = self.name.gsub(/Test$/, '').constantize
18
+ matcher = have_attached_file name
19
+ should matcher.description do
20
+ assert_accepts(matcher, klass)
21
+ end
22
+ end
23
+
24
+ # Tests for validations on the presence of the attachment.
25
+ def should_validate_attachment_presence name
26
+ klass = self.name.gsub(/Test$/, '').constantize
27
+ matcher = validate_attachment_presence name
28
+ should matcher.description do
29
+ assert_accepts(matcher, klass)
30
+ end
31
+ end
32
+
33
+ # Tests that you have content_type validations specified. There are two
34
+ # options, :valid and :invalid. Both accept an array of strings. The
35
+ # strings should be a list of content types which will pass and fail
36
+ # validation, respectively.
37
+ def should_validate_attachment_content_type name, options = {}
38
+ klass = self.name.gsub(/Test$/, '').constantize
39
+ valid = [options[:valid]].flatten
40
+ invalid = [options[:invalid]].flatten
41
+ matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
42
+ should matcher.description do
43
+ assert_accepts(matcher, klass)
44
+ end
45
+ end
46
+
47
+ # Tests to ensure that you have file size validations turned on. You
48
+ # can pass the same options to this that you can to
49
+ # validate_attachment_file_size - :less_than, :greater_than, and :in.
50
+ # :less_than checks that a file is less than a certain size, :greater_than
51
+ # checks that a file is more than a certain size, and :in takes a Range or
52
+ # Array which specifies the lower and upper limits of the file size.
53
+ def should_validate_attachment_size name, options = {}
54
+ klass = self.name.gsub(/Test$/, '').constantize
55
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
56
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
57
+ range = (min..max)
58
+ matcher = validate_attachment_size(name).in(range)
59
+ should matcher.description do
60
+ assert_accepts(matcher, klass)
61
+ end
62
+ end
63
+
64
+ # Stubs the HTTP PUT for an attachment using S3 storage.
65
+ #
66
+ # @example
67
+ # stub_paperclip_s3('user', 'avatar', 'png')
68
+ def stub_paperclip_s3(model, attachment, extension)
69
+ definition = model.gsub(" ", "_").classify.constantize.
70
+ attachment_definitions[attachment.to_sym]
71
+
72
+ path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
73
+ path.gsub!(/:([^\/\.]+)/) do |match|
74
+ "([^\/\.]+)"
75
+ end
76
+
77
+ begin
78
+ FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
79
+ rescue NameError
80
+ raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
81
+ end
82
+ end
83
+
84
+ # Stub S3 and return a file for attachment. Best with Factory Girl.
85
+ # Uses a strict directory convention:
86
+ #
87
+ # features/support/paperclip
88
+ #
89
+ # This method is used by the Paperclip-provided Cucumber step:
90
+ #
91
+ # When I attach a "demo_tape" "mp3" file to a "band" on S3
92
+ #
93
+ # @example
94
+ # Factory.define :band_with_demo_tape, :parent => :band do |band|
95
+ # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
96
+ # end
97
+ def paperclip_fixture(model, attachment, extension)
98
+ stub_paperclip_s3(model, attachment, extension)
99
+ base_path = File.join(File.dirname(__FILE__), "..", "..",
100
+ "features", "support", "paperclip")
101
+ File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
102
+ end
103
+ end
104
+ end
105
+
106
+ if defined?(ActionDispatch::Integration::Session)
107
+ class ActionDispatch::IntegrationTest::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+ elsif defined?(ActionController::Integration::Session)
111
+ class ActionController::Integration::Session #:nodoc:
112
+ include Paperclip::Shoulda
113
+ end
114
+ end
115
+
116
+ if defined?(FactoryGirl::Factory)
117
+ class FactoryGirl::Factory
118
+ include Paperclip::Shoulda #:nodoc:
119
+ end
120
+ else
121
+ class Factory
122
+ include Paperclip::Shoulda #:nodoc:
123
+ end
124
+ end
125
+
126
+ if defined?(Minitest)
127
+ class Minitest::Unit::TestCase #:nodoc:
128
+ extend Paperclip::Shoulda
129
+ end
130
+ elsif defined?(Test)
131
+ class Test::Unit::TestCase #:nodoc:
132
+ extend Paperclip::Shoulda
133
+ end
134
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,4 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Attachment Definitions" do
4
+ it 'returns all of the attachments on the class' do
5
+ reset_class "Dummy"
6
+ Dummy.has_attached_file :avatar, {path: "abc"}
7
+ Dummy.has_attached_file :other_attachment, {url: "123"}
8
+ Dummy.do_not_validate_attachment_file_type :avatar
9
+ expected = {avatar: {path: "abc"}, other_attachment: {url: "123"}}
10
+
11
+ expect(Dummy.attachment_definitions).to eq expected
12
+ end
13
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'Attachment Processing' do
5
+ context 'using validates_attachment_content_type' do
6
+ before do
7
+ rebuild_class
8
+ end
9
+
10
+ it 'processes attachments given a valid assignment' do
11
+ file = File.new(fixture_file("5k.png"))
12
+ Dummy.validates_attachment_content_type :avatar, content_type: "image/png"
13
+ instance = Dummy.new
14
+ attachment = instance.avatar
15
+ attachment.expects(:post_process_styles)
16
+
17
+ attachment.assign(file)
18
+ end
19
+
20
+ it 'does not process attachments given an invalid assignment with :not' do
21
+ file = File.new(fixture_file("5k.png"))
22
+ Dummy.validates_attachment_content_type :avatar, not: "image/png"
23
+ instance = Dummy.new
24
+ attachment = instance.avatar
25
+ attachment.expects(:post_process_styles).never
26
+
27
+ attachment.assign(file)
28
+ end
29
+
30
+ it 'does not process attachments given an invalid assignment with :content_type' do
31
+ file = File.new(fixture_file("5k.png"))
32
+ Dummy.validates_attachment_content_type :avatar, content_type: "image/tiff"
33
+ instance = Dummy.new
34
+ attachment = instance.avatar
35
+ attachment.expects(:post_process_styles).never
36
+
37
+ attachment.assign(file)
38
+ end
39
+
40
+ it 'allows what would be an invalid assignment when validation :if clause returns false' do
41
+ invalid_assignment = File.new(fixture_file("5k.png"))
42
+ Dummy.validates_attachment_content_type :avatar, content_type: "image/tiff", if: lambda{false}
43
+ instance = Dummy.new
44
+ attachment = instance.avatar
45
+ attachment.expects(:post_process_styles)
46
+
47
+ attachment.assign(invalid_assignment)
48
+ end
49
+ end
50
+
51
+ context 'using validates_attachment' do
52
+ it 'processes attachments given a valid assignment' do
53
+ file = File.new(fixture_file("5k.png"))
54
+ Dummy.validates_attachment :avatar, content_type: {content_type: "image/png"}
55
+ instance = Dummy.new
56
+ attachment = instance.avatar
57
+ attachment.expects(:post_process_styles)
58
+
59
+ attachment.assign(file)
60
+ end
61
+
62
+ it 'does not process attachments given an invalid assignment with :not' do
63
+ file = File.new(fixture_file("5k.png"))
64
+ Dummy.validates_attachment :avatar, content_type: {not: "image/png"}
65
+ instance = Dummy.new
66
+ attachment = instance.avatar
67
+ attachment.expects(:post_process_styles).never
68
+
69
+ attachment.assign(file)
70
+ end
71
+
72
+ it 'does not process attachments given an invalid assignment with :content_type' do
73
+ file = File.new(fixture_file("5k.png"))
74
+ Dummy.validates_attachment :avatar, content_type: {content_type: "image/tiff"}
75
+ instance = Dummy.new
76
+ attachment = instance.avatar
77
+ attachment.expects(:post_process_styles).never
78
+
79
+ attachment.assign(file)
80
+ end
81
+ end
82
+ end