tdd-attachment_fu 0.9.6 → 0.9.9.b

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ * Mar 15, 2010 *
2
+ * Added a symbol syntax for parent-type-based size spec: calls the corresponding method on the current asset instance to
3
+ get a Hash of required thumbnails. Lets us dynamically specify what would otherwise be hard-coded, which is useful
4
+ when the set of thumbnails for a given parent type varies depending on the usage context.
5
+
6
+ * Aug 6, 2009 *
7
+ * JPEG quality control finalized across processors (although CoreImage applies it in a rather fuzzy-logic way), with tests.
8
+
1
9
  * Aug 4, 2009 *
2
10
  * Supports the :aspect/'!' geometry flag in all processors
3
11
  (thanks to http://www.deepcalm.com/writing/cropped-thumbnails-in-attachment_fu-using-imagescience)
data/README CHANGED
@@ -22,43 +22,80 @@ attachment_fu models
22
22
  ====================
23
23
 
24
24
  For all three of these storage options a table of metadata is required. This table will contain information about the file (hence the 'meta') and its location. This table has no restrictions on naming, unlike the extra table required for database storage, which must have a table name of db_files (and by convention a model of DbFile).
25
-
25
+
26
26
  In the model there are two methods made available by this plugins: has_attachment and validates_as_attachment.
27
27
 
28
28
  has_attachment(options = {})
29
29
  This method accepts the options in a hash:
30
- :content_type # Allowed content types.
31
- # Allows all by default. Use :image to allow all standard image types.
32
- :min_size # Minimum size allowed.
33
- # 1 byte is the default.
34
- :max_size # Maximum size allowed.
35
- # 1.megabyte is the default.
36
- :size # Range of sizes allowed.
37
- # (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
38
- :resize_to # Used by RMagick to resize images.
39
- # Pass either an array of width/height, or a geometry string.
40
- :thumbnails # Specifies a set of thumbnails to generate.
41
- # This accepts a hash of filename suffixes and RMagick resizing options.
42
- # This option need only be included if you want thumbnailing.
43
- :thumbnail_class # Set which model class to use for thumbnails.
44
- # This current attachment class is used by default.
45
- :jpeg_quality # Used to provide explicit JPEG quality for thumbnail/resize saves. Has to be an integer between 0 and 100.
46
- # Defaults vary depending on the processor (ImageScience: 100%, Rmagick/MiniMagick/Gd2: 75%, CoreImage: ~72%).
47
- # Note that only tdd-image_science (available from GitHub) currently supports explicit JPEG quality;
48
- # the default image_science currently forces 100%.
49
- :path_prefix # Path to store the uploaded files in.
50
- # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the S3 and Cloud Files backend.
51
- # Setting this sets the :storage to :file_system.
52
- :partition # Whether to partiton files in directories like /0000/0001/image.jpg. Default is true. Only applicable to the :file_system backend.
53
- :storage # Specifies the storage system to use..
54
- # Defaults to :db_file. Options are :file_system, :db_file, :s3, and :cloud_files.
55
- :cloudfront # If using S3 for storage, this option allows for serving the files via Amazon CloudFront.
56
- # Defaults to false.
57
- :processor # Sets the image processor to use for resizing of the attached image.
58
- # Options include ImageScience, Rmagick, MiniMagick, Gd2 and CoreImage. Default is whatever is installed.
59
- :uuid_primary_key # If your model's primary key is a 128-bit UUID in hexadecimal format, then set this to true.
60
- :association_options # attachment_fu automatically defines associations with thumbnails with has_many and belongs_to. If there are any additional options that you want to pass to these methods, then specify them here.
61
-
30
+ :content_type # Allowed content types.
31
+ # Allows all by default. Use :image to allow all standard image types.
32
+ #
33
+ :min_size # Minimum size allowed.
34
+ # 1 byte is the default.
35
+ #
36
+ :max_size # Maximum size allowed.
37
+ # 1.megabyte is the default.
38
+ #
39
+ :size # Range of sizes allowed.
40
+ # (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
41
+ #
42
+ :resize_to # Used by RMagick to resize images.
43
+ # Pass either an array of width/height, or a geometry string.
44
+ #
45
+ :thumbnails # Specifies a set of thumbnails to generate.
46
+ # This accepts a hash of filename suffixes and RMagick resizing options.
47
+ # This option need only be included if you want thumbnailing.
48
+ # If you have a polymorphic parent relationship, you can provide parent-type-specific
49
+ # thumbnail settings by using a pair with the type string as key and a Hash of thumbnail
50
+ # definitions, or a method symbol, as value. The method symbol will call the named
51
+ # method in order to get a dynamically-built Hash of thumbnail definitions, which gives
52
+ # you full flexibility.
53
+ # AttachmentFu automatically detects your first polymorphic +belongs_to+ relationship.
54
+ #
55
+ :thumbnail_class # Set which model class to use for thumbnails.
56
+ # This current attachment class is used by default.
57
+ #
58
+ :jpeg_quality # Used to provide explicit JPEG quality for thumbnail/resize saves. Can have multiple
59
+ # formats:
60
+ # * Integer from 0 (basically crap) to 100 (basically lossless, fat files).
61
+ # * When relying on tdd-image_science, you can also use one of its +JPEG_xxx+ constants
62
+ # for predefined ratios/settings.
63
+ # * You can also use a Hash, with keys being either thumbnail symbols (I repeat:
64
+ # _symbols_) or surface boundaries. A surface boundary is a string starting with either
65
+ # '<' or '>=', followed by a number of pixels. This lets you specify per-thumbnail or
66
+ # per-general-thumbnail-"size" JPEG qualities. (which can be useful when you have a
67
+ # _lot_ of thumbnail options). Surface example: +{ '<2000' => 90, '>=2000' => 75 }+.
68
+ #
69
+ # Defaults vary depending on the processor (ImageScience: 100%,
70
+ # Rmagick/MiniMagick/Gd2: 75%, CoreImage: auto-adjust).
71
+ # Note that only tdd-image_science (available from GitHub) currently supports explicit
72
+ # JPEG quality; the default image_science currently forces 100%.
73
+ #
74
+ :path_prefix # Path to store the uploaded files in.
75
+ # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the
76
+ # S3 and Cloud Files backend.
77
+ # Setting this sets the :storage to :file_system.
78
+ #
79
+ :partition # Whether to partiton files in directories like /0000/0001/image.jpg. Default is true.
80
+ # Only applicable to the :file_system backend.
81
+ #
82
+ :storage # Specifies the storage system to use..
83
+ # Defaults to :db_file. Options are :file_system, :db_file, :s3, and :cloud_files.
84
+ #
85
+ :cloudfront # If using S3 for storage, this option allows for serving the files via Amazon CloudFront.
86
+ # Defaults to false.
87
+ #
88
+ :processor # Sets the image processor to use for resizing of the attached image.
89
+ # Options include ImageScience, Rmagick, MiniMagick, Gd2 and CoreImage. Default is
90
+ # whatever is installed.
91
+ #
92
+ :uuid_primary_key # If your model's primary key is a 128-bit UUID in hexadecimal format, then set this to
93
+ # true.
94
+ #
95
+ :association_options # attachment_fu automatically defines associations with thumbnails with has_many and
96
+ # belongs_to. If there are any additional options that you want to pass to these methods,
97
+ # then specify them here.
98
+
62
99
 
63
100
  Examples:
64
101
  has_attachment :max_size => 1.kilobyte
@@ -69,7 +106,7 @@ has_attachment(options = {})
69
106
  has_attachment :content_type => ['application/pdf', :image], :resize_to => 'x50'
70
107
  has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
71
108
  has_attachment :storage => :file_system, :path_prefix => 'public/files'
72
- has_attachment :storage => :file_system, :path_prefix => 'public/files',
109
+ has_attachment :storage => :file_system, :path_prefix => 'public/files',
73
110
  :content_type => :image, :resize_to => [50,50], :partition => false
74
111
  has_attachment :storage => :file_system, :path_prefix => 'public/files',
75
112
  :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
@@ -77,9 +114,28 @@ has_attachment(options = {})
77
114
  has_attachment :store => :s3, :cloudfront => true
78
115
  has_attachment :storage => :cloud_files
79
116
 
117
+ # Let's say we have a polymorphic belongs_to, e.g. called 'imageable', where imageable_type (or whatever the
118
+ # :foreign_type option was set to) can be, among other things, 'Product', 'User' or 'Editorial', each of which
119
+ # should have extra thumbnails:
120
+
121
+ has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50',
122
+ :products => { :large_thumb => '169x169!', :zoomed => '500x500>' },
123
+ :editorials => { :fullsize => '150x100>' },
124
+ :users => { :avatar => '64x64!' } }
125
+
126
+ # JPEG qualities…
127
+
128
+ has_attachment :jpeg_quality => 75
129
+ has_attachment :jpeg_quality => 80 | ImageScience::JPEG_PROGRESSIVE
130
+ has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' },
131
+ :jpeg_quality => { nil => 75, :thumb => 90, :geometry => 90 }
132
+ has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' },
133
+ :jpeg_quality => { '<2000' => 90, '>=2000' => 75 }
134
+ }
135
+
80
136
  validates_as_attachment
81
137
  This method prevents files outside of the valid range (:min_size to :max_size, or the :size range) from being saved. It does not however, halt the upload of such files. They will be uploaded into memory regardless of size before validation.
82
-
138
+
83
139
  Example:
84
140
  validates_as_attachment
85
141
 
@@ -98,12 +154,12 @@ Fields for attachment_fu metadata tables...
98
154
  that reference images that will be thumbnailed:
99
155
  parent_id, :integer # id of parent image (on the same table, a self-referencing foreign-key).
100
156
  # Only populated if the current object is a thumbnail.
101
- thumbnail, :string # the 'type' of thumbnail this attachment record describes.
157
+ thumbnail, :string # the 'type' of thumbnail this attachment record describes.
102
158
  # Only populated if the current object is a thumbnail.
103
159
  # Usage:
104
160
  # [ In Model 'Avatar' ]
105
- # has_attachment :content_type => :image,
106
- # :storage => :file_system,
161
+ # has_attachment :content_type => :image,
162
+ # :storage => :file_system,
107
163
  # :max_size => 500.kilobytes,
108
164
  # :resize_to => '320x200>',
109
165
  # :thumbnails => { :small => '10x10>',
@@ -112,7 +168,7 @@ Fields for attachment_fu metadata tables...
112
168
  # @user.avatar.thumbnails.first.thumbnail #=> 'small'
113
169
  that reference files stored in the database (:db_file):
114
170
  db_file_id, :integer # id of the file in the database (foreign key)
115
-
171
+
116
172
  Field for attachment_fu db_files table:
117
173
  data, :binary # binary file data, for use in database file storage
118
174
 
@@ -126,7 +182,7 @@ There are two parts of the upload form that differ from typical usage.
126
182
  1. Include ':multipart => true' in the html options of the form_for tag.
127
183
  Example:
128
184
  <% form_for(:attachment_metadata, :url => { :action => "create" }, :html => { :multipart => true }) do |form| %>
129
-
185
+
130
186
  2. Use the file_field helper with :uploaded_data as the field name.
131
187
  Example:
132
188
  <%= form.file_field :uploaded_data %>
@@ -154,7 +210,7 @@ Example:
154
210
  def readme
155
211
  send_file '/path/to/readme.txt', :type => 'plain/text', :disposition => 'inline'
156
212
  end
157
-
213
+
158
214
  See the possible values for send_file for reference.
159
215
 
160
216
 
@@ -167,7 +223,7 @@ Example in controller:
167
223
  @attachable_file = AttachmentMetadataModel.new(params[:attachable])
168
224
  if @attachable_file.save
169
225
  flash[:notice] = 'Attachment was successfully created.'
170
- redirect_to attachable_url(@attachable_file)
226
+ redirect_to attachable_url(@attachable_file)
171
227
  else
172
228
  render :action => :new
173
229
  end
@@ -176,12 +232,12 @@ Example in controller:
176
232
  attachement_fu scripting
177
233
  ====================================
178
234
 
179
- You may wish to import a large number of images or attachments.
180
- The following example shows how to upload a file from a script.
235
+ You may wish to import a large number of images or attachments.
236
+ The following example shows how to upload a file from a script.
181
237
 
182
238
  #!/usr/bin/env ./script/runner
183
239
 
184
- # required to use ActionController::TestUploadedFile
240
+ # required to use ActionController::TestUploadedFile
185
241
  require 'action_controller'
186
242
  require 'action_controller/test_process.rb'
187
243
 
@@ -46,7 +46,20 @@ module Technoweenie # :nodoc:
46
46
  # * <tt>:max_size</tt> - Maximum size allowed. 1.megabyte is the default.
47
47
  # * <tt>:size</tt> - Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
48
48
  # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string.
49
- # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and RMagick resizing options.
49
+ # * <tt>:jpeg_quality</tt> - Used to provide explicit JPEG quality for thumbnail/resize saves. Can have multiple formats:
50
+ # * Integer from 0 (basically crap) to 100 (basically lossless, fat files).
51
+ # * When relying on ImageScience, you can also use one of its +JPEG_xxx+ constants for predefined ratios/settings.
52
+ # * You can also use a Hash, with keys being either thumbnail symbols (I repeat: _symbols_) or surface boundaries.
53
+ # A surface boundary is a string starting with either '<' or '>=', followed by a number of pixels. This lets you
54
+ # specify per-thumbnail or per-general-thumbnail-"size" JPEG qualities. (which can be useful when you have a
55
+ # _lot_ of thumbnail options). Surface example: +{ '<2000' => 90, '>=2000' => 75 }+.
56
+ # Defaults vary depending on the processor (ImageScience: 100%, Rmagick/MiniMagick/Gd2: 75%,
57
+ # CoreImage: auto-adjust). Note that only tdd-image_science (available from GitHub) currently supports explicit JPEG quality;
58
+ # the default image_science currently forces 100%.
59
+ # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and
60
+ # RMagick resizing options. If you have a polymorphic parent relationship, you can provide parent-type-specific
61
+ # thumbnail settings by using a pair with the type string as key and a Hash of thumbnail definitions as value.
62
+ # AttachmentFu automatically detects your first polymorphic +belongs_to+ relationship.
50
63
  # * <tt>:thumbnail_class</tt> - Set what class to use for thumbnails. This attachment class is used by default.
51
64
  # * <tt>:path_prefix</tt> - path to store the uploaded files. Uses public/#{table_name} by default for the filesystem, and just #{table_name}
52
65
  # for the S3 backend. Setting this sets the :storage to :file_system.
@@ -251,6 +264,13 @@ module Technoweenie # :nodoc:
251
264
  tmp.close
252
265
  end
253
266
  end
267
+
268
+ def polymorphic_relation_type_column
269
+ return @@_polymorphic_relation_type_column if defined?(@@_polymorphic_relation_type_column)
270
+ # Checked against ActiveRecord 1.15.6 through Edge @ 2009-08-05.
271
+ ref = reflections.values.detect { |r| r.macro == :belongs_to && r.options[:polymorphic] }
272
+ @@_polymorphic_relation_type_column = ref && ref.options[:foreign_type]
273
+ end
254
274
  end
255
275
 
256
276
  module InstanceMethods
@@ -453,7 +473,22 @@ module Technoweenie # :nodoc:
453
473
  if @saved_attachment
454
474
  if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
455
475
  temp_file = temp_path || create_temp_file
456
- attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
476
+ attachment_options[:thumbnails].each { |suffix, size|
477
+ if size.is_a?(Symbol)
478
+ parent_type = polymorphic_parent_type
479
+ next unless parent_type && [parent_type, parent_type.tableize].include?(suffix.to_s) && respond_to?(size)
480
+ size = send(size)
481
+ end
482
+ if size.is_a?(Hash)
483
+ parent_type = polymorphic_parent_type
484
+ next unless parent_type && [parent_type, parent_type.tableize].include?(suffix.to_s)
485
+ size.each { |ppt_suffix, ppt_size|
486
+ create_or_update_thumbnail(temp_file, ppt_suffix, *ppt_size)
487
+ }
488
+ else
489
+ create_or_update_thumbnail(temp_file, suffix, *size)
490
+ end
491
+ }
457
492
  end
458
493
  save_to_storage
459
494
  @temp_paths.clear
@@ -509,6 +544,27 @@ module Technoweenie # :nodoc:
509
544
  def destroy_thumbnails
510
545
  self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable?
511
546
  end
547
+
548
+ def polymorphic_parent_type
549
+ rel_name = self.class.polymorphic_relation_type_column
550
+ rel_name && send(rel_name)
551
+ end
552
+
553
+ def get_jpeg_quality(require_0_to_100 = true)
554
+ quality = attachment_options[:jpeg_quality]
555
+ if quality.is_a?(Hash)
556
+ sbl_quality = thumbnail && quality[thumbnail.to_sym]
557
+ sbl_quality = nil if sbl_quality && require_0_to_100 && !sbl_quality.to_i.between?(0, 100)
558
+ surface = (width || 1) * (height || 1)
559
+ size_quality = quality.detect { |k, v|
560
+ next unless k.is_a?(String) && k =~ /^(<|>=)(\d+)$/
561
+ op, threshold = $1, $2.to_i
562
+ surface.send(op, threshold)
563
+ }
564
+ quality = sbl_quality || size_quality && size_quality[1]
565
+ end
566
+ return quality && (!require_0_to_100 || quality.to_i.between?(0, 100)) ? quality : nil
567
+ end
512
568
  end
513
569
  end
514
570
  end
@@ -44,16 +44,17 @@ module Technoweenie # :nodoc:
44
44
  processor.render do |result|
45
45
  self.width = result.extent.size.width if respond_to?(:width)
46
46
  self.height = result.extent.size.height if respond_to?(:height)
47
-
48
- # Get a new temp_path for the image before saving
49
47
  out_file = random_tempfile_filename
50
48
  temp_paths.unshift Tempfile.new(out_file, Technoweenie::AttachmentFu.tempfile_path).path
51
49
  properties = nil
52
- jpeg = out_file =~ /\.jpe?g\z/i
53
- quality = attachment_options[:jpeg_quality]
54
- quality = quality ? quality.to_i : -1
55
- properties = { OSX::NSImageCompressionFactor => quality / 100.0 } if jpeg && quality.between?(0, 100)
50
+ # We don't check the source image since we're forcing the output to JPEG, apparently…
51
+ # Beware: apparently CoreImage only takes the percentage as a HINT, using a different actual quality…
52
+ quality = get_jpeg_quality
53
+ properties = { OSX::NSImageCompressionFactor => quality / 100.0 } if quality
56
54
  result.save(self.temp_path, OSX::NSJPEGFileType, properties)
55
+ #
56
+ # puts "#{self.temp_path} @ #{quality.inspect} -> #{%x(identify -format '%Q' "#{self.temp_path}")}"
57
+ #
57
58
  self.size = File.size(self.temp_path)
58
59
  end
59
60
  end
@@ -44,16 +44,13 @@ module Technoweenie # :nodoc:
44
44
  w, h = [img.width, img.height] / size.to_s
45
45
  img.resize!(w, h, false)
46
46
  end
47
+ self.width = img.width if respond_to?(:width)
48
+ self.height = img.height if respond_to?(:height)
47
49
  out_file = random_tempfile_filename
48
50
  temp_paths.unshift out_file
49
51
  jpeg = out_file =~ /\.jpe?g\z/i
50
- quality = attachment_options[:jpeg_quality]
51
- quality = quality ? quality.to_i : -1
52
- self.size = if jpeg && quality.between?(0, 100)
53
- img.export(self.temp_path, :quality => quality)
54
- else
55
- img.export(self.temp_path)
56
- end
52
+ quality = jpeg && get_jpeg_quality
53
+ self.size = img.export(self.temp_path, quality ? { :quality => quality } : {})
57
54
  end
58
55
 
59
56
  end
@@ -40,7 +40,7 @@ module Technoweenie # :nodoc:
40
40
  self.height = img.height if respond_to?(:height)
41
41
 
42
42
  # We don't check for quality being a 0-100 value as we also allow FreeImage JPEG_xxx constants.
43
- quality = attachment_options[:jpeg_quality]
43
+ quality = content_type[/jpe?g/i] && get_jpeg_quality(false)
44
44
  # Traditional ImageScience has a 1-arg save method, tdd-image_science has 1 mandatory + 1 optional
45
45
  if quality && img.method(:save).arity == -2
46
46
  img.save self.temp_path, quality
@@ -38,18 +38,13 @@ module Technoweenie # :nodoc:
38
38
  # Performs the actual resizing operation for a thumbnail
39
39
  def resize_image(img, size)
40
40
  size = size.first if size.is_a?(Array) && size.length == 1
41
+ format = img[:format]
41
42
  img.combine_options do |commands|
42
43
  commands.strip unless attachment_options[:keep_profile]
43
44
 
44
45
  # GIF is not handled correctly, so we move to PNG, as in other processors…
45
- format = img['format']
46
46
  if format == 'GIF'
47
47
  commands.format('PNG')
48
- elsif format == 'JPEG'
49
- quality = attachment_options[:jpeg_quality]
50
- quality = quality ? quality.to_i : -1
51
- # FIXME: this DOESN'T work, for whatever reason…
52
- commands.quality(quality) if quality.between?(0, 100)
53
48
  end
54
49
 
55
50
  if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
@@ -101,7 +96,16 @@ module Technoweenie # :nodoc:
101
96
  commands.resize(size.to_s)
102
97
  end
103
98
  end
99
+ dims = img[:dimensions]
100
+ self.width = dims[0] if respond_to?(:width)
101
+ self.height = dims[1] if respond_to?(:height)
102
+ # Has to be done this far so we get proper dimensions
103
+ if format == 'JPEG'
104
+ quality = get_jpeg_quality
105
+ img.quality(quality) if quality
106
+ end
104
107
  temp_paths.unshift img
108
+ self.size = File.size(self.temp_path)
105
109
  end
106
110
 
107
111
  def calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
@@ -49,15 +49,13 @@ module Technoweenie # :nodoc:
49
49
  else
50
50
  img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) }
51
51
  end
52
+ self.width = img.columns if respond_to?(:width)
53
+ self.height = img.rows if respond_to?(:height)
52
54
  img.strip! unless attachment_options[:keep_profile]
53
- opts = attachment_options
54
- out_file = write_to_temp_file(img.to_blob {
55
- qty = opts[:jpeg_quality]
56
- qty = qty ? qty.to_i : -1
57
- # FIXME: this DOESN'T work, for whatever reason…
58
- self.quality = qty if img.format.to_s[/JPEG/] && qty.between?(0, 100)
59
- })
55
+ quality = img.format.to_s[/JPEG/] && get_jpeg_quality
56
+ out_file = write_to_temp_file(img.to_blob { self.quality = quality if quality })
60
57
  temp_paths.unshift out_file
58
+ self.size = File.size(self.temp_path)
61
59
  end
62
60
  end
63
61
  end
data/test/basic_test.rb CHANGED
@@ -4,15 +4,15 @@ class BasicTest < Test::Unit::TestCase
4
4
  def test_should_set_default_min_size
5
5
  assert_equal 1, Attachment.attachment_options[:min_size]
6
6
  end
7
-
7
+
8
8
  def test_should_set_default_max_size
9
9
  assert_equal 1.megabyte, Attachment.attachment_options[:max_size]
10
10
  end
11
-
11
+
12
12
  def test_should_set_default_size
13
13
  assert_equal (1..1.megabyte), Attachment.attachment_options[:size]
14
14
  end
15
-
15
+
16
16
  def test_should_set_default_thumbnails_option
17
17
  assert_equal Hash.new, Attachment.attachment_options[:thumbnails]
18
18
  end
@@ -20,19 +20,19 @@ class BasicTest < Test::Unit::TestCase
20
20
  def test_should_set_default_thumbnail_class
21
21
  assert_equal Attachment, Attachment.attachment_options[:thumbnail_class]
22
22
  end
23
-
23
+
24
24
  def test_should_normalize_content_types_to_array
25
25
  assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type]
26
26
  assert_equal %w(pdf doc txt), DocAttachment.attachment_options[:content_type]
27
27
  assert_equal Technoweenie::AttachmentFu.content_types, ImageAttachment.attachment_options[:content_type]
28
28
  assert_equal ['pdf'] + Technoweenie::AttachmentFu.content_types, ImageOrPdfAttachment.attachment_options[:content_type]
29
29
  end
30
-
30
+
31
31
  def test_should_sanitize_content_type
32
32
  @attachment = Attachment.new :content_type => ' foo '
33
33
  assert_equal 'foo', @attachment.content_type
34
34
  end
35
-
35
+
36
36
  def test_should_sanitize_filenames
37
37
  @attachment = Attachment.new :filename => 'blah/foo.bar'
38
38
  assert_equal 'foo.bar', @attachment.filename
@@ -42,29 +42,79 @@ class BasicTest < Test::Unit::TestCase
42
42
 
43
43
  @attachment.filename = 'f o!O-.bar'
44
44
  assert_equal 'f_o_O-.bar', @attachment.filename
45
-
45
+
46
46
  @attachment.filename = 'sheeps_says_bææ'
47
47
  assert_equal 'sheeps_says_b__', @attachment.filename
48
48
 
49
49
  @attachment.filename = nil
50
50
  assert_nil @attachment.filename
51
51
  end
52
-
52
+
53
53
  def test_should_convert_thumbnail_name
54
54
  @attachment = FileAttachment.new :filename => 'foo.bar'
55
55
  assert_equal 'foo.bar', @attachment.thumbnail_name_for(nil)
56
56
  assert_equal 'foo.bar', @attachment.thumbnail_name_for('')
57
57
  assert_equal 'foo_blah.bar', @attachment.thumbnail_name_for(:blah)
58
58
  assert_equal 'foo_blah.blah.bar', @attachment.thumbnail_name_for('blah.blah')
59
-
59
+
60
60
  @attachment.filename = 'foo.bar.baz'
61
61
  assert_equal 'foo.bar_blah.baz', @attachment.thumbnail_name_for(:blah)
62
62
  end
63
-
63
+
64
64
  def test_should_require_valid_thumbnails_option
65
65
  klass = Class.new(ActiveRecord::Base)
66
66
  assert_raise ArgumentError do
67
67
  klass.has_attachment :thumbnails => []
68
68
  end
69
69
  end
70
+
71
+ class ::ImageWithPolymorphicThumbsAttachment
72
+ cattr_accessor :thumbnail_creations
73
+
74
+ def create_or_update_thumbnail(path, thumb, *size)
75
+ @@thumbnail_creations[thumb] = size.size == 1 ? size.first : size
76
+ end
77
+
78
+ def self.reset_creations
79
+ @@thumbnail_creations = {}
80
+ end
81
+ end
82
+
83
+ def test_should_handle_polymorphic_thumbnails_option
84
+ assert_polymorphic_thumb_creation nil,
85
+ :thumb => [50, 50], :geometry => 'x50'
86
+ assert_polymorphic_thumb_creation 'Product',
87
+ :thumb => [50, 50], :geometry => 'x50', :large_thumb => '169x169!', :zoomed => '500x500>'
88
+ assert_polymorphic_thumb_creation 'Editorial',
89
+ :thumb => [50, 50], :geometry => 'x50', :fullsize => '150x100>'
90
+ assert_polymorphic_thumb_creation 'User',
91
+ :thumb => [50, 50], :geometry => 'x50', :avatar => '64x64!'
92
+ end
93
+
94
+ def test_should_compute_per_thumbnail_jpeg_quality
95
+ assert_jpeg_quality :thumb, 90
96
+ assert_jpeg_quality :avatar, 85
97
+ assert_jpeg_quality :large, 75
98
+ assert_jpeg_quality :large, 0x200 | 75, false
99
+ assert_jpeg_quality nil, 75
100
+ end
101
+
102
+ private
103
+ def assert_jpeg_quality(thumbnail, quality, require_0_to_100 = true)
104
+ klass = ImageWithPerThumbJpegAttachment
105
+ w, h = if thumbnail
106
+ klass.attachment_options[:thumbnails][thumbnail].scan(/\d+/)
107
+ else
108
+ klass.attachment_options[:resize_to].scan(/\d+/)
109
+ end
110
+ attachment = klass.new(:thumbnail => thumbnail, :width => w, :height => h)
111
+ assert_equal quality, attachment.send(:get_jpeg_quality, require_0_to_100)
112
+ end
113
+
114
+ def assert_polymorphic_thumb_creation(parent, defs)
115
+ attachment_model ImageWithPolymorphicThumbsAttachment
116
+ attachment_model.reset_creations
117
+ attachment = upload_file :filename => '/files/rails.png', :imageable_type => parent.to_s.classify, :imageable_id => nil
118
+ assert_equal defs, attachment_model.thumbnail_creations
119
+ end
70
120
  end
@@ -44,6 +44,23 @@ class ImageWithThumbsAttachment < Attachment
44
44
  # end
45
45
  end
46
46
 
47
+ class ImageWithPerThumbJpegAttachment < Attachment
48
+ has_attachment :resize_to => '500x500!',
49
+ :thumbnails => { :thumb => '50x50!', :large => '300x300!', :avatar => '64x64!' },
50
+ :jpeg_quality => { :thumb => 90, '<5000' => 85, '>=5000' => 75, :large => 0x200 | 75 }
51
+ end
52
+
53
+ class ImageWithPolymorphicThumbsAttachment < Attachment
54
+ belongs_to :imageable, :polymorphic => true
55
+ has_attachment :thumbnails => {
56
+ :thumb => [50, 50],
57
+ :geometry => 'x50',
58
+ :products => { :large_thumb => '169x169!', :zoomed => '500x500>' },
59
+ :editorials => { :fullsize => '150x100>' },
60
+ 'User' => { :avatar => '64x64!' }
61
+ }
62
+ end
63
+
47
64
  class FileAttachment < ActiveRecord::Base
48
65
  has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
49
66
  validates_as_attachment
@@ -133,6 +150,14 @@ begin
133
150
  :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
134
151
  :jpeg_quality => 75
135
152
  end
153
+
154
+ class ImageScienceWithPerThumbJpegAttachment < ImageScienceAttachment
155
+ has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
156
+ :processor => :image_science,
157
+ :resize_to => '100x100',
158
+ :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
159
+ :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
160
+ end
136
161
  rescue MissingSourceFile
137
162
  puts $!.message
138
163
  puts "no ImageScience"
@@ -145,11 +170,18 @@ begin
145
170
  end
146
171
 
147
172
  class LowerQualityCoreImageAttachment < CoreImageAttachment
148
- set_table_name 'core_image_attachments'
149
173
  has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
150
174
  :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
151
175
  :jpeg_quality => 50
152
176
  end
177
+
178
+ class CoreImageWithPerThumbJpegAttachment < CoreImageAttachment
179
+ has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
180
+ :processor => :core_image,
181
+ :resize_to => '100x100',
182
+ :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
183
+ :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
184
+ end
153
185
  rescue MissingSourceFile
154
186
  puts $!.message
155
187
  puts "no CoreImage"
@@ -160,7 +192,7 @@ begin
160
192
  has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
161
193
  :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
162
194
  end
163
-
195
+
164
196
  class ImageThumbnailCrop < MiniMagickAttachment
165
197
  has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
166
198
  :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"}
@@ -194,13 +226,21 @@ begin
194
226
  end
195
227
  end
196
228
 
197
- class LowerQualityMiniMagickAttachment < MiniMagickAttachment
229
+ class LowerQualityMiniMagickAttachment < ActiveRecord::Base
198
230
  set_table_name 'mini_magick_attachments'
199
231
  has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
200
232
  :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
201
233
  :jpeg_quality => 50
202
234
  end
203
235
 
236
+ class MiniMagickWithPerThumbJpegAttachment < MiniMagickAttachment
237
+ has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
238
+ :processor => :mini_magick,
239
+ :resize_to => '100x100',
240
+ :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
241
+ :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
242
+ end
243
+
204
244
  rescue MissingSourceFile
205
245
  puts $!.message
206
246
  puts "no Mini Magick"
@@ -217,6 +257,14 @@ begin
217
257
  :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
218
258
  :jpeg_quality => 50
219
259
  end
260
+
261
+ class GD2WithPerThumbJpegAttachment < GD2Attachment
262
+ has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
263
+ :processor => :gd2,
264
+ :resize_to => '100x100',
265
+ :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
266
+ :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
267
+ end
220
268
  rescue MissingSourceFile
221
269
  puts $!.message
222
270
  puts "no GD2"
@@ -35,12 +35,20 @@ class CoreImageTest < Test::Unit::TestCase
35
35
 
36
36
  def test_should_handle_jpeg_quality
37
37
  attachment_model CoreImageAttachment
38
- attachment = upload_file :filename => '/files/rails.jpg'
38
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
39
39
  full_size = attachment.size
40
40
  attachment_model LowerQualityCoreImageAttachment
41
- attachment = upload_file :filename => '/files/rails.jpg'
41
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
42
42
  lq_size = attachment.size
43
43
  assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
44
+
45
+ # FIXME: wait for Marcus' reply to determine whether I can get exact-quality output or need to adjust for CoreImage.
46
+ # attachment_model CoreImageWithPerThumbJpegAttachment
47
+ # attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
48
+ # assert_file_jpeg_quality attachment, :thumb, 90
49
+ # assert_file_jpeg_quality attachment, :avatar, 80
50
+ # assert_file_jpeg_quality attachment, :editorial, 75
51
+ # assert_file_jpeg_quality attachment, nil, 75
44
52
  end
45
53
  else
46
54
  def test_flunk
@@ -29,12 +29,19 @@ class GD2Test < Test::Unit::TestCase
29
29
 
30
30
  def test_should_handle_jpeg_quality
31
31
  attachment_model GD2Attachment
32
- attachment = upload_file :filename => '/files/rails.jpg'
32
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
33
33
  full_size = attachment.size
34
34
  attachment_model LowerQualityGD2Attachment
35
- attachment = upload_file :filename => '/files/rails.jpg'
35
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
36
36
  lq_size = attachment.size
37
37
  assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
38
+
39
+ attachment_model GD2WithPerThumbJpegAttachment
40
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
41
+ assert_file_jpeg_quality attachment, :thumb, 90
42
+ assert_file_jpeg_quality attachment, :avatar, 80
43
+ assert_file_jpeg_quality attachment, :editorial, 75
44
+ assert_file_jpeg_quality attachment, nil, 75
38
45
  end
39
46
  else
40
47
  def test_flunk
@@ -28,16 +28,23 @@ class ImageScienceTest < Test::Unit::TestCase
28
28
  end
29
29
 
30
30
  def test_should_handle_jpeg_quality
31
- attachment = upload_file :filename => '/files/rails.jpg'
31
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
32
32
  full_size = attachment.size
33
33
  attachment_model ImageScienceLowerQualityAttachment
34
- attachment = upload_file :filename => '/files/rails.jpg'
34
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
35
35
  lq_size = attachment.size
36
36
  if ImageScience.instance_method(:save).arity == -2 # tdd-image_science: JPEG quality processing
37
37
  assert lq_size <= full_size * 0.75, 'Lower-quality JPEG filesize should be congruently smaller'
38
38
  else
39
39
  assert_equal full_size, lq_size, 'Unsupported lower-quality JPEG should yield exact same file size'
40
40
  end
41
+
42
+ attachment_model ImageScienceWithPerThumbJpegAttachment
43
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
44
+ assert_file_jpeg_quality attachment, :thumb, 90
45
+ assert_file_jpeg_quality attachment, :avatar, 80
46
+ assert_file_jpeg_quality attachment, :editorial, 75
47
+ assert_file_jpeg_quality attachment, nil, 75
41
48
  end
42
49
  else
43
50
  def test_flunk
@@ -66,13 +66,19 @@ class MiniMagickTest < Test::Unit::TestCase
66
66
 
67
67
  def test_should_handle_jpeg_quality
68
68
  attachment_model MiniMagickAttachment
69
- attachment = upload_file :filename => '/files/rails.jpg'
69
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
70
70
  full_size = attachment.size
71
71
  attachment_model LowerQualityMiniMagickAttachment
72
- attachment = upload_file :filename => '/files/rails.jpg'
72
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
73
73
  lq_size = attachment.size
74
- puts "FULL: #{full_size} - LQ: #{lq_size}"
75
74
  assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
75
+
76
+ attachment_model MiniMagickWithPerThumbJpegAttachment
77
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
78
+ assert_file_jpeg_quality attachment, :thumb, 90
79
+ assert_file_jpeg_quality attachment, :avatar, 80
80
+ assert_file_jpeg_quality attachment, :editorial, 75
81
+ assert_file_jpeg_quality attachment, nil, 75
76
82
  end
77
83
  else
78
84
  def test_flunk
@@ -251,13 +251,19 @@ class RmagickTest < Test::Unit::TestCase
251
251
 
252
252
  def test_should_handle_jpeg_quality
253
253
  attachment_model ImageWithThumbsAttachment
254
- attachment = upload_file :filename => '/files/rails.jpg'
254
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
255
255
  full_size = attachment.size
256
256
  attachment_model LowerQualityAttachment
257
- attachment = upload_file :filename => '/files/rails.jpg'
257
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
258
258
  lq_size = attachment.size
259
- puts "FULL: #{full_size} - LQ: #{lq_size}"
260
259
  assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
260
+
261
+ attachment_model ImageWithPerThumbJpegAttachment
262
+ attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
263
+ assert_file_jpeg_quality attachment, :thumb, 90
264
+ assert_file_jpeg_quality attachment, :avatar, 85
265
+ assert_file_jpeg_quality attachment, :large, 75
266
+ assert_file_jpeg_quality attachment, nil, 75
261
267
  end
262
268
  else
263
269
  def test_flunk
data/test/schema.rb CHANGED
@@ -2,6 +2,8 @@ ActiveRecord::Schema.define(:version => 0) do
2
2
  create_table :attachments, :force => true do |t|
3
3
  t.column :db_file_id, :integer
4
4
  t.column :parent_id, :integer
5
+ t.column :imageable_id, :integer
6
+ t.column :imageable_type, :string, :limit => 255
5
7
  t.column :thumbnail, :string
6
8
  t.column :filename, :string, :limit => 255
7
9
  t.column :content_type, :string, :limit => 255
data/test/test_helper.rb CHANGED
@@ -84,7 +84,9 @@ class Test::Unit::TestCase #:nodoc:
84
84
  protected
85
85
  def upload_file(options = {})
86
86
  use_temp_file options[:filename] do |file|
87
- att = attachment_model.create :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png')
87
+ opts = { :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png') }
88
+ opts.update(options.reject { |k, v| ![:imageable_type, :imageable_id].include?(k) })
89
+ att = attachment_model.create opts
88
90
  att.reload unless att.new_record?
89
91
  return att
90
92
  end
@@ -119,6 +121,27 @@ class Test::Unit::TestCase #:nodoc:
119
121
  end
120
122
  end
121
123
 
124
+ def assert_file_jpeg_quality(model, thumbnail, expected)
125
+ filename = if model.respond_to?(:full_filename)
126
+ model.full_filename(thumbnail)
127
+ else
128
+ thumb = thumbnail ? model.thumbnails.find(:first, :conditions => { :thumbnail => thumbnail.to_s }, :include => :db_file) : model
129
+ unless thumb && thumb.db_file && thumb.db_file.data && thumb.db_file.data.size > 0
130
+ STDERR.puts "Cannot find DB file data for thumbnail #{thumbnail.inspect} -> Aborting JPEG quality check."
131
+ return
132
+ end
133
+ result = Tempfile.new('dbfile_dump').path
134
+ File.open(result, 'wb') { |f| f.write(thumb.db_file.data) }
135
+ result
136
+ end
137
+ quality = %x(identify -format '%Q' "#{filename}" 2> /dev/null)
138
+ if $?.success?
139
+ assert_equal expected, quality.to_i, "Produced JPEG quality (thumbnail: #{thumbnail.inspect}) is incorrect."
140
+ else
141
+ STDERR.puts "ImageMagick's identify not found / not in PATH: can't quickly check produced image quality."
142
+ end
143
+ end
144
+
122
145
  def assert_not_created
123
146
  assert_created(0) { yield }
124
147
  end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tdd-attachment_fu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 9
8
+ - 9
9
+ - b
10
+ version: 0.9.9.b
5
11
  platform: ruby
6
12
  authors:
7
13
  - Rick Olson
@@ -10,7 +16,7 @@ autorequire:
10
16
  bindir: bin
11
17
  cert_chain: []
12
18
 
13
- date: 2009-08-04 00:00:00 -07:00
19
+ date: 2010-03-15 00:00:00 +01:00
14
20
  default_executable:
15
21
  dependencies: []
16
22
 
@@ -73,7 +79,8 @@ files:
73
79
  - vendor/red_artisan/core_image/filters/watermark.rb
74
80
  has_rdoc: true
75
81
  homepage: http://github.com/tdd/attachment_fu
76
- licenses:
82
+ licenses: []
83
+
77
84
  post_install_message:
78
85
  rdoc_options:
79
86
  - --inline-source
@@ -84,18 +91,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
91
  requirements:
85
92
  - - ">="
86
93
  - !ruby/object:Gem::Version
94
+ segments:
95
+ - 0
87
96
  version: "0"
88
- version:
89
97
  required_rubygems_version: !ruby/object:Gem::Requirement
90
98
  requirements:
91
99
  - - ">="
92
100
  - !ruby/object:Gem::Version
101
+ segments:
102
+ - 0
93
103
  version: "0"
94
- version:
95
104
  requirements: []
96
105
 
97
- rubyforge_project: attachment_fu
98
- rubygems_version: 1.3.5
106
+ rubyforge_project:
107
+ rubygems_version: 1.3.6
99
108
  signing_key:
100
109
  specification_version: 2
101
110
  summary: attachment_fu with more geometries, polymorphic-based settings and JPEG quality control.