paperclip 2.3.16 → 2.4.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.

Potentially problematic release.


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

@@ -41,7 +41,7 @@ module Paperclip
41
41
  def failure_message
42
42
  "".tap do |str|
43
43
  str << "Content types #{@allowed_types.join(", ")} should be accepted" if @allowed_types.present?
44
- str << "\n" if @allowed_types.present && @rejected_types.present?
44
+ str << "\n" if @allowed_types.present? && @rejected_types.present?
45
45
  str << "Content types #{@rejected_types.join(", ")} should be rejected by #{@attachment_name}" if @rejected_types.present?
46
46
  end
47
47
  end
@@ -49,7 +49,7 @@ module Paperclip
49
49
  def negative_failure_message
50
50
  "".tap do |str|
51
51
  str << "Content types #{@allowed_types.join(", ")} should be rejected" if @allowed_types.present?
52
- str << "\n" if @allowed_types.present && @rejected_types.present?
52
+ str << "\n" if @allowed_types.present? && @rejected_types.present?
53
53
  str << "Content types #{@rejected_types.join(", ")} should be accepted by #{@attachment_name}" if @rejected_types.present?
54
54
  end
55
55
  end
@@ -0,0 +1,86 @@
1
+
2
+ require 'set'
3
+ module Paperclip
4
+
5
+ class << self
6
+ attr_accessor :classes_with_attachments
7
+ attr_writer :registered_attachments_styles_path
8
+ def registered_attachments_styles_path
9
+ @registered_attachments_styles_path ||= Rails.root.join('public/system/paperclip_attachments.yml').to_s
10
+ end
11
+ end
12
+
13
+ self.classes_with_attachments = Set.new
14
+
15
+
16
+ # Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles)
17
+ def self.get_registered_attachments_styles
18
+ YAML.load_file(Paperclip.registered_attachments_styles_path)
19
+ rescue Errno::ENOENT
20
+ nil
21
+ end
22
+ private_class_method :get_registered_attachments_styles
23
+
24
+ def self.save_current_attachments_styles!
25
+ File.open(Paperclip.registered_attachments_styles_path, 'w') do |f|
26
+ YAML.dump(current_attachments_styles, f)
27
+ end
28
+ end
29
+
30
+ # Returns hash with styles for all classes using Paperclip.
31
+ # Unfortunately current version does not work with lambda styles:(
32
+ # {
33
+ # :User => {:avatar => [:small, :big]},
34
+ # :Book => {
35
+ # :cover => [:thumb, :croppable]},
36
+ # :sample => [:thumb, :big]},
37
+ # }
38
+ # }
39
+ def self.current_attachments_styles
40
+ Hash.new.tap do |current_styles|
41
+ Paperclip.classes_with_attachments.each do |klass|
42
+ klass.attachment_definitions.each do |attachment_name, attachment_attributes|
43
+ # TODO: is it even possible to take into account Procs?
44
+ next if attachment_attributes[:styles].kind_of?(Proc)
45
+ attachment_attributes[:styles].try(:keys).try(:each) do |style_name|
46
+ klass_sym = klass.to_s.to_sym
47
+ current_styles[klass_sym] ||= Hash.new
48
+ current_styles[klass_sym][attachment_name.to_sym] ||= Array.new
49
+ current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
50
+ current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ private_class_method :current_attachments_styles
57
+
58
+ # Returns hash with styles missing from recent run of rake paperclip:refresh:missing_styles
59
+ # {
60
+ # :User => {:avatar => [:big]},
61
+ # :Book => {
62
+ # :cover => [:croppable]},
63
+ # }
64
+ # }
65
+ def self.missing_attachments_styles
66
+ current_styles = current_attachments_styles
67
+ registered_styles = get_registered_attachments_styles
68
+
69
+ Hash.new.tap do |missing_styles|
70
+ current_styles.each do |klass, attachment_definitions|
71
+ attachment_definitions.each do |attachment_name, styles|
72
+ registered = registered_styles[klass][attachment_name] rescue []
73
+ missed = styles - registered
74
+ if missed.present?
75
+ klass_sym = klass.to_s.to_sym
76
+ missing_styles[klass_sym] ||= Hash.new
77
+ missing_styles[klass_sym][attachment_name.to_sym] ||= Array.new
78
+ missing_styles[klass_sym][attachment_name.to_sym].concat(missed.to_a)
79
+ missing_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ end
@@ -46,6 +46,9 @@ module Paperclip
46
46
  end
47
47
  FileUtils.chmod(0666&~File.umask, path(style_name))
48
48
  end
49
+
50
+ after_flush_writes # allows attachment to clean up temp files
51
+
49
52
  @queued_for_write = {}
50
53
  end
51
54
 
@@ -42,12 +42,15 @@ module Paperclip
42
42
 
43
43
  base.instance_eval do
44
44
  @fog_directory = @options[:fog_directory]
45
- @fog_credentials = @options[:fog_credentials]
45
+ @fog_credentials = parse_credentials(@options[:fog_credentials])
46
46
  @fog_host = @options[:fog_host]
47
- @fog_public = @options[:fog_public] || true
47
+ @fog_public = @options.key?(:fog_public) ? @options[:fog_public] : true
48
48
  @fog_file = @options[:fog_file] || {}
49
49
 
50
- @url = ':fog_public_url'
50
+ unless @url.to_s.match(/^:fog.*url$/)
51
+ @path = @path.gsub(/:url/, @url)
52
+ @url = ':fog_public_url'
53
+ end
51
54
  Paperclip.interpolates(:fog_public_url) do |attachment, style|
52
55
  attachment.public_url(style)
53
56
  end unless Paperclip::Interpolations.respond_to? :fog_public_url
@@ -79,6 +82,9 @@ module Paperclip
79
82
  retry
80
83
  end
81
84
  end
85
+
86
+ after_flush_writes # allows attachment to clean up temp files
87
+
82
88
  @queued_for_write = {}
83
89
  end
84
90
 
@@ -117,8 +123,27 @@ module Paperclip
117
123
  end
118
124
  end
119
125
 
126
+ def parse_credentials(creds)
127
+ creds = find_credentials(creds).stringify_keys
128
+ env = Object.const_defined?(:Rails) ? Rails.env : nil
129
+ (creds[env] || creds).symbolize_keys
130
+ end
131
+
120
132
  private
121
133
 
134
+ def find_credentials(creds)
135
+ case creds
136
+ when File
137
+ YAML::load(ERB.new(File.read(creds.path)).result)
138
+ when String, Pathname
139
+ YAML::load(ERB.new(File.read(creds)).result)
140
+ when Hash
141
+ creds
142
+ else
143
+ raise ArgumentError, "Credentials are not a path, file, or hash."
144
+ end
145
+ end
146
+
122
147
  def connection
123
148
  @connection ||= ::Fog::Storage.new(@fog_credentials)
124
149
  end
@@ -126,8 +151,6 @@ module Paperclip
126
151
  def directory
127
152
  @directory ||= connection.directories.new(:key => @fog_directory)
128
153
  end
129
-
130
154
  end
131
-
132
155
  end
133
156
  end
@@ -196,6 +196,9 @@ module Paperclip
196
196
  raise
197
197
  end
198
198
  end
199
+
200
+ after_flush_writes # allows attachment to clean up temp files
201
+
199
202
  @queued_for_write = {}
200
203
  end
201
204
 
@@ -49,6 +49,10 @@ module Paperclip
49
49
  attachment.send(:extra_options_for, name)
50
50
  end
51
51
 
52
+ def source_file_options
53
+ attachment.send(:extra_source_file_options_for, name)
54
+ end
55
+
52
56
  # returns the geometry string for this style
53
57
  # if a proc has been supplied, we call it here
54
58
  def geometry
@@ -63,7 +67,7 @@ module Paperclip
63
67
  @other_args.each do |k,v|
64
68
  args[k] = v.respond_to?(:call) ? v.call(attachment) : v
65
69
  end
66
- [:processors, :geometry, :format, :whiny, :convert_options].each do |k|
70
+ [:processors, :geometry, :format, :whiny, :convert_options, :source_file_options].each do |k|
67
71
  (arg = send(k)) && args[k] = arg
68
72
  end
69
73
  args
@@ -72,7 +76,7 @@ module Paperclip
72
76
  # Supports getting and setting style properties with hash notation to ensure backwards-compatibility
73
77
  # eg. @attachment.styles[:large][:geometry]@ will still work
74
78
  def [](key)
75
- if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated].include?(key)
79
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)
76
80
  send(key)
77
81
  elsif defined? @other_args[key]
78
82
  @other_args[key]
@@ -80,7 +84,7 @@ module Paperclip
80
84
  end
81
85
 
82
86
  def []=(key, value)
83
- if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated].include?(key)
87
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key)
84
88
  send("#{key}=".intern, value)
85
89
  else
86
90
  @other_args[key] = value
@@ -14,14 +14,25 @@ module Paperclip
14
14
  # unless specified. Thumbnail creation will raise no errors unless
15
15
  # +whiny+ is true (which it is, by default. If +convert_options+ is
16
16
  # set, the options will be appended to the convert command upon image conversion
17
- def initialize file, options = {}, attachment = nil
17
+ #
18
+ # Options include:
19
+ #
20
+ # +geometry+ - the desired width and height of the thumbnail (required)
21
+ # +file_geometry_parser+ - an object with a method named +from_file+ that takes an image file and produces its geometry and a +transformation_to+. Defaults to Paperclip::Geometry
22
+ # +string_geometry_parser+ - an object with a method named +parse+ that takes a string and produces an object with +width+, +height+, and +to_s+ accessors. Defaults to Paperclip::Geometry
23
+ # +source_file_options+ - flags passed to the +convert+ command that influence how the source file is read
24
+ # +convert_options+ - flags passed to the +convert+ command that influence how the image is processed
25
+ # +whiny+ - whether to raise an error when processing fails. Defaults to true
26
+ # +format+ - the desired filename extension
27
+ # +animated+ - whether to merge all the layers in the image. Defaults to true
28
+ def initialize(file, options = {}, attachment = nil)
18
29
  super
19
30
 
20
- geometry = options[:geometry]
31
+ geometry = options[:geometry] # this is not an option
21
32
  @file = file
22
33
  @crop = geometry[-1,1] == '#'
23
- @target_geometry = Geometry.parse geometry
24
- @current_geometry = Geometry.from_file @file
34
+ @target_geometry = (options[:string_geometry_parser] || Geometry).parse(geometry)
35
+ @current_geometry = (options[:file_geometry_parser] || Geometry).from_file(@file)
25
36
  @source_file_options = options[:source_file_options]
26
37
  @convert_options = options[:convert_options]
27
38
  @whiny = options[:whiny].nil? ? true : options[:whiny]
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "2.3.16" unless defined? Paperclip::VERSION
2
+ VERSION = "2.4.0" unless defined? Paperclip::VERSION
3
3
  end
@@ -60,6 +60,22 @@ namespace :paperclip do
60
60
  end
61
61
  end
62
62
  end
63
+
64
+ desc "Regenerates missing thumbnail styles for all classes using Paperclip."
65
+ task :missing_styles => :environment do
66
+ # Force loading all model classes to never miss any has_attached_file declaration:
67
+ Dir[Rails.root + 'app/models/**/*.rb'].each { |path| load path }
68
+ Paperclip.missing_attachments_styles.each do |klass, attachment_definitions|
69
+ attachment_definitions.each do |attachment_name, missing_styles|
70
+ puts "Regenerating #{klass} -> #{attachment_name} -> #{missing_styles.inspect}"
71
+ ENV['CLASS'] = klass.to_s
72
+ ENV['ATTACHMENT'] = attachment_name.to_s
73
+ ENV['STYLES'] = missing_styles.join(',')
74
+ Rake::Task['paperclip:refresh:thumbnails'].execute
75
+ end
76
+ end
77
+ Paperclip.save_current_attachments_styles!
78
+ end
63
79
  end
64
80
 
65
81
  desc "Cleans out invalid attachments. Useful after you've added new validations."
@@ -263,6 +263,29 @@ class AttachmentTest < Test::Unit::TestCase
263
263
  end
264
264
  end
265
265
 
266
+ context "An attachment with :source_file_options" do
267
+ setup do
268
+ rebuild_model :styles => {
269
+ :thumb => "100x100",
270
+ :large => "400x400"
271
+ },
272
+ :source_file_options => {
273
+ :all => "-density 400",
274
+ :thumb => "-depth 8"
275
+ }
276
+ @dummy = Dummy.new
277
+ @dummy.avatar
278
+ end
279
+
280
+ should "report the correct options when sent #extra_source_file_options_for(:thumb)" do
281
+ assert_equal "-depth 8 -density 400", @dummy.avatar.send(:extra_source_file_options_for, :thumb), @dummy.avatar.source_file_options.inspect
282
+ end
283
+
284
+ should "report the correct options when sent #extra_source_file_options_for(:large)" do
285
+ assert_equal "-density 400", @dummy.avatar.send(:extra_source_file_options_for, :large)
286
+ end
287
+ end
288
+
266
289
  context "An attachment with :only_process" do
267
290
  setup do
268
291
  rebuild_model :styles => {
@@ -424,6 +447,10 @@ class AttachmentTest < Test::Unit::TestCase
424
447
 
425
448
  context "An attachment with :processors that is a proc" do
426
449
  setup do
450
+ class Paperclip::Test < Paperclip::Processor; end
451
+ @file = StringIO.new("...")
452
+ Paperclip::Test.stubs(:make).returns(@file)
453
+
427
454
  rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
428
455
  @attachment = Dummy.new.avatar
429
456
  end
@@ -476,7 +503,12 @@ class AttachmentTest < Test::Unit::TestCase
476
503
  end
477
504
 
478
505
  before_should "call #make with the right parameters passed as second argument" do
479
- expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
506
+ expected_params = @style_params[:once].merge({
507
+ :processors => [:thumbnail, :test],
508
+ :whiny => true,
509
+ :convert_options => "",
510
+ :source_file_options => ""
511
+ })
480
512
  Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file)
481
513
  end
482
514
 
@@ -1011,7 +1043,7 @@ class AttachmentTest < Test::Unit::TestCase
1011
1043
  end
1012
1044
  end
1013
1045
  end
1014
-
1046
+
1015
1047
  context "an attachment with delete_file option set to false" do
1016
1048
  setup do
1017
1049
  rebuild_model :preserve_files => true
@@ -1019,19 +1051,61 @@ class AttachmentTest < Test::Unit::TestCase
1019
1051
  @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
1020
1052
  @dummy.avatar = @file
1021
1053
  @dummy.save!
1022
- @attachment = @dummy.avatar
1023
- @path = @attachment.path
1054
+ @attachment = @dummy.avatar
1055
+ @path = @attachment.path
1024
1056
  end
1025
-
1026
- should "not delete the files from storage when attachment is destroyed" do
1057
+
1058
+ should "not delete the files from storage when attachment is destroyed" do
1027
1059
  @attachment.destroy
1028
1060
  assert File.exists?(@path)
1029
1061
  end
1030
-
1031
- should "not dleete the file when model is destroy" do
1062
+
1063
+ should "not delete the file when model is destroy" do
1032
1064
  @dummy.destroy
1033
1065
  assert File.exists?(@path)
1034
1066
  end
1035
1067
  end
1036
1068
 
1069
+ context "setting an interpolation class" do
1070
+ should "produce the URL with the given interpolations" do
1071
+ Interpolator = Class.new do
1072
+ def self.interpolate(pattern, attachment, style_name)
1073
+ "hello"
1074
+ end
1075
+ end
1076
+
1077
+ instance = Dummy.new
1078
+ attachment = Paperclip::Attachment.new(:avatar, instance, :interpolator => Interpolator)
1079
+
1080
+ assert_equal "hello", attachment.url
1081
+ end
1082
+ end
1083
+
1084
+ context "An attached file" do
1085
+ setup do
1086
+ rebuild_model
1087
+ @dummy = Dummy.new
1088
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
1089
+ @dummy.avatar = @file
1090
+ @dummy.save!
1091
+ @attachment = @dummy.avatar
1092
+ @path = @attachment.path
1093
+ end
1094
+
1095
+ should "not be deleted when the model fails to destroy" do
1096
+ @dummy.stubs(:destroy).raises(Exception)
1097
+
1098
+ assert_raise Exception do
1099
+ @dummy.destroy
1100
+ end
1101
+
1102
+ assert File.exists?(@path)
1103
+ end
1104
+
1105
+ should "be deleted when the model is destroyed" do
1106
+ @dummy.destroy
1107
+ assert ! File.exists?(@path)
1108
+ end
1109
+ end
1110
+
1037
1111
  end
@@ -0,0 +1,8 @@
1
+ development:
2
+ provider: AWS
3
+ aws_access_key_id: AWS_ID
4
+ aws_secret_access_key: AWS_SECRET
5
+ test:
6
+ provider: AWS
7
+ aws_access_key_id: AWS_ID
8
+ aws_secret_access_key: AWS_SECRET
@@ -6,6 +6,67 @@ Fog.mock!
6
6
  class FogTest < Test::Unit::TestCase
7
7
  context "" do
8
8
 
9
+ context "with credentials provided in a path string" do
10
+ setup do
11
+ rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" },
12
+ :storage => :fog,
13
+ :url => '/:attachment/:filename',
14
+ :fog_directory => "paperclip",
15
+ :fog_credentials => File.join(File.dirname(__FILE__), 'fixtures', 'fog.yml')
16
+ @dummy = Dummy.new
17
+ @dummy.avatar = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
18
+ end
19
+
20
+ should "have the proper information loading credentials from a file" do
21
+ assert_equal @dummy.avatar.instance_variable_get("@fog_credentials")[:provider], 'AWS'
22
+ end
23
+ end
24
+
25
+ context "with credentials provided in a File object" do
26
+ setup do
27
+ rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" },
28
+ :storage => :fog,
29
+ :url => '/:attachment/:filename',
30
+ :fog_directory => "paperclip",
31
+ :fog_credentials => File.open(File.join(File.dirname(__FILE__), 'fixtures', 'fog.yml'))
32
+ @dummy = Dummy.new
33
+ @dummy.avatar = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
34
+ end
35
+
36
+ should "have the proper information loading credentials from a file" do
37
+ assert_equal @dummy.avatar.instance_variable_get("@fog_credentials")[:provider], 'AWS'
38
+ end
39
+ end
40
+
41
+ context "with default values for path and url" do
42
+ setup do
43
+ rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" },
44
+ :storage => :fog,
45
+ :url => '/:attachment/:filename',
46
+ :fog_directory => "paperclip",
47
+ :fog_credentials => {
48
+ :provider => 'AWS',
49
+ :aws_access_key_id => 'AWS_ID',
50
+ :aws_secret_access_key => 'AWS_SECRET'
51
+ }
52
+ @dummy = Dummy.new
53
+ @dummy.avatar = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
54
+ end
55
+ should "be able to interpolate the path without blowing up" do
56
+ puts @dummy.avatar.instance_variable_get("@path")
57
+ assert_equal File.expand_path(File.join(File.dirname(__FILE__), "../public/avatars/5k.png")),
58
+ @dummy.avatar.path
59
+ end
60
+
61
+ should "clean up file objects" do
62
+ File.stubs(:exist?).returns(true)
63
+ Paperclip::Tempfile.any_instance.expects(:close).at_least_once()
64
+ Paperclip::Tempfile.any_instance.expects(:unlink).at_least_once()
65
+
66
+ @dummy.save!
67
+ end
68
+ end
69
+
9
70
  setup do
10
71
  @fog_directory = 'papercliptests'
11
72
 
@@ -112,6 +173,19 @@ class FogTest < Test::Unit::TestCase
112
173
  end
113
174
  end
114
175
 
176
+ context "with fog_public set to false" do
177
+ setup do
178
+ rebuild_model(@options.merge(:fog_public => false))
179
+ @dummy = Dummy.new
180
+ @dummy.avatar = StringIO.new('.')
181
+ @dummy.save
182
+ end
183
+
184
+ should 'set the @fog_public instance variable to false' do
185
+ assert_equal false, @dummy.avatar.instance_variable_get('@fog_public')
186
+ end
187
+ end
188
+
115
189
  end
116
190
 
117
191
  end