Commit d948f58184c5d793bb95a2ea87eea9314c5649a4

Authored by Rodrigo Souto
1 parent 92bbe720

rails3: updating attachment_fu

Using https://github.com/pothoven/attachment_fu.git. This repository is
an integration of attachment_fu for ruby 1.9.2 and rails 3.2.
Showing 44 changed files with 2163 additions and 283 deletions   Show diff stats
vendor/plugins/attachment_fu/.gitignore
... ... @@ -1,2 +0,0 @@
1   -test/amazon_s3.yml
2   -test/debug.log
vendor/plugins/attachment_fu/CHANGELOG
  1 +* Aug 22, 2013 *
  2 +* Added cache-control header option and Ruby 1.9 fix for S3 from tricycle
  3 +
  4 +* Aug 21, 2013 *
  5 +* Added S3 :encrypted_storage option support from cschulte22
  6 +
  7 +* Jul 5, 2013
  8 +* Pull in changes from lchimonji10 to reformat README to RDoc format
  9 +
  10 +* Apr 10, 2013 *
  11 +* Ruby 2 compatibility fix
  12 +* Removed some lingering occurrences of RAILS_ROOT and RAILS_ENV
  13 +* clean up of gemspec file
  14 +* no longer package unnecessary files like the test files
  15 +
  16 +* Mar 12, 2013 *
  17 +* Pull in changes from https://github.com/aalong/attachment_fu to use Ruby 1.9.2 Tempfile naming strategy to fix conflicts with Sprockets (see https://github.com/aalong/attachment_fu/commit/938ec3b8597fbf82b1de8c98de12c4688463450a)
  18 +
  19 +* Feb 19, 2013 *
  20 +* Pull in changes from https://github.com/itkin/attachment_fu fork to fix mini magick
  21 +
  22 +* Nov 15, 2012 *
  23 +* Removed 'crop:' option as already included when prefixing geometry with a 'c'. Add note to docs.
  24 +* Added option to sharpen resized images ':sharpen_on_resize'
  25 +
  26 +* Oct 26, 2012 *
  27 +* Merged with https://github.com/tdd/attachment_fu fork to include GEM support for use in Rails 3.2, and bumped version to 3.2.x to indicate Rails 3.2 support.
  28 +
  29 +* Oct 25, 2012 *
  30 +* Fix to support Ruby 1.9.3
  31 +* Image cropping support (append 'crop:' to desired file size when resizing)
  32 +
  33 +* Mar 15, 2010 *
  34 +* Added a symbol syntax for parent-type-based size spec: calls the corresponding method on the current asset instance to
  35 + get a Hash of required thumbnails. Lets us dynamically specify what would otherwise be hard-coded, which is useful
  36 + when the set of thumbnails for a given parent type varies depending on the usage context.
  37 +
  38 +* Aug 6, 2009 *
  39 +* JPEG quality control finalized across processors (although CoreImage applies it in a rather fuzzy-logic way), with tests.
  40 +
  41 +* Aug 4, 2009 *
  42 +* Supports the :aspect/'!' geometry flag in all processors
  43 + (thanks to http://www.deepcalm.com/writing/cropped-thumbnails-in-attachment_fu-using-imagescience)
  44 +* JPEG quality control on thumbnailing/resizing (still buggy on Rmagick/MiniMagick though).
  45 +* Moves from GIF to PNG regardless of the source file extension's case (used to require lowercase)
  46 +* Auto-orients image (if EXIF suggests it) prior to processing with RMagickProcessor
  47 +* Fixes non-image upload tests (both regular files and Merb files)
  48 +* Fixes obsolete failures on RMagick tests (aspect_ratio tested but not initialized anymore due to new callback architecture
  49 +
1 50 * Apr 17 2008 *
2 51 * amazon_s3.yml is now passed through ERB before being passed to AWS::S3 [François Beausoleil]
3 52  
... ...
vendor/plugins/attachment_fu/Gemfile 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +source 'http://rubygems.org'
  2 +
  3 +
  4 +group :test, :development do
  5 + gem 'rails', '~> 3.2'
  6 + gem 'sqlite3'
  7 + gem 'pothoven-attachment_fu', :path => '.'
  8 + gem 'rmagick'
  9 + gem 'core_image'
  10 + gem 'mini_magick'
  11 + gem 'aws-s3', :require => 'aws/s3'
  12 + gem 'test-unit'
  13 +end
... ...
vendor/plugins/attachment_fu/Gemfile.lock 0 → 100644
... ... @@ -0,0 +1,109 @@
  1 +PATH
  2 + remote: .
  3 + specs:
  4 + pothoven-attachment_fu (3.2.10)
  5 +
  6 +GEM
  7 + remote: http://rubygems.org/
  8 + specs:
  9 + actionmailer (3.2.14)
  10 + actionpack (= 3.2.14)
  11 + mail (~> 2.5.4)
  12 + actionpack (3.2.14)
  13 + activemodel (= 3.2.14)
  14 + activesupport (= 3.2.14)
  15 + builder (~> 3.0.0)
  16 + erubis (~> 2.7.0)
  17 + journey (~> 1.0.4)
  18 + rack (~> 1.4.5)
  19 + rack-cache (~> 1.2)
  20 + rack-test (~> 0.6.1)
  21 + sprockets (~> 2.2.1)
  22 + activemodel (3.2.14)
  23 + activesupport (= 3.2.14)
  24 + builder (~> 3.0.0)
  25 + activerecord (3.2.14)
  26 + activemodel (= 3.2.14)
  27 + activesupport (= 3.2.14)
  28 + arel (~> 3.0.2)
  29 + tzinfo (~> 0.3.29)
  30 + activeresource (3.2.14)
  31 + activemodel (= 3.2.14)
  32 + activesupport (= 3.2.14)
  33 + activesupport (3.2.14)
  34 + i18n (~> 0.6, >= 0.6.4)
  35 + multi_json (~> 1.0)
  36 + arel (3.0.2)
  37 + aws-s3 (0.6.3)
  38 + builder
  39 + mime-types
  40 + xml-simple
  41 + builder (3.0.4)
  42 + core_image (0.0.3.5)
  43 + erubis (2.7.0)
  44 + hike (1.2.3)
  45 + i18n (0.6.5)
  46 + journey (1.0.4)
  47 + json (1.8.0)
  48 + mail (2.5.4)
  49 + mime-types (~> 1.16)
  50 + treetop (~> 1.4.8)
  51 + mime-types (1.24)
  52 + mini_magick (3.6.0)
  53 + subexec (~> 0.2.1)
  54 + multi_json (1.7.9)
  55 + polyglot (0.3.3)
  56 + rack (1.4.5)
  57 + rack-cache (1.2)
  58 + rack (>= 0.4)
  59 + rack-ssl (1.3.3)
  60 + rack
  61 + rack-test (0.6.2)
  62 + rack (>= 1.0)
  63 + rails (3.2.14)
  64 + actionmailer (= 3.2.14)
  65 + actionpack (= 3.2.14)
  66 + activerecord (= 3.2.14)
  67 + activeresource (= 3.2.14)
  68 + activesupport (= 3.2.14)
  69 + bundler (~> 1.0)
  70 + railties (= 3.2.14)
  71 + railties (3.2.14)
  72 + actionpack (= 3.2.14)
  73 + activesupport (= 3.2.14)
  74 + rack-ssl (~> 1.3.2)
  75 + rake (>= 0.8.7)
  76 + rdoc (~> 3.4)
  77 + thor (>= 0.14.6, < 2.0)
  78 + rake (10.1.0)
  79 + rdoc (3.12.2)
  80 + json (~> 1.4)
  81 + rmagick (2.13.2)
  82 + sprockets (2.2.2)
  83 + hike (~> 1.2)
  84 + multi_json (~> 1.0)
  85 + rack (~> 1.0)
  86 + tilt (~> 1.1, != 1.3.0)
  87 + sqlite3 (1.3.8)
  88 + subexec (0.2.3)
  89 + test-unit (2.5.5)
  90 + thor (0.18.1)
  91 + tilt (1.4.1)
  92 + treetop (1.4.15)
  93 + polyglot
  94 + polyglot (>= 0.3.1)
  95 + tzinfo (0.3.37)
  96 + xml-simple (1.1.2)
  97 +
  98 +PLATFORMS
  99 + ruby
  100 +
  101 +DEPENDENCIES
  102 + aws-s3
  103 + core_image
  104 + mini_magick
  105 + pothoven-attachment_fu!
  106 + rails (~> 3.2)
  107 + rmagick
  108 + sqlite3
  109 + test-unit
... ...
vendor/plugins/attachment_fu/LICENSE 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2009 rick olson
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
... ...
vendor/plugins/attachment_fu/README
... ... @@ -9,12 +9,13 @@ attachment_fu functionality
9 9  
10 10 attachment_fu facilitates file uploads in Ruby on Rails. There are a few storage options for the actual file data, but the plugin always at a minimum stores metadata for each file in the database.
11 11  
12   -There are three storage options for files uploaded through attachment_fu:
  12 +There are four storage options for files uploaded through attachment_fu:
13 13 File system
14 14 Database file
15 15 Amazon S3
  16 + Rackspace (Mosso) Cloud Files
16 17  
17   -Each method of storage many options associated with it that will be covered in the following section. Something to note, however, is that the Amazon S3 storage requires you to modify config/amazon_s3.yml and the Database file storage requires an extra table.
  18 +Each method of storage many options associated with it that will be covered in the following section. Something to note, however, is that the Amazon S3 storage requires you to modify config/amazon_s3.yml, the Rackspace Cloud Files storage requires you to modify config/rackspace_cloudfiles.yml, and the Database file storage requires an extra table.
18 19  
19 20  
20 21 attachment_fu models
... ... @@ -41,13 +42,18 @@ has_attachment(options = {})
41 42 # This option need only be included if you want thumbnailing.
42 43 :thumbnail_class # Set which model class to use for thumbnails.
43 44 # This current attachment class is used by default.
44   - :path_prefix # path to store the uploaded files.
45   - # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the S3 backend.
  45 + :path_prefix # Path to store the uploaded files in.
  46 + # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the S3 and Cloud Files backend.
46 47 # Setting this sets the :storage to :file_system.
  48 + :partition # Whether to partiton files in directories like /0000/0001/image.jpg. Default is true. Only applicable to the :file_system backend.
47 49 :storage # Specifies the storage system to use..
48   - # Defaults to :db_file. Options are :file_system, :db_file, and :s3.
  50 + # Defaults to :db_file. Options are :file_system, :db_file, :s3, and :cloud_files.
  51 + :cloudfront # If using S3 for storage, this option allows for serving the files via Amazon CloudFront.
  52 + # Defaults to false.
49 53 :processor # Sets the image processor to use for resizing of the attached image.
50 54 # Options include ImageScience, Rmagick, and MiniMagick. Default is whatever is installed.
  55 + :uuid_primary_key # If your model's primary key is a 128-bit UUID in hexadecimal format, then set this to true.
  56 + :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.
51 57  
52 58  
53 59 Examples:
... ... @@ -60,10 +66,12 @@ has_attachment(options = {})
60 66 has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
61 67 has_attachment :storage => :file_system, :path_prefix => 'public/files'
62 68 has_attachment :storage => :file_system, :path_prefix => 'public/files',
63   - :content_type => :image, :resize_to => [50,50]
  69 + :content_type => :image, :resize_to => [50,50], :partition => false
64 70 has_attachment :storage => :file_system, :path_prefix => 'public/files',
65 71 :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
66 72 has_attachment :storage => :s3
  73 + has_attachment :store => :s3, :cloudfront => true
  74 + has_attachment :storage => :cloud_files
67 75  
68 76 validates_as_attachment
69 77 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.
... ... @@ -119,7 +127,7 @@ There are two parts of the upload form that differ from typical usage.
119 127 Example:
120 128 <%= form.file_field :uploaded_data %>
121 129  
122   -Displaying uploaded images is made easy by the public_filename method of the ActiveRecord attachment objects using file system and s3 storage.
  130 +Displaying uploaded images is made easy by the public_filename method of the ActiveRecord attachment objects using file system, s3, and Cloud Files storage.
123 131  
124 132 public_filename(thumbnail = nil)
125 133 Returns the public path to the file. If a thumbnail prefix is specified it will return the public file path to the corresponding thumbnail.
... ... @@ -160,3 +168,26 @@ Example in controller:
160 168 render :action => :new
161 169 end
162 170 end
  171 +
  172 +attachement_fu scripting
  173 +====================================
  174 +
  175 +You may wish to import a large number of images or attachments.
  176 +The following example shows how to upload a file from a script.
  177 +
  178 +#!/usr/bin/env ./script/runner
  179 +
  180 +# required to use ActionController::TestUploadedFile
  181 +require 'action_controller'
  182 +require 'action_controller/test_process.rb'
  183 +
  184 +path = "./public/images/x.jpg"
  185 +
  186 +# mimetype is a string like "image/jpeg". One way to get the mimetype for a given file on a UNIX system
  187 +# mimetype = `file -ib #{path}`.gsub(/\n/,"")
  188 +
  189 +mimetype = "image/jpeg"
  190 +
  191 +# This will "upload" the file at path and create the new model.
  192 +@attachable = AttachmentMetadataModel.new(:uploaded_data => ActionController::TestUploadedFile.new(path, mimetype))
  193 +@attachable.save
... ...
vendor/plugins/attachment_fu/README.rdoc 0 → 100644
... ... @@ -0,0 +1,352 @@
  1 += attachment-fu
  2 +
  3 +attachment_fu is a plugin by Rick Olson (aka technoweenie
  4 +http://techno-weenie.net) and is the successor to acts_as_attachment. To get a
  5 +basic run-through of its capabilities, check out {Mike Clark's
  6 +tutorial}[http://clarkware.com/cgi/blosxom/2007/02/24#FileUploadFu].
  7 +
  8 += attachment_fu functionality
  9 +
  10 +attachment_fu facilitates file uploads in Ruby on Rails. There are a few
  11 +storage options for the actual file data, but the plugin always at a minimum
  12 +stores metadata for each file in the database.
  13 +
  14 +There are four storage options for files uploaded through attachment_fu:
  15 +
  16 +* File system
  17 +* Database file
  18 +* Amazon S3
  19 +* Rackspace (Mosso) Cloud Files
  20 +
  21 +Each method of storage many options associated with it that will be covered in
  22 +the following section. Something to note, however, is that the Amazon S3 storage
  23 +requires you to modify +config/amazon_s3.yml+, the Rackspace Cloud Files storage
  24 +requires you to modify +config/rackspace_cloudfiles.yml+, and the Database file
  25 +storage requires an extra table.
  26 +
  27 += attachment_fu models
  28 +
  29 +For all three of these storage options a table of metadata is required. This
  30 +table will contain information about the file (hence the 'meta') and its
  31 +location. This table has no restrictions on naming, unlike the extra table
  32 +required for database storage, which must have a table name of +db_files+ (and
  33 +by convention a model of +DbFile+).
  34 +
  35 +Two methods are available to models: +has_attachment+ and
  36 ++validates_as_attachment+.
  37 +
  38 +== has_attachment(options = {})
  39 +
  40 +This method accepts the options in a hash:
  41 +
  42 +[:content_type]
  43 + Allowed content types.
  44 +
  45 + By default, all content types are allowed. Use +:image+ to allow all
  46 + standard image types.
  47 +
  48 +[:min_size]
  49 + Minimum file size.
  50 +
  51 + By default, set to +1.byte+.
  52 +
  53 +[:max_size]
  54 + Maximum file size.
  55 +
  56 + By default, set to +1.megabyte+.
  57 +
  58 +[:size]
  59 + Minimum and maximum file size.
  60 +
  61 + By default, set to +1..1.megabyte+. Overrides +:min_size+ and
  62 + +:max_size+.
  63 +
  64 +[:resize_to]
  65 + Used by RMagick.
  66 +
  67 + Tells RMagick how to resize images. Pass either an array specifying
  68 + width and height or a geometry string. Prefixing the geometry string
  69 + with a 'c' will crop the image to the specified size.
  70 +
  71 +[:sharpen_on_resize]
  72 + Used by RMagick.
  73 +
  74 + If set to true, images are sharpened after being resized.
  75 +
  76 +[:thumbnails]
  77 + A set of thumbnails to generate.
  78 +
  79 + This accepts a hash of filename suffixes and RMagick resizing options. This
  80 + option need only be included if you want thumbnailing.
  81 +
  82 + If you have a polymorphic parent relationship, you can provide
  83 + parent-type-specific thumbnail settings by using a pair with the type string
  84 + as key and a Hash of thumbnail definitions, or a method symbol, as value.
  85 + The method symbol will call the named method in order to get a
  86 + dynamically-built Hash of thumbnail definitions, which gives you full
  87 + flexibility. AttachmentFu automatically detects your first polymorphic
  88 + +belongs_to+ relationship.
  89 +
  90 +[:thumbnail_class]
  91 + Which model class to use for thumbnails.
  92 +
  93 + By default, the current attachment class is used.
  94 +
  95 +[:jpeg_quality]
  96 + JPEG quality settings for thumbnail resizes.
  97 +
  98 + Arguments can be in multiple formats:
  99 +
  100 + * Integer from 0 (basically crap) to 100 (basically lossless, fat files).
  101 +
  102 + * When relying on tdd-image_science, you can also use one of its +JPEG_xxx+
  103 + constants for predefined ratios/settings.
  104 +
  105 + * You can also use a Hash, with keys being either thumbnail symbols (I
  106 + repeat: _symbols_) or surface boundaries. A surface boundary is a string
  107 + starting with either '<' or '>=', followed by a number of pixels. This
  108 + lets you specify per-thumbnail or per-general-thumbnail-"size" JPEG
  109 + qualities. (which can be useful when you have a _lot_ of thumbnail
  110 + options). Surface example: <code>{'<2000' => 90, '>=2000' => 75}</code>.
  111 +
  112 + Defaults vary depending on the processor (ImageScience: 100%,
  113 + Rmagick/MiniMagick/Gd2: 75%, CoreImage: auto-adjust). Note that only
  114 + tdd-image_science (available from GitHub) currently supports explicit JPEG
  115 + quality; the default image_science currently forces 100%.
  116 +
  117 +[:path_prefix]
  118 + Path to store the uploaded files in. Uses <code>public/#{table_name}</code>
  119 + by default for the filesystem, and just <code>#{table_name}</code> for the
  120 + S3 and Cloud Files backend. Setting this sets the +:storage+ to
  121 + +:file_system+.
  122 +
  123 +[:partition]
  124 + Whether to partiton files in directories like +/0000/0001/image.jpg+.
  125 + Default is true. Only applicable to the +:file_system+ backend.
  126 +
  127 +[:storage]
  128 + Specifies the storage system to use. Defaults to +:db_file+. Options are
  129 + +:file_system+, +:db_file+, +:s3+, and +:cloud_files+.
  130 +
  131 +[:cloudfront]
  132 + If using S3 for storage, this option allows for serving the files via Amazon
  133 + CloudFront. Defaults to false.
  134 +
  135 +[:processor]
  136 + Sets the image processor to use for resizing of the attached image. Options
  137 + include ImageScience, Rmagick, MiniMagick, Gd2 and CoreImage. Default is
  138 + whatever is installed.
  139 +
  140 +[:uuid_primary_key]
  141 + If your model's primary key is a 128-bit UUID in hexadecimal format, then
  142 + set this to true.
  143 +
  144 +[:association_options]
  145 + attachment_fu automatically defines associations with thumbnails with
  146 + +has_many+ and +belongs_to+. If there are any additional options that you
  147 + want to pass to these methods, then specify them here.
  148 +
  149 +Examples:
  150 +
  151 + has_attachment(content_type: 'application/pdf')
  152 + has_attachment(
  153 + content_type: ['application/pdf', 'application/msword', 'text/plain']
  154 + )
  155 + has_attachment(content_type: ['application/pdf', :image], resize_to: 'x50')
  156 + has_attachment(content_type: :image, resize_to: [50,50])
  157 + has_attachment(max_size: 1.kilobyte)
  158 + has_attachment(size: 1.megabyte..2.megabytes)
  159 + has_attachment(storage: :cloud_files)
  160 + has_attachment(storage: :file_system, path_prefix: 'public/files')
  161 + has_attachment(
  162 + storage: :file_system,
  163 + path_prefix: 'public/files',
  164 + content_type: :image,
  165 + resize_to: [50, 50],
  166 + partition: false
  167 + )
  168 + has_attachment(
  169 + storage: :file_system,
  170 + path_prefix: 'public/files',
  171 + thumbnails: {thumb: [50, 50], geometry: 'x50'}
  172 + )
  173 + has_attachment(storage: :s3)
  174 + has_attachment(store: :s3, cloudfront: true)
  175 + has_attachment(thumbnails: {thumb: [50, 50], geometry: 'x50'})
  176 +
  177 + # Let's say we have a polymorphic belongs_to, e.g. called 'imageable', where
  178 + # imageable_type (or whatever the :foreign_type option was set to) can be,
  179 + # among other things, 'Product', 'User' or 'Editorial', each of which should
  180 + # have extra thumbnails:
  181 +
  182 + has_attachment(thumbnails: {
  183 + editorials: {fullsize: '150x100>'},
  184 + geometry: 'x50',
  185 + products: {large_thumb: '169x169!', zoomed: '500x500>'},
  186 + thumb: [50, 50],
  187 + users: {avatar: '64x64!'}
  188 + })
  189 +
  190 + # JPEG qualities…
  191 +
  192 + has_attachment(jpeg_quality: 75)
  193 + has_attachment(jpeg_quality: 80 | ImageScience::JPEG_PROGRESSIVE)
  194 + has_attachment(
  195 + thumbnails: {thumb: [50, 50], geometry: 'x50'},
  196 + jpeg_quality: {'<2000' => 90, '>=2000' => 75}
  197 + )
  198 + has_attachment(
  199 + thumbnails: {thumb: [50, 50], geometry: 'x50'},
  200 + jpeg_quality: {nil => 75, thumb: 90, geometry: 90}
  201 + )
  202 +
  203 +== validates_as_attachment
  204 +
  205 +This method prevents files outside of the valid range (+:min_size+ to
  206 ++:max_size+, or the +:size+ range) from being saved. It does not however, halt
  207 +the upload of such files. They will be uploaded into memory regardless of size
  208 +before validation.
  209 +
  210 +To perform this validation, simply add +validates_as_attachment+ to your model.
  211 +
  212 += attachment_fu migrations
  213 +
  214 +Fields for attachment_fu metadata tables…
  215 +
  216 +In general:
  217 +
  218 + size, :integer # file size in bytes
  219 + content_type, :string # mime type, ex: application/mp3
  220 + filename, :string # sanitized filename
  221 +
  222 +That reference images:
  223 +
  224 + height, :integer # in pixels
  225 + width, :integer # in pixels
  226 +
  227 +That reference images that will be thumbnailed:
  228 +
  229 + parent_id, :integer # id of parent image (on the same table, a
  230 + # self-referencing foreign-key). Only populated if
  231 + # the current object is a thumbnail.
  232 + thumbnail, :string # The type of thumbnail this attachment record
  233 + # describes. Only populated if the current object is
  234 + # a thumbnail. Example:
  235 + #
  236 + # (In Model 'Avatar')
  237 + # has_attachment(
  238 + # :content_type => :image,
  239 + # :storage => :file_system,
  240 + # :max_size => 500.kilobytes,
  241 + # :resize_to => '320x200>',
  242 + # :thumbnails => {
  243 + # :small => '10x10>',
  244 + # :thumb => '100x100>'
  245 + # }
  246 + # )
  247 + #
  248 + # (Elsewhere)
  249 + # @user.avatar.thumbnails.first.thumbnail # => 'small'
  250 + #
  251 + db_file_id, :integer # ID of the file in the database (foreign key) that
  252 + # reference files stored in the database (:db_file).
  253 +
  254 +Field for attachment_fu +db_files+ table:
  255 +
  256 + data, :binary # binary file data, for use in database file storage
  257 +
  258 += attachment_fu views
  259 +
  260 +There are two main views tasks that will be directly affected by attachment_fu:
  261 +upload forms and displaying uploaded images.
  262 +
  263 +There are two parts of the upload form that differ from typical usage.
  264 +
  265 +1. Include <code>multipart: true</code> in the html options of the +form_for+
  266 + tag. Example:
  267 +
  268 + <%=
  269 + form_for(
  270 + :attachment_metadata,
  271 + url: {action: "create"},
  272 + html: {multipart: true}
  273 + ) do |form|
  274 + %>
  275 +
  276 +2. Use the +file_field+ helper with +:uploaded_data+ as the field name. Example:
  277 +
  278 + <%= form.file_field(:uploaded_data) %>
  279 +
  280 +Displaying uploaded images is made easy by the +public_filename+ method of the
  281 +ActiveRecord attachment objects using file system, s3, and Cloud Files storage.
  282 +
  283 +== public_filename(thumbnail = nil)
  284 +
  285 +Returns the public path to the file. If a thumbnail prefix is specified it will
  286 +return the public file path to the corresponding thumbnail. Examples:
  287 +
  288 + attachment_obj.public_filename #=> /attachments/2/file.jpg
  289 + attachment_obj.public_filename(:thumb) #=> /attachments/2/file_thumb.jpg
  290 + attachment_obj.public_filename(:small) #=> /attachments/2/file_small.jpg
  291 +
  292 +When serving files from database storage, doing more than simply downloading the
  293 +file is beyond the scope of this document.
  294 +
  295 += attachment_fu controllers
  296 +
  297 +There are two considerations to take into account when using attachment_fu in
  298 +controllers.
  299 +
  300 +The first is when the files have no publicly accessible path and need to be
  301 +downloaded through an action. Example:
  302 +
  303 + def readme
  304 + send_file(
  305 + '/path/to/readme.txt',
  306 + type: 'plain/text',
  307 + disposition: 'inline'
  308 + )
  309 + end
  310 +
  311 +See the possible values for +send_file+ for reference.
  312 +
  313 +The second is when saving the file when submitted from a form. Example:
  314 +
  315 +In a view:
  316 +
  317 + <%= form.file_field(:attachable, :uploaded_data) %>
  318 +
  319 +In a controller:
  320 +
  321 + def create
  322 + @attachable_file = AttachmentMetadataModel.new(params[:attachable])
  323 + if @attachable_file.save
  324 + flash[:notice] = 'Attachment was successfully created.'
  325 + redirect_to(attachable_url(@attachable_file))
  326 + else
  327 + redirect_to(action: 'new')
  328 + end
  329 + end
  330 +
  331 += attachment_fu scripting
  332 +
  333 +You may wish to import a large number of images or attachments. The following
  334 +example shows how to upload a file from a script.
  335 +
  336 + #!/usr/bin/env ./script/runner
  337 +
  338 + # required to use ActionController::TestUploadedFile
  339 + require 'action_controller'
  340 + require 'action_controller/test_process.rb'
  341 +
  342 + path = "./public/images/x.jpg"
  343 +
  344 + # `mimetype` is a string like "image/jpeg". One way to get the mimetype for
  345 + # a given file on a UNIX system: mimetype = `file -ib #{path}`.gsub(/\n/,"")
  346 + mimetype = "image/jpeg"
  347 +
  348 + # This will "upload" the file at path and create the new model.
  349 + @attachable = AttachmentMetadataModel.new(
  350 + uploaded_data: ActionController::TestUploadedFile.new(path, mimetype)
  351 + )
  352 + @attachable.save
... ...
vendor/plugins/attachment_fu/Rakefile
1 1 require 'rake'
2 2 require 'rake/testtask'
3   -require 'rake/rdoctask'
  3 +require 'rdoc/task'
4 4  
5 5 desc 'Default: run unit tests.'
6 6 task :default => :test
... ... @@ -16,6 +16,7 @@ desc &#39;Generate documentation for the attachment_fu plugin.&#39;
16 16 Rake::RDocTask.new(:rdoc) do |rdoc|
17 17 rdoc.rdoc_dir = 'rdoc'
18 18 rdoc.title = 'ActsAsAttachment'
19   - rdoc.rdoc_files.include('README')
  19 + rdoc.options << '--line-numbers --inline-source'
  20 + rdoc.rdoc_files.include('README.rdoc')
20 21 rdoc.rdoc_files.include('lib/**/*.rb')
21 22 end
... ...
vendor/plugins/attachment_fu/amazon_s3.yml.tpl
... ... @@ -2,13 +2,16 @@ development:
2 2 bucket_name: appname_development
3 3 access_key_id:
4 4 secret_access_key:
  5 + distribution_domain: XXXX.cloudfront.net
5 6  
6 7 test:
7 8 bucket_name: appname_test
8 9 access_key_id:
9 10 secret_access_key:
  11 + distribution_domain: XXXX.cloudfront.net
10 12  
11 13 production:
12 14 bucket_name: appname
13 15 access_key_id:
14 16 secret_access_key:
  17 + distribution_domain: XXXX.cloudfront.net
... ...
vendor/plugins/attachment_fu/attachment_fu.gemspec 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +# -*- encoding: utf-8 -*-
  2 +
  3 +Gem::Specification.new do |s|
  4 + s.name = %q{pothoven-attachment_fu}
  5 + s.authors = ["Rick Olson", "Steven Pothoven"]
  6 + s.summary = %q{attachment_fu as a gem}
  7 + s.description = %q{This is a fork of Rick Olson's attachment_fu adding Ruby 1.9 and Rails 3.2 support as well as some other enhancements.}
  8 + s.email = %q{steven@pothoven.net}
  9 + s.homepage = %q{http://github.com/pothoven/attachment_fu}
  10 + s.version = "3.2.10"
  11 + s.date = %q{2013-08-22}
  12 +
  13 + s.files = Dir.glob("{lib,vendor}/**/*") + %w( CHANGELOG LICENSE README.rdoc amazon_s3.yml.tpl rackspace_cloudfiles.yml.tpl )
  14 + s.extra_rdoc_files = ["README.rdoc"]
  15 + s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
  16 + s.require_paths = ["lib"]
  17 + s.rubyforge_project = "nowarning"
  18 + s.rubygems_version = %q{1.3.5}
  19 +
  20 + if s.respond_to? :specification_version then
  21 + s.specification_version = 2
  22 + end
  23 +end
... ...
vendor/plugins/attachment_fu/config/database.yml 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +test:
  2 + adapter: sqlite3
  3 + database: ":memory:"
... ...
vendor/plugins/attachment_fu/config/environment.rb 0 → 100644
vendor/plugins/attachment_fu/init.rb
1   -require 'tempfile'
2   -
3   -Tempfile.class_eval do
4   - # overwrite so tempfiles use the extension of the basename. important for rmagick and image science
5   - def make_tmpname(basename, n)
6   - ext = nil
7   - sprintf("%s%d-%d%s", basename.to_s.gsub(/\.\w+$/) { |s| ext = s; '' }, $$, n, ext)
8   - end
9   -end
10   -
11 1 require 'geometry'
12 2 ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
13 3 Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
... ...
vendor/plugins/attachment_fu/install.rb
... ... @@ -2,4 +2,6 @@ require &#39;fileutils&#39;
2 2  
3 3 s3_config = File.dirname(__FILE__) + '/../../../config/amazon_s3.yml'
4 4 FileUtils.cp File.dirname(__FILE__) + '/amazon_s3.yml.tpl', s3_config unless File.exist?(s3_config)
5   -puts IO.read(File.join(File.dirname(__FILE__), 'README'))
6 5 \ No newline at end of file
  6 +cloudfiles_config = File.dirname(__FILE__) + '/../../../config/rackspace_cloudfiles.yml'
  7 +FileUtils.cp File.dirname(__FILE__) + '/rackspace_cloudfiles.yml.tpl', cloudfiles_config unless File.exist?(cloudfiles_config)
  8 +puts IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
... ...
vendor/plugins/attachment_fu/lib/geometry.rb
1 1 # This Geometry class was yanked from RMagick. However, it lets ImageMagick handle the actual change_geometry.
2   -# Use #new_dimensions_for to get new dimensons
  2 +# Use #new_dimensions_for to get new dimensions
3 3 # Used so I can use spiffy RMagick geometry strings with ImageScience
4 4 class Geometry
5   - # ! and @ are removed until support for them is added
6   - FLAGS = ['', '%', '<', '>']#, '!', '@']
  5 + # @ is removed until support for them is added
  6 + FLAGS = ['', '%', '<', '>', '!']#, '@']
7 7 RFLAGS = { '%' => :percent,
8 8 '!' => :aspect,
9 9 '<' => :>,
... ... @@ -25,7 +25,7 @@ class Geometry
25 25 end
26 26  
27 27 # Construct an object from a geometry string
28   - RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
  28 + RE = /\A(\d*)(?:x(\d+)?)?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
29 29  
30 30 def self.from_s(str)
31 31 raise(ArgumentError, "no geometry string specified") unless str
... ... @@ -59,6 +59,9 @@ class Geometry
59 59 scale_y = @height.zero? ? @width : @height
60 60 new_width = scale_x.to_f * (orig_width.to_f / 100.0)
61 61 new_height = scale_y.to_f * (orig_height.to_f / 100.0)
  62 + when :aspect
  63 + new_width = @width unless @width.nil?
  64 + new_height = @height unless @height.nil?
62 65 when :<, :>, nil
63 66 scale_factor =
64 67 if new_width.zero? || new_height.zero?
... ... @@ -76,7 +79,7 @@ class Geometry
76 79 new_height = orig_height if @flag && orig_height.send(@flag, new_height)
77 80 end
78 81  
79   - [new_width, new_height].collect! { |v| v.round }
  82 + [new_width, new_height].collect! { |v| [v.round, 1].max }
80 83 end
81 84 end
82 85  
... ... @@ -90,4 +93,4 @@ class Array
90 93 geometry = Geometry.from_s(geometry) if geometry.is_a?(String)
91 94 geometry.new_dimensions_for first, last
92 95 end
93   -end
94 96 \ No newline at end of file
  97 +end
... ...
vendor/plugins/attachment_fu/lib/pothoven-attachment_fu.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class Engine < Rails::Engine
  2 + # Mimic old vendored plugin behavior, attachment_fu/lib is autoloaded.
  3 + config.autoload_paths << File.expand_path("..", __FILE__)
  4 +
  5 + initializer "attachment_fu" do
  6 + require 'geometry'
  7 +
  8 + ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
  9 + Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
  10 + FileUtils.mkdir_p Technoweenie::AttachmentFu.tempfile_path
  11 + end
  12 +end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb
... ... @@ -2,7 +2,37 @@ module Technoweenie # :nodoc:
2 2 module AttachmentFu # :nodoc:
3 3 @@default_processors = %w(ImageScience Rmagick MiniMagick Gd2 CoreImage)
4 4 @@tempfile_path = File.join(Rails.root, 'tmp', 'attachment_fu')
5   - @@content_types = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg']
  5 + @@content_types = [
  6 + 'image/jpeg',
  7 + 'image/pjpeg',
  8 + 'image/jpg',
  9 + 'image/gif',
  10 + 'image/png',
  11 + 'image/x-png',
  12 + 'image/jpg',
  13 + 'image/x-ms-bmp',
  14 + 'image/bmp',
  15 + 'image/x-bmp',
  16 + 'image/x-bitmap',
  17 + 'image/x-xbitmap',
  18 + 'image/x-win-bitmap',
  19 + 'image/x-windows-bmp',
  20 + 'image/ms-bmp',
  21 + 'application/bmp',
  22 + 'application/x-bmp',
  23 + 'application/x-win-bitmap',
  24 + 'application/preview',
  25 + 'image/jp_',
  26 + 'application/jpg',
  27 + 'application/x-jpg',
  28 + 'image/pipeg',
  29 + 'image/vnd.swiftview-jpeg',
  30 + 'image/x-xbitmap',
  31 + 'application/png',
  32 + 'application/x-png',
  33 + 'image/gi_',
  34 + 'image/x-citrix-pjpeg'
  35 + ]
6 36 mattr_reader :content_types, :tempfile_path, :default_processors
7 37 mattr_writer :tempfile_path
8 38  
... ... @@ -15,12 +45,32 @@ module Technoweenie # :nodoc:
15 45 # * <tt>:min_size</tt> - Minimum size allowed. 1 byte is the default.
16 46 # * <tt>:max_size</tt> - Maximum size allowed. 1.megabyte is the default.
17 47 # * <tt>:size</tt> - Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
18   - # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string.
19   - # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and RMagick resizing options.
  48 + # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string. Prefix geometry string with 'c' to crop image, ex. 'c100x100'
  49 + # * <tt>:sharpen_on_resize</tt> - When using RMagick, setting to true will sharpen images after resizing.
  50 + # * <tt>:jpeg_quality</tt> - Used to provide explicit JPEG quality for thumbnail/resize saves. Can have multiple formats:
  51 + # * Integer from 0 (basically crap) to 100 (basically lossless, fat files).
  52 + # * When relying on ImageScience, you can also use one of its +JPEG_xxx+ constants for predefined ratios/settings.
  53 + # * You can also use a Hash, with keys being either thumbnail symbols (I repeat: _symbols_) or surface boundaries.
  54 + # A surface boundary is a string starting with either '<' or '>=', followed by a number of pixels. This lets you
  55 + # specify per-thumbnail or per-general-thumbnail-"size" JPEG qualities. (which can be useful when you have a
  56 + # _lot_ of thumbnail options). Surface example: +{ '<2000' => 90, '>=2000' => 75 }+.
  57 + # Defaults vary depending on the processor (ImageScience: 100%, Rmagick/MiniMagick/Gd2: 75%,
  58 + # CoreImage: auto-adjust). Note that only tdd-image_science (available from GitHub) currently supports explicit JPEG quality;
  59 + # the default image_science currently forces 100%.
  60 + # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and
  61 + # RMagick resizing options. If you have a polymorphic parent relationship, you can provide parent-type-specific
  62 + # thumbnail settings by using a pair with the type string as key and a Hash of thumbnail definitions as value.
  63 + # AttachmentFu automatically detects your first polymorphic +belongs_to+ relationship.
20 64 # * <tt>:thumbnail_class</tt> - Set what class to use for thumbnails. This attachment class is used by default.
21 65 # * <tt>:path_prefix</tt> - path to store the uploaded files. Uses public/#{table_name} by default for the filesystem, and just #{table_name}
22 66 # for the S3 backend. Setting this sets the :storage to :file_system.
  67 +
23 68 # * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system.
  69 + # * <tt>:cloundfront</tt> - Set to true if you are using S3 storage and want to serve the files through CloudFront. You will need to
  70 + # set a distribution domain in the amazon_s3.yml config file. Defaults to false
  71 + # * <tt>:bucket_key</tt> - Use this to specify a different bucket key other than :bucket_name in the amazon_s3.yml file. This allows you to use
  72 + # different buckets for different models. An example setting would be :image_bucket and the you would need to define the name of the corresponding
  73 + # bucket in the amazon_s3.yml file.
24 74  
25 75 # * <tt>:keep_profile</tt> By default image EXIF data will be stripped to minimize image size. For small thumbnails this proivides important savings. Picture quality is not affected. Set to false if you want to keep the image profile as is. ImageScience will allways keep EXIF data.
26 76 #
... ... @@ -46,7 +96,9 @@ module Technoweenie # :nodoc:
46 96 options[:thumbnails] ||= {}
47 97 options[:thumbnail_class] ||= self
48 98 options[:s3_access] ||= :public_read
49   - options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? Technoweenie::AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?
  99 + options[:cloudfront] ||= false
  100 + options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? ::Technoweenie::AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?
  101 + options[:cache_control] ||= "max-age=315360000" # 10 years
50 102  
51 103 unless options[:thumbnails].is_a?(Hash)
52 104 raise ArgumentError, ":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }"
... ... @@ -65,25 +117,33 @@ module Technoweenie # :nodoc:
65 117 attachment_options[:storage] ||= parent_options[:storage]
66 118 attachment_options[:path_prefix] ||= attachment_options[:file_system_path]
67 119 if attachment_options[:path_prefix].nil?
68   - attachment_options[:path_prefix] = attachment_options[:storage] == :s3 ? table_name : File.join("public", table_name)
  120 + attachment_options[:path_prefix] = case attachment_options[:storage]
  121 + when :s3 then table_name
  122 + when :cloud_files then table_name
  123 + else File.join("public", table_name)
  124 + end
69 125 end
70 126 attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
71 127  
72   - with_options :foreign_key => 'parent_id' do |m|
  128 + association_options = { :foreign_key => 'parent_id' }
  129 + if attachment_options[:association_options]
  130 + association_options.merge!(attachment_options[:association_options])
  131 + end
  132 + with_options(association_options) do |m|
73 133 m.has_many :thumbnails, :class_name => "::#{attachment_options[:thumbnail_class]}"
74 134 m.belongs_to :parent, :class_name => "::#{base_class}" unless options[:thumbnails].empty?
75 135 end
76 136  
77   - storage_mod = Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend")
  137 + storage_mod = ::Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend")
78 138 include storage_mod unless included_modules.include?(storage_mod)
79 139  
80 140 case attachment_options[:processor]
81 141 when :none, nil
82   - processors = Technoweenie::AttachmentFu.default_processors.dup
  142 + processors = ::Technoweenie::AttachmentFu.default_processors.dup
83 143 begin
84 144 if processors.any?
85   - attachment_options[:processor] = "#{processors.first}Processor"
86   - processor_mod = Technoweenie::AttachmentFu::Processors.const_get(attachment_options[:processor])
  145 + attachment_options[:processor] = processors.first
  146 + processor_mod = ::Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
87 147 include processor_mod unless included_modules.include?(processor_mod)
88 148 end
89 149 rescue Object, Exception
... ... @@ -94,7 +154,7 @@ module Technoweenie # :nodoc:
94 154 end
95 155 else
96 156 begin
97   - processor_mod = Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
  157 + processor_mod = ::Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
98 158 include processor_mod unless included_modules.include?(processor_mod)
99 159 rescue Object, Exception
100 160 raise unless load_related_exception?($!)
... ... @@ -118,7 +178,7 @@ module Technoweenie # :nodoc:
118 178 end
119 179  
120 180 module ClassMethods
121   - delegate :content_types, :to => Technoweenie::AttachmentFu
  181 + delegate :content_types, :to => ::Technoweenie::AttachmentFu
122 182  
123 183 # Performs common validations for attachment models.
124 184 def validates_as_attachment
... ... @@ -135,12 +195,12 @@ module Technoweenie # :nodoc:
135 195 base.class_attribute :attachment_options
136 196 base.before_destroy :destroy_thumbnails
137 197 base.before_validation :set_size_from_temp_path
138   - base.after_save :after_process_attachment
139 198 base.after_destroy :destroy_file
140 199 base.after_validation :process_attachment
141   - if defined?(::ActiveSupport::Callbacks)
142   - base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved
143   - end
  200 + base.after_save :after_process_attachment
  201 + #if defined?(::ActiveSupport::Callbacks)
  202 + # base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved
  203 + #end
144 204 end
145 205  
146 206 unless defined?(::ActiveSupport::Callbacks)
... ... @@ -192,7 +252,7 @@ module Technoweenie # :nodoc:
192 252  
193 253 # Copies the given file path to a new tempfile, returning the closed tempfile.
194 254 def copy_to_temp_file(file, temp_base_name)
195   - returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
  255 + Tempfile.new(temp_base_name, ::Technoweenie::AttachmentFu.tempfile_path).tap do |tmp|
196 256 tmp.close
197 257 FileUtils.cp file, tmp.path
198 258 end
... ... @@ -200,12 +260,19 @@ module Technoweenie # :nodoc:
200 260  
201 261 # Writes the given data to a new tempfile, returning the closed tempfile.
202 262 def write_to_temp_file(data, temp_base_name)
203   - returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
  263 + Tempfile.new(temp_base_name, ::Technoweenie::AttachmentFu.tempfile_path).tap do |tmp|
204 264 tmp.binmode
205 265 tmp.write data
206 266 tmp.close
207 267 end
208 268 end
  269 +
  270 + def polymorphic_relation_type_column
  271 + return @@_polymorphic_relation_type_column if defined?(@@_polymorphic_relation_type_column)
  272 + # Checked against ActiveRecord 1.15.6 through Edge @ 2009-08-05.
  273 + ref = reflections.values.detect { |r| r.macro == :belongs_to && r.options[:polymorphic] }
  274 + @@_polymorphic_relation_type_column = ref && ref.options[:foreign_type]
  275 + end
209 276 end
210 277  
211 278 module InstanceMethods
... ... @@ -220,11 +287,7 @@ module Technoweenie # :nodoc:
220 287  
221 288 # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute.
222 289 def thumbnailable?
223   - image? && !is_thumbnail?
224   - end
225   -
226   - def is_thumbnail?
227   - (thumbnail_class == self.class) && !(respond_to?(:parent_id) && parent_id.nil?)
  290 + image? && respond_to?(:parent_id) && parent_id.nil?
228 291 end
229 292  
230 293 # Returns the class used to create new thumbnails for this attachment.
... ... @@ -234,26 +297,33 @@ module Technoweenie # :nodoc:
234 297  
235 298 # Gets the thumbnail name for a filename. 'foo.jpg' becomes 'foo_thumbnail.jpg'
236 299 def thumbnail_name_for(thumbnail = nil)
237   - return filename if thumbnail.blank?
  300 + if thumbnail.blank?
  301 + if filename.nil?
  302 + return ''
  303 + else
  304 + return filename
  305 + end
  306 + end
  307 +
238 308 ext = nil
239 309 basename = filename.gsub /\.\w+$/ do |s|
240 310 ext = s; ''
241 311 end
242 312 # ImageScience doesn't create gif thumbnails, only pngs
243   - ext.sub!(/gif$/, 'png') if attachment_options[:processor] == "ImageScience"
  313 + ext.sub!(/gif$/i, 'png') if attachment_options[:processor] == "ImageScience"
244 314 "#{basename}_#{thumbnail}#{ext}"
245 315 end
246 316  
247 317 # Creates or updates the thumbnail for the current attachment.
248 318 def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
249 319 thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
250   - returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
251   - thumb.attributes = {
  320 + find_or_initialize_thumbnail(file_name_suffix).tap do |thumb|
  321 + thumb.temp_paths.unshift temp_file
  322 + thumb.send(:assign_attributes, {
252 323 :content_type => content_type,
253 324 :filename => thumbnail_name_for(file_name_suffix),
254   - :temp_path => temp_file,
255 325 :thumbnail_resize_options => size
256   - }
  326 + }, :without_protection => true)
257 327 callback_with_args :before_thumbnail_saved, thumb
258 328 thumb.save!
259 329 end
... ... @@ -276,7 +346,7 @@ module Technoweenie # :nodoc:
276 346  
277 347 # Returns true if the attachment data will be written to the storage system on the next save
278 348 def save_attachment?
279   - File.file?(temp_path.to_s)
  349 + File.file?(temp_path.class == String ? temp_path : temp_path.to_filename)
280 350 end
281 351  
282 352 # nil placeholder in case this field is used in a form.
... ... @@ -294,14 +364,21 @@ module Technoweenie # :nodoc:
294 364 #
295 365 # TODO: Allow it to work with Merb tempfiles too.
296 366 def uploaded_data=(file_data)
297   - return nil if file_data.nil? || file_data.size == 0
298   - self.content_type = file_data.content_type
299   - self.filename = file_data.original_filename if respond_to?(:filename)
  367 + if file_data.respond_to?(:content_type)
  368 + return nil if file_data.size == 0
  369 + self.content_type = file_data.content_type
  370 + self.filename = file_data.original_filename if respond_to?(:filename)
  371 + else
  372 + return nil if file_data.blank? || file_data['size'] == 0
  373 + self.content_type = file_data['content_type']
  374 + self.filename = file_data['filename']
  375 + file_data = file_data['tempfile']
  376 + end
300 377 if file_data.is_a?(StringIO)
301 378 file_data.rewind
302   - self.temp_data = file_data.read
  379 + set_temp_data file_data.read
303 380 else
304   - self.temp_path = file_data
  381 + file_data.respond_to?(:tempfile) ? self.temp_paths.unshift( file_data.tempfile.path ) : self.temp_paths.unshift( file_data.path )
305 382 end
306 383 end
307 384  
... ... @@ -320,22 +397,14 @@ module Technoweenie # :nodoc:
320 397 [] : [copy_to_temp_file(full_filename)])
321 398 end
322 399  
323   - # Adds a new temp_path to the array. This should take a string or a Tempfile. This class makes no
324   - # attempt to remove the files, so Tempfiles should be used. Tempfiles remove themselves when they go out of scope.
325   - # You can also use string paths for temporary files, such as those used for uploaded files in a web server.
326   - def temp_path=(value)
327   - temp_paths.unshift value
328   - temp_path
329   - end
330   -
331 400 # Gets the data from the latest temp file. This will read the file into memory.
332 401 def temp_data
333 402 save_attachment? ? File.read(temp_path) : nil
334 403 end
335 404  
336 405 # Writes the given data to a Tempfile and adds it to the collection of temp files.
337   - def temp_data=(data)
338   - self.temp_path = write_to_temp_file data unless data.nil?
  406 + def set_temp_data(data)
  407 + temp_paths.unshift write_to_temp_file data unless data.nil?
339 408 end
340 409  
341 410 # Copies the given file to a randomly named Tempfile.
... ... @@ -364,17 +433,20 @@ module Technoweenie # :nodoc:
364 433 protected
365 434 # Generates a unique filename for a Tempfile.
366 435 def random_tempfile_filename
367   - "#{rand Time.now.to_i}#{filename || 'attachment'}"
  436 + base_filename = filename ? filename.gsub(/\.\w+$/, '') : 'attachment'
  437 + ext = filename.slice(/\.\w+$/)
  438 + ["#{rand Time.now.to_i}#{base_filename}", ext || '']
368 439 end
369 440  
370 441 def sanitize_filename(filename)
371   - returning filename.strip do |name|
  442 + return unless filename
  443 + filename.strip.tap do |name|
372 444 # NOTE: File.basename doesn't work right with Windows paths on Unix
373 445 # get only the filename, not the whole path
374 446 name.gsub! /^.*(\\|\/)/, ''
375 447  
376 448 # Finally, replace all non alphanumeric, underscore or periods with underscore
377   - name.gsub! /[^\w\.\-]/, '_'
  449 + name.gsub! /[^A-Za-z0-9\.\-]/, '_'
378 450 end
379 451 end
380 452  
... ... @@ -387,13 +459,17 @@ module Technoweenie # :nodoc:
387 459 def attachment_attributes_valid?
388 460 [:size, :content_type].each do |attr_name|
389 461 enum = attachment_options[attr_name]
390   - errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
  462 + if Object.const_defined?(:I18n) # Rails >= 2.2
  463 + errors.add attr_name, I18n.translate("activerecord.errors.messages.inclusion", attr_name => enum) unless enum.nil? || enum.include?(send(attr_name))
  464 + else
  465 + errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
  466 + end
391 467 end
392 468 end
393 469  
394 470 # Initializes a new thumbnail with the given suffix.
395 471 def find_or_initialize_thumbnail(file_name_suffix)
396   - thumbnail_class.columns.map(&:name).include?('parent_id') ?
  472 + respond_to?(:parent_id) ?
397 473 thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
398 474 thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
399 475 end
... ... @@ -406,29 +482,51 @@ module Technoweenie # :nodoc:
406 482 # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
407 483 def after_process_attachment
408 484 if @saved_attachment
409   - if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank?
  485 + if respond_to?(:process_attachment_with_processing, true) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
410 486 temp_file = temp_path || create_temp_file
411   - attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
  487 + attachment_options[:thumbnails].each { |suffix, size|
  488 + if size.is_a?(Symbol)
  489 + parent_type = polymorphic_parent_type
  490 + next unless parent_type && [parent_type, parent_type.tableize].include?(suffix.to_s) && respond_to?(size)
  491 + size = send(size)
  492 + end
  493 + if size.is_a?(Hash)
  494 + parent_type = polymorphic_parent_type
  495 + next unless parent_type && [parent_type, parent_type.tableize].include?(suffix.to_s)
  496 + size.each { |ppt_suffix, ppt_size|
  497 + create_or_update_thumbnail(temp_file, ppt_suffix, *ppt_size)
  498 + }
  499 + else
  500 + create_or_update_thumbnail(temp_file, suffix, *size)
  501 + end
  502 + }
412 503 end
413 504 save_to_storage
414 505 @temp_paths.clear
415 506 @saved_attachment = nil
416   - callback :after_attachment_saved
  507 + #callback :after_attachment_saved
  508 + callback_with_args :after_attachment_saved, nil
417 509 end
418 510 end
419 511  
420 512 # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options.
421 513 def resize_image_or_thumbnail!(img)
422   - if !is_thumbnail? && attachment_options[:resize_to] # parent image
  514 + if (!respond_to?(:parent_id) || parent_id.nil?) && attachment_options[:resize_to] # parent image
423 515 resize_image(img, attachment_options[:resize_to])
424 516 elsif thumbnail_resize_options # thumbnail
425 517 resize_image(img, thumbnail_resize_options)
426 518 end
427 519 end
428 520  
  521 + if defined?(Rails) && Rails::VERSION::MAJOR >= 3
  522 + def callback_with_args(method, arg = self)
  523 + if respond_to?(method)
  524 + send(method, arg)
  525 + end
  526 + end
429 527 # Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self.
430 528 # Only accept blocks, however
431   - if ActiveSupport.const_defined?(:Callbacks)
  529 + elsif ActiveSupport.const_defined?(:Callbacks)
432 530 # Rails 2.1 and beyond!
433 531 def callback_with_args(method, arg = self)
434 532 notify(method)
... ... @@ -464,6 +562,27 @@ module Technoweenie # :nodoc:
464 562 def destroy_thumbnails
465 563 self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable?
466 564 end
  565 +
  566 + def polymorphic_parent_type
  567 + rel_name = self.class.polymorphic_relation_type_column
  568 + rel_name && send(rel_name)
  569 + end
  570 +
  571 + def get_jpeg_quality(require_0_to_100 = true)
  572 + quality = attachment_options[:jpeg_quality]
  573 + if quality.is_a?(Hash)
  574 + sbl_quality = thumbnail && quality[thumbnail.to_sym]
  575 + sbl_quality = nil if sbl_quality && require_0_to_100 && !sbl_quality.to_i.between?(0, 100)
  576 + surface = (width || 1) * (height || 1)
  577 + size_quality = quality.detect { |k, v|
  578 + next unless k.is_a?(String) && k =~ /^(<|>=)(\d+)$/
  579 + op, threshold = $1, $2.to_i
  580 + surface.send(op, threshold)
  581 + }
  582 + quality = sbl_quality || size_quality && size_quality[1]
  583 + end
  584 + return quality && (!require_0_to_100 || quality.to_i.between?(0, 100)) ? quality : nil
  585 + end
467 586 end
468 587 end
469 588 end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb 0 → 100644
... ... @@ -0,0 +1,211 @@
  1 +module Technoweenie # :nodoc:
  2 + module AttachmentFu # :nodoc:
  3 + module Backends
  4 + # = CloudFiles Storage Backend
  5 + #
  6 + # Enables use of {Rackspace Cloud Files}[http://www.mosso.com/cloudfiles.jsp] as a storage mechanism
  7 + #
  8 + # Based heavily on the Amazon S3 backend.
  9 + #
  10 + # == Requirements
  11 + #
  12 + # Requires the {Cloud Files Gem}[http://www.mosso.com/cloudfiles.jsp] by Rackspace
  13 + #
  14 + # == Configuration
  15 + #
  16 + # Configuration is done via <tt>Rails.root.to_s/config/rackspace_cloudfiles.yml</tt> and is loaded according to the <tt>#{Rails.env}</tt>.
  17 + # The minimum connection options that you must specify are a container name, your Mosso login name and your Mosso API key.
  18 + # You can sign up for Cloud Files and get access keys by visiting https://www.mosso.com/buy.htm
  19 + #
  20 + # Example configuration (Rails.root.to_s/config/rackspace_cloudfiles.yml)
  21 + #
  22 + # development:
  23 + # container_name: appname_development
  24 + # username: <your key>
  25 + # api_key: <your key>
  26 + #
  27 + # test:
  28 + # container_name: appname_test
  29 + # username: <your key>
  30 + # api_key: <your key>
  31 + #
  32 + # production:
  33 + # container_name: appname
  34 + # username: <your key>
  35 + # apik_key: <your key>
  36 + #
  37 + # You can change the location of the config path by passing a full path to the :cloudfiles_config_path option.
  38 + #
  39 + # has_attachment :storage => :cloud_files, :cloudfiles_config_path => (Rails.root.to_s + '/config/mosso.yml')
  40 + #
  41 + # === Required configuration parameters
  42 + #
  43 + # * <tt>:username</tt> - The username for your Rackspace Cloud (Mosso) account. Provided by Rackspace.
  44 + # * <tt>:secret_access_key</tt> - The api key for your Rackspace Cloud account. Provided by Rackspace.
  45 + # * <tt>:container_name</tt> - The name of a container in your Cloud Files account.
  46 + #
  47 + # If any of these required arguments is missing, a AuthenticationException will be raised from CloudFiles::Connection.
  48 + #
  49 + # == Usage
  50 + #
  51 + # To specify Cloud Files as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:cloud_files/tt>.
  52 + #
  53 + # class Photo < ActiveRecord::Base
  54 + # has_attachment :storage => :cloud_files
  55 + # end
  56 + #
  57 + # === Customizing the path
  58 + #
  59 + # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
  60 + # in Cloud Files object names (and urls) that look like: http://:server/:container_name/:table_name/:id/:filename with :table_name
  61 + # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
  62 + # option:
  63 + #
  64 + # class Photo < ActiveRecord::Base
  65 + # has_attachment :storage => :cloud_files, :path_prefix => 'my/custom/path'
  66 + # end
  67 + #
  68 + # Which would result in public URLs like <tt>http(s)://:server/:container_name/my/custom/path/:id/:filename.</tt>
  69 + #
  70 + # === Permissions
  71 + #
  72 + # File permisisons are determined by the permissions of the container. At present, the options are public (and distributed
  73 + # by the Limelight CDN), and private (only available to your login)
  74 + #
  75 + # === Other options
  76 + #
  77 + # Of course, all the usual configuration options apply, such as content_type and thumbnails:
  78 + #
  79 + # class Photo < ActiveRecord::Base
  80 + # has_attachment :storage => :cloud_files, :content_type => ['application/pdf', :image], :resize_to => 'x50'
  81 + # has_attachment :storage => :cloud_files, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  82 + # end
  83 + #
  84 + # === Accessing Cloud Files URLs
  85 + #
  86 + # You can get an object's public URL using the cloudfiles_url accessor. For example, assuming that for your postcard app
  87 + # you had a container name like 'postcard_world_development', and an attachment model called Photo:
  88 + #
  89 + # @postcard.cloudfiles_url # => http://cdn.cloudfiles.mosso.com/c45182/uploaded_files/20/london.jpg
  90 + #
  91 + # The resulting url is in the form: http://:server/:container_name/:table_name/:id/:file.
  92 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  93 + #
  94 + # Additionally, you can get an object's base path relative to the container root using
  95 + # <tt>base_path</tt>:
  96 + #
  97 + # @photo.file_base_path # => uploaded_files/20
  98 + #
  99 + # And the full path (including the filename) using <tt>full_filename</tt>:
  100 + #
  101 + # @photo.full_filename # => uploaded_files/20/london.jpg
  102 + #
  103 + # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the container name as part of the path.
  104 + # You can retrieve the container name using the <tt>container_name</tt> method.
  105 + module CloudFileBackend
  106 + class RequiredLibraryNotFoundError < StandardError; end
  107 + class ConfigFileNotFoundError < StandardError; end
  108 +
  109 + def self.included(base) #:nodoc:
  110 + mattr_reader :container_name, :cloudfiles_config
  111 +
  112 + begin
  113 + require 'cloudfiles'
  114 + rescue LoadError
  115 + raise RequiredLibraryNotFoundError.new('CloudFiles could not be loaded')
  116 + end
  117 +
  118 + begin
  119 + @@cloudfiles_config_path = base.attachment_options[:cloudfiles_config_path] || (Rails.root.to_s + '/config/rackspace_cloudfiles.yml')
  120 + @@cloudfiles_config = @@cloudfiles_config = YAML.load(ERB.new(File.read(@@cloudfiles_config_path)).result)[Rails.env].symbolize_keys
  121 + rescue
  122 + #raise ConfigFileNotFoundError.new('File %s not found' % @@cloudfiles_config_path)
  123 + end
  124 +
  125 + @@container_name = @@cloudfiles_config[:container_name]
  126 + @@cf = CloudFiles::Connection.new(@@cloudfiles_config[:username], @@cloudfiles_config[:api_key])
  127 + @@container = @@cf.container(@@container_name)
  128 +
  129 + base.before_update :rename_file
  130 + end
  131 +
  132 + # Overwrites the base filename writer in order to store the old filename
  133 + def filename=(value)
  134 + @old_filename = filename unless filename.nil? || @old_filename
  135 + write_attribute :filename, sanitize_filename(value)
  136 + end
  137 +
  138 + # The attachment ID used in the full path of a file
  139 + def attachment_path_id
  140 + ((respond_to?(:parent_id) && parent_id) || id).to_s
  141 + end
  142 +
  143 + # The pseudo hierarchy containing the file relative to the container name
  144 + # Example: <tt>:table_name/:id</tt>
  145 + def base_path
  146 + File.join(attachment_options[:path_prefix], attachment_path_id)
  147 + end
  148 +
  149 + # The full path to the file relative to the container name
  150 + # Example: <tt>:table_name/:id/:filename</tt>
  151 + def full_filename(thumbnail = nil)
  152 + File.join(base_path, thumbnail_name_for(thumbnail))
  153 + end
  154 +
  155 + # All public objects are accessible via a GET request to the Cloud Files servers. You can generate a
  156 + # url for an object using the cloudfiles_url method.
  157 + #
  158 + # @photo.cloudfiles_url
  159 + #
  160 + # The resulting url is in the CDN URL for the object
  161 + #
  162 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  163 + #
  164 + # If you are trying to get the URL for a nonpublic container, nil will be returned.
  165 + def cloudfiles_url(thumbnail = nil)
  166 + if @@container.public?
  167 + File.join(@@container.cdn_url, full_filename(thumbnail))
  168 + else
  169 + nil
  170 + end
  171 + end
  172 + alias :public_filename :cloudfiles_url
  173 +
  174 + def create_temp_file
  175 + write_to_temp_file current_data
  176 + end
  177 +
  178 + def current_data
  179 + @@container.get_object(full_filename).data
  180 + end
  181 +
  182 + protected
  183 + # Called in the after_destroy callback
  184 + def destroy_file
  185 + @@container.delete_object(full_filename)
  186 + end
  187 +
  188 + def rename_file
  189 + # Cloud Files doesn't rename right now, so we'll just nuke.
  190 + return unless @old_filename && @old_filename != filename
  191 +
  192 + old_full_filename = File.join(base_path, @old_filename)
  193 + @@container.delete_object(old_full_filename)
  194 +
  195 + @old_filename = nil
  196 + true
  197 + end
  198 +
  199 + def save_to_storage
  200 + if save_attachment?
  201 + @object = @@container.create_object(full_filename)
  202 + @object.write((temp_path ? File.open(temp_path) : temp_data))
  203 + end
  204 +
  205 + @old_filename = nil
  206 + true
  207 + end
  208 + end
  209 + end
  210 + end
  211 +end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/file_system_backend.rb
  1 +require 'fileutils'
  2 +require 'digest/sha2'
  3 +
1 4 module Technoweenie # :nodoc:
2 5 module AttachmentFu # :nodoc:
3 6 module Backends
... ... @@ -6,12 +9,12 @@ module Technoweenie # :nodoc:
6 9 def self.included(base) #:nodoc:
7 10 base.before_update :rename_file
8 11 end
9   -
  12 +
10 13 # Gets the full path to the filename in this format:
11 14 #
12 15 # # This assumes a model name like MyModel
13   - # # public/#{table_name} is the default filesystem path
14   - # Rails.root/public/my_models/5/blah.jpg
  16 + # # public/#{table_name} is the default filesystem path
  17 + # #{Rails.root}/public/my_models/5/blah.jpg
15 18 #
16 19 # Overwrite this method in your model to customize the filename.
17 20 # The optional thumbnail argument will output the thumbnail's filename.
... ... @@ -19,29 +22,56 @@ module Technoweenie # :nodoc:
19 22 file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
20 23 File.join(Rails.root, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
21 24 end
22   -
  25 +
23 26 # Used as the base path that #public_filename strips off full_filename to create the public path
24 27 def base_path
25 28 @base_path ||= File.join(Rails.root, 'public')
26 29 end
27   -
  30 +
28 31 # The attachment ID used in the full path of a file
29 32 def attachment_path_id
30   - (is_thumbnail? && respond_to?(:parent_id)) ? parent_id : id
  33 + ((respond_to?(:parent_id) && parent_id) || id) || 0
31 34 end
32   -
33   - # overrwrite this to do your own app-specific partitioning.
34   - # you can thank Jamis Buck for this: http://www.37signals.com/svn/archives2/id_partitioning.php
  35 +
  36 + # Partitions the given path into an array of path components.
  37 + #
  38 + # For example, given an <tt>*args</tt> of ["foo", "bar"], it will return
  39 + # <tt>["0000", "0001", "foo", "bar"]</tt> (assuming that that id returns 1).
  40 + #
  41 + # If the id is not an integer, then path partitioning will be performed by
  42 + # hashing the string value of the id with SHA-512, and splitting the result
  43 + # into 4 components. If the id a 128-bit UUID (as set by :uuid_primary_key => true)
  44 + # then it will be split into 2 components.
  45 + #
  46 + # To turn this off entirely, set :partition => false.
35 47 def partitioned_path(*args)
36   - ("%08d" % attachment_path_id).scan(/..../) + args
  48 + if respond_to?(:attachment_options) && attachment_options[:partition] == false
  49 + args
  50 + elsif attachment_options[:uuid_primary_key]
  51 + # Primary key is a 128-bit UUID in hex format. Split it into 2 components.
  52 + path_id = attachment_path_id.to_s
  53 + component1 = path_id[0..15] || "-"
  54 + component2 = path_id[16..-1] || "-"
  55 + [component1, component2] + args
  56 + else
  57 + path_id = attachment_path_id
  58 + if path_id.is_a?(Integer)
  59 + # Primary key is an integer. Split it after padding it with 0.
  60 + ("%08d" % path_id).scan(/..../) + args
  61 + else
  62 + # Primary key is a String. Hash it, then split it into 4 components.
  63 + hash = Digest::SHA512.hexdigest(path_id.to_s)
  64 + [hash[0..31], hash[32..63], hash[64..95], hash[96..127]] + args
  65 + end
  66 + end
37 67 end
38   -
  68 +
39 69 # Gets the public path to the file
40 70 # The optional thumbnail argument will output the thumbnail's filename.
41 71 def public_filename(thumbnail = nil)
42 72 full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
43 73 end
44   -
  74 +
45 75 def filename=(value)
46 76 @old_filename = full_filename unless filename.nil? || @old_filename
47 77 write_attribute :filename, sanitize_filename(value)
... ... @@ -74,19 +104,19 @@ module Technoweenie # :nodoc:
74 104 @old_filename = nil
75 105 true
76 106 end
77   -
  107 +
78 108 # Saves the file to the file system
79 109 def save_to_storage
80 110 if save_attachment?
81 111 # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
82 112 FileUtils.mkdir_p(File.dirname(full_filename))
83   - File.cp(temp_path, full_filename)
84   - File.chmod(attachment_options[:chmod] || 0644, full_filename)
  113 + FileUtils.cp(temp_path, full_filename)
  114 + FileUtils.chmod(attachment_options[:chmod] || 0644, full_filename)
85 115 end
86 116 @old_filename = nil
87 117 true
88 118 end
89   -
  119 +
90 120 def current_data
91 121 File.file?(full_filename) ? File.read(full_filename) : nil
92 122 end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb
... ... @@ -12,31 +12,37 @@ module Technoweenie # :nodoc:
12 12 #
13 13 # == Configuration
14 14 #
15   - # Configuration is done via <tt>Rails.root/config/amazon_s3.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>.
  15 + # Configuration is done via <tt>#{Rails.root}/config/amazon_s3.yml</tt> and is loaded according to the <tt>#{Rails.env}</tt>.
16 16 # The minimum connection options that you must specify are a bucket name, your access key id and your secret access key.
17 17 # If you don't already have your access keys, all you need to sign up for the S3 service is an account at Amazon.
18 18 # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
19 19 #
20   - # Example configuration (Rails.root/config/amazon_s3.yml)
21   - #
  20 + # If you wish to use Amazon CloudFront to serve the files, you can also specify a distibution domain for the bucket.
  21 + # To read more about CloudFront, visit http://aws.amazon.com/cloudfront
  22 + #
  23 + # Example configuration (#{Rails.root}/config/amazon_s3.yml)
  24 + #
22 25 # development:
23 26 # bucket_name: appname_development
24 27 # access_key_id: <your key>
25 28 # secret_access_key: <your key>
26   - #
  29 + # distribution_domain: XXXX.cloudfront.net
  30 + #
27 31 # test:
28 32 # bucket_name: appname_test
29 33 # access_key_id: <your key>
30 34 # secret_access_key: <your key>
31   - #
  35 + # distribution_domain: XXXX.cloudfront.net
  36 + #
32 37 # production:
33 38 # bucket_name: appname
34 39 # access_key_id: <your key>
35 40 # secret_access_key: <your key>
  41 + # distribution_domain: XXXX.cloudfront.net
36 42 #
37 43 # You can change the location of the config path by passing a full path to the :s3_config_path option.
38 44 #
39   - # has_attachment :storage => :s3, :s3_config_path => (Rails.root + '/config/s3.yml')
  45 + # has_attachment :storage => :s3, :s3_config_path => (#{Rails.root} + '/config/s3.yml')
40 46 #
41 47 # === Required configuration parameters
42 48 #
... ... @@ -59,6 +65,8 @@ module Technoweenie # :nodoc:
59 65 # * <tt>:server</tt> - The server to make requests to. Defaults to <tt>s3.amazonaws.com</tt>.
60 66 # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if <tt>:use_ssl</tt> is set.
61 67 # * <tt>:use_ssl</tt> - If set to true, <tt>:port</tt> will be implicitly set to 443, unless specified otherwise. Defaults to false.
  68 + # * <tt>:distribution_domain</tt> - The CloudFront distribution domain for the bucket. This can either be the assigned
  69 + # distribution domain (ie. XXX.cloudfront.net) or a chosen domain using a CNAME. See CloudFront for more details.
62 70 #
63 71 # == Usage
64 72 #
... ... @@ -81,10 +89,43 @@ module Technoweenie # :nodoc:
81 89 #
82 90 # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt>
83 91 #
  92 + # === Using different bucket names on different models
  93 + #
  94 + # By default the bucket name that the file will be stored to is the one specified by the
  95 + # <tt>:bucket_name</tt> key in the amazon_s3.yml file. You can use the <tt>:bucket_key</tt> option
  96 + # to overide this behavior on a per model basis. For instance if you want a bucket that will hold
  97 + # only Photos you can do this:
  98 + #
  99 + # class Photo < ActiveRecord::Base
  100 + # has_attachment :storage => :s3, :bucket_key => :photo_bucket_name
  101 + # end
  102 + #
  103 + # And then your amazon_s3.yml file needs to look like this.
  104 + #
  105 + # development:
  106 + # bucket_name: appname_development
  107 + # access_key_id: <your key>
  108 + # secret_access_key: <your key>
  109 + #
  110 + # test:
  111 + # bucket_name: appname_test
  112 + # access_key_id: <your key>
  113 + # secret_access_key: <your key>
  114 + #
  115 + # production:
  116 + # bucket_name: appname
  117 + # photo_bucket_name: appname_photos
  118 + # access_key_id: <your key>
  119 + # secret_access_key: <your key>
  120 + #
  121 + # If the bucket_key you specify is not there in a certain environment then attachment_fu will
  122 + # default to the <tt>bucket_name</tt> key. This way you only have to create special buckets
  123 + # this can be helpful if you only need special buckets in certain environments.
  124 + #
84 125 # === Permissions
85 126 #
86 127 # By default, files are stored on S3 with public access permissions. You can customize this using
87   - # the <tt>:s3_access</tt> option to <tt>has_attachment</tt>. Available values are
  128 + # the <tt>:s3_access</tt> option to <tt>has_attachment</tt>. Available values are
88 129 # <tt>:private</tt>, <tt>:public_read_write</tt>, and <tt>:authenticated_read</tt>.
89 130 #
90 131 # === Other options
... ... @@ -117,13 +158,23 @@ module Technoweenie # :nodoc:
117 158 #
118 159 # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the bucket name as part of the path.
119 160 # You can retrieve the bucket name using the <tt>bucket_name</tt> method.
  161 + #
  162 + # === Accessing CloudFront URLs
  163 + #
  164 + # You can get an object's CloudFront URL using the cloudfront_url accessor. Using the example from above:
  165 + # @postcard.cloudfront_url # => http://XXXX.cloudfront.net/photos/1/mexico.jpg
  166 + #
  167 + # The resulting url is in the form: http://:distribution_domain/:table_name/:id/:file
  168 + #
  169 + # If you set :cloudfront to true in your model, the public_filename will be the CloudFront
  170 + # URL, not the S3 URL.
120 171 module S3Backend
121 172 class RequiredLibraryNotFoundError < StandardError; end
122 173 class ConfigFileNotFoundError < StandardError; end
123 174  
124 175 def self.included(base) #:nodoc:
125 176 mattr_reader :bucket_name, :s3_config
126   -
  177 +
127 178 begin
128 179 require 'aws/s3'
129 180 include AWS::S3
... ... @@ -132,13 +183,20 @@ module Technoweenie # :nodoc:
132 183 end
133 184  
134 185 begin
135   - @@s3_config_path = base.attachment_options[:s3_config_path] || (Rails.root + '/config/amazon_s3.yml')
136   - @@s3_config = @@s3_config = YAML.load(ERB.new(File.read(@@s3_config_path)).result)[RAILS_ENV].symbolize_keys
  186 + @@s3_config_path = base.attachment_options[:s3_config_path] || File.join(Rails.root, 'config', 'amazon_s3.yml')
  187 + @@s3_config = @@s3_config = YAML.load(ERB.new(File.read(@@s3_config_path)).result)[Rails.env].symbolize_keys
137 188 #rescue
138 189 # raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path)
139 190 end
140 191  
141   - @@bucket_name = s3_config[:bucket_name]
  192 + bucket_key = base.attachment_options[:bucket_key]
  193 +
  194 + if bucket_key and s3_config[bucket_key.to_sym]
  195 + eval_string = "def bucket_name()\n \"#{s3_config[bucket_key.to_sym]}\"\nend"
  196 + else
  197 + eval_string = "def bucket_name()\n \"#{s3_config[:bucket_name]}\"\nend"
  198 + end
  199 + base.class_eval(eval_string, __FILE__, __LINE__)
142 200  
143 201 Base.establish_connection!(s3_config.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy))
144 202  
... ... @@ -150,27 +208,35 @@ module Technoweenie # :nodoc:
150 208 def self.protocol
151 209 @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
152 210 end
153   -
  211 +
154 212 def self.hostname
155 213 @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
156 214 end
157   -
  215 +
158 216 def self.port_string
159 217 @port_string ||= (s3_config[:port].nil? || s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80)) ? '' : ":#{s3_config[:port]}"
160 218 end
161 219  
  220 + def self.distribution_domain
  221 + @distribution_domain = s3_config[:distribution_domain]
  222 + end
  223 +
162 224 module ClassMethods
163 225 def s3_protocol
164 226 Technoweenie::AttachmentFu::Backends::S3Backend.protocol
165 227 end
166   -
  228 +
167 229 def s3_hostname
168 230 Technoweenie::AttachmentFu::Backends::S3Backend.hostname
169 231 end
170   -
  232 +
171 233 def s3_port_string
172 234 Technoweenie::AttachmentFu::Backends::S3Backend.port_string
173 235 end
  236 +
  237 + def cloudfront_distribution_domain
  238 + Technoweenie::AttachmentFu::Backends::S3Backend.distribution_domain
  239 + end
174 240 end
175 241  
176 242 # Overwrites the base filename writer in order to store the old filename
... ... @@ -196,22 +262,42 @@ module Technoweenie # :nodoc:
196 262 File.join(base_path, thumbnail_name_for(thumbnail))
197 263 end
198 264  
199   - # All public objects are accessible via a GET request to the S3 servers. You can generate a
  265 + # All public objects are accessible via a GET request to the S3 servers. You can generate a
200 266 # url for an object using the s3_url method.
201 267 #
202 268 # @photo.s3_url
203 269 #
204 270 # The resulting url is in the form: <tt>http(s)://:server/:bucket_name/:table_name/:id/:file</tt> where
205 271 # the <tt>:server</tt> variable defaults to <tt>AWS::S3 URL::DEFAULT_HOST</tt> (s3.amazonaws.com) and can be
206   - # set using the configuration parameters in <tt>Rails.root/config/amazon_s3.yml</tt>.
  272 + # set using the configuration parameters in <tt>#{Rails.root}/config/amazon_s3.yml</tt>.
207 273 #
208 274 # The optional thumbnail argument will output the thumbnail's filename (if any).
209 275 def s3_url(thumbnail = nil)
210 276 File.join(s3_protocol + s3_hostname + s3_port_string, bucket_name, full_filename(thumbnail))
211 277 end
212   - alias :public_filename :s3_url
213 278  
214   - # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
  279 + # All public objects are accessible via a GET request to CloudFront. You can generate a
  280 + # url for an object using the cloudfront_url method.
  281 + #
  282 + # @photo.cloudfront_url
  283 + #
  284 + # The resulting url is in the form: <tt>http://:distribution_domain/:table_name/:id/:file</tt> using
  285 + # the <tt>:distribution_domain</tt> variable set in the configuration parameters in <tt>#{Rails.root}/config/amazon_s3.yml</tt>.
  286 + #
  287 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  288 + def cloudfront_url(thumbnail = nil)
  289 + s3_protocol + cloudfront_distribution_domain + "/" + full_filename(thumbnail)
  290 + end
  291 +
  292 + def public_filename(*args)
  293 + if attachment_options[:cloudfront]
  294 + cloudfront_url(*args)
  295 + else
  296 + s3_url(*args)
  297 + end
  298 + end
  299 +
  300 + # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
215 301 # authenticated url for an object like this:
216 302 #
217 303 # @photo.authenticated_s3_url
... ... @@ -223,7 +309,7 @@ module Technoweenie # :nodoc:
223 309 #
224 310 # # Absolute expiration date (October 13th, 2025)
225 311 # @photo.authenticated_s3_url(:expires => Time.mktime(2025,10,13).to_i)
226   - #
  312 + #
227 313 # # Expiration in five hours from now
228 314 # @photo.authenticated_s3_url(:expires_in => 5.hours)
229 315 #
... ... @@ -236,8 +322,9 @@ module Technoweenie # :nodoc:
236 322 #
237 323 # @photo.authenticated_s3_url('thumbnail', :expires_in => 5.hours, :use_ssl => true)
238 324 def authenticated_s3_url(*args)
239   - thumbnail = args.first.is_a?(String) ? args.first : nil
240   - options = args.last.is_a?(Hash) ? args.last : {}
  325 + options = args.extract_options!
  326 + options[:expires_in] = options[:expires_in].to_i if options[:expires_in]
  327 + thumbnail = args.shift
241 328 S3Object.url_for(full_filename(thumbnail), bucket_name, options)
242 329 end
243 330  
... ... @@ -246,21 +333,29 @@ module Technoweenie # :nodoc:
246 333 end
247 334  
248 335 def current_data
249   - S3Object.value full_filename, bucket_name
  336 + if attachment_options[:encrypted_storage] && self.respond_to?(:encryption_key) && self.encryption_key != nil
  337 + EncryptedData.decrypt_data(S3Object.value(full_filename, bucket_name), self.encryption_key)
  338 + else
  339 + S3Object.value full_filename, bucket_name
  340 + end
250 341 end
251 342  
252 343 def s3_protocol
253 344 Technoweenie::AttachmentFu::Backends::S3Backend.protocol
254 345 end
255   -
  346 +
256 347 def s3_hostname
257 348 Technoweenie::AttachmentFu::Backends::S3Backend.hostname
258 349 end
259   -
  350 +
260 351 def s3_port_string
261 352 Technoweenie::AttachmentFu::Backends::S3Backend.port_string
262 353 end
263 354  
  355 + def cloudfront_distribution_domain
  356 + Technoweenie::AttachmentFu::Backends::S3Backend.distribution_domain
  357 + end
  358 +
264 359 protected
265 360 # Called in the after_destroy callback
266 361 def destroy_file
... ... @@ -269,7 +364,7 @@ module Technoweenie # :nodoc:
269 364  
270 365 def rename_file
271 366 return unless @old_filename && @old_filename != filename
272   -
  367 +
273 368 old_full_filename = File.join(base_path, @old_filename)
274 369  
275 370 S3Object.rename(
... ... @@ -285,13 +380,27 @@ module Technoweenie # :nodoc:
285 380  
286 381 def save_to_storage
287 382 if save_attachment?
288   - S3Object.store(
289   - full_filename,
290   - (temp_path ? File.open(temp_path) : temp_data),
291   - bucket_name,
292   - :content_type => content_type,
293   - :access => attachment_options[:s3_access]
294   - )
  383 + if attachment_options[:encrypted_storage]
  384 + S3Object.store(
  385 + full_filename,
  386 + (temp_path ? File.open(temp_path) : temp_data),
  387 + bucket_name,
  388 + :content_type => content_type,
  389 + :cache_control => attachment_options[:cache_control],
  390 + :access => attachment_options[:s3_access],
  391 + 'x-amz-server-side-encryption' => 'AES256',
  392 + 'Content-Disposition' => "attachment; filename=\"#{filename}\""
  393 + )
  394 + else
  395 + S3Object.store(
  396 + full_filename,
  397 + (temp_path ? File.open(temp_path) : temp_data),
  398 + bucket_name,
  399 + :content_type => content_type,
  400 + :cache_control => attachment_options[:cache_control],
  401 + :access => attachment_options[:s3_access]
  402 + )
  403 + end
295 404 end
296 405  
297 406 @old_filename = nil
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/core_image_processor.rb
... ... @@ -44,7 +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   - result.save self.temp_path, OSX::NSJPEGFileType
  47 + out_file = random_tempfile_filename
  48 + temp_paths.unshift Tempfile.new(out_file, Technoweenie::AttachmentFu.tempfile_path).path
  49 + properties = nil
  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
  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 + #
48 58 self.size = File.size(self.temp_path)
49 59 end
50 60 end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/gd2_processor.rb
... ... @@ -44,8 +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.temp_path = random_tempfile_filename
48   - self.size = img.export(self.temp_path)
  47 + self.width = img.width if respond_to?(:width)
  48 + self.height = img.height if respond_to?(:height)
  49 + out_file = random_tempfile_filename
  50 + temp_paths.unshift out_file
  51 + jpeg = out_file =~ /\.jpe?g\z/i
  52 + quality = jpeg && get_jpeg_quality
  53 + self.size = img.export(self.temp_path, quality ? { :quality => quality } : {})
49 54 end
50 55  
51 56 end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/image_science_processor.rb
... ... @@ -32,13 +32,21 @@ module Technoweenie # :nodoc:
32 32 # pngs for thumbnails. It has something to do with trying to save gifs
33 33 # with a larger palette than 256 colors, which is all the gif format
34 34 # supports.
35   - filename.sub! /gif$/, 'png'
  35 + filename.sub! /gif$/i, 'png'
36 36 content_type.sub!(/gif$/, 'png')
37   - self.temp_path = write_to_temp_file(filename)
  37 + temp_paths.unshift write_to_temp_file(filename)
38 38 grab_dimensions = lambda do |img|
39 39 self.width = img.width if respond_to?(:width)
40 40 self.height = img.height if respond_to?(:height)
41   - img.save self.temp_path
  41 +
  42 + # We don't check for quality being a 0-100 value as we also allow FreeImage JPEG_xxx constants.
  43 + quality = content_type[/jpe?g/i] && get_jpeg_quality(false)
  44 + # Traditional ImageScience has a 1-arg save method, tdd-image_science has 1 mandatory + 1 optional
  45 + if quality && img.method(:save).arity == -2
  46 + img.save self.temp_path, quality
  47 + else
  48 + img.save self.temp_path
  49 + end
42 50 self.size = File.size(self.temp_path)
43 51 callback_with_args :after_resize, img
44 52 end
... ... @@ -52,7 +60,18 @@ module Technoweenie # :nodoc:
52 60 end
53 61 else
54 62 new_size = [img.width, img.height] / size.to_s
55   - img.resize(new_size[0], new_size[1], &grab_dimensions)
  63 + if size.ends_with?('!')
  64 + aspect = new_size[0].to_f / new_size[1].to_f
  65 + ih, iw = img.height, img.width
  66 + w, h = (ih * aspect), (iw / aspect)
  67 + w = [iw, w].min.to_i
  68 + h = [ih, h].min.to_i
  69 + img.with_crop((iw-w)/2, (ih-h)/2, (iw+w)/2, (ih+h)/2) { |crop|
  70 + crop.resize(new_size[0], new_size[1], &grab_dimensions)
  71 + }
  72 + else
  73 + img.resize(new_size[0], new_size[1], &grab_dimensions)
  74 + end
56 75 end
57 76 end
58 77 end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb
... ... @@ -7,12 +7,12 @@ module Technoweenie # :nodoc:
7 7 base.send :extend, ClassMethods
8 8 base.alias_method_chain :process_attachment, :processing
9 9 end
10   -
  10 +
11 11 module ClassMethods
12 12 # Yields a block containing an MiniMagick Image for the given binary data.
13 13 def with_image(file, &block)
14 14 begin
15   - binary_data = file.is_a?(MiniMagick::Image) ? file : MiniMagick::Image.from_file(file) unless !Object.const_defined?(:MiniMagick)
  15 + binary_data = file.is_a?(MiniMagick::Image) ? file : MiniMagick::Image.open(file) unless !Object.const_defined?(:MiniMagick)
16 16 rescue
17 17 # Log the failure to load the image.
18 18 logger.debug("Exception working with image: #{$!}")
... ... @@ -23,23 +23,30 @@ module Technoweenie # :nodoc:
23 23 !binary_data.nil?
24 24 end
25 25 end
26   -
  26 +
27 27 protected
28 28 def process_attachment_with_processing
29 29 return unless process_attachment_without_processing
30 30 with_image do |img|
31 31 resize_image_or_thumbnail! img
32   - self.width = img[:width] if respond_to?(:width)
33   - self.height = img[:height] if respond_to?(:height)
  32 + self.width = img[:width] if respond_to?(:width)
  33 + self.height = img[:height] if respond_to?(:height)
34 34 callback_with_args :after_resize, img
35 35 end if image?
36 36 end
37   -
  37 +
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]
  44 +
  45 + # GIF is not handled correctly, so we move to PNG, as in other processors…
  46 + if format == 'GIF'
  47 + img.format('PNG')
  48 + end
  49 +
43 50 if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
44 51 if size.is_a?(Fixnum)
45 52 size = [size, size]
... ... @@ -47,13 +54,89 @@ module Technoweenie # :nodoc:
47 54 else
48 55 commands.resize(size.join('x') + '!')
49 56 end
  57 + # extend to thumbnail size
  58 + elsif size.is_a?(String) and size =~ /e$/
  59 + size = size.gsub(/e/, '')
  60 + commands.resize(size.to_s + '>')
  61 + commands.background('#ffffff')
  62 + commands.gravity('center')
  63 + commands.extent(size)
  64 + # crop thumbnail, the smart way
  65 + elsif size.is_a?(String) and size =~ /c$/
  66 + size = size.gsub(/c/, '')
  67 +
  68 + # calculate sizes and aspect ratio
  69 + thumb_width, thumb_height = size.split("x")
  70 + thumb_width = thumb_width.to_f
  71 + thumb_height = thumb_height.to_f
  72 +
  73 + thumb_aspect = thumb_width.to_f / thumb_height.to_f
  74 + image_width, image_height = img[:width].to_f, img[:height].to_f
  75 + image_aspect = image_width / image_height
  76 +
  77 + # only crop if image is not smaller in both dimensions
  78 + unless image_width < thumb_width and image_height < thumb_height
  79 + command = calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
  80 +
  81 + # crop image
  82 + commands.extract(command)
  83 + end
  84 +
  85 + # don not resize if image is not as height or width then thumbnail
  86 + if image_width < thumb_width or image_height < thumb_height
  87 + commands.background('#ffffff')
  88 + commands.gravity('center')
  89 + commands.extent(size)
  90 + # resize image
  91 + else
  92 + commands.resize("#{size.to_s}")
  93 + end
  94 + # crop end
50 95 else
51 96 commands.resize(size.to_s)
52 97 end
53 98 end
54   - self.temp_path = img
  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
  107 + temp_paths.unshift img
  108 + self.size = File.size(self.temp_path)
55 109 end
  110 +
  111 + def calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
  112 + # only crop if image is not smaller in both dimensions
  113 +
  114 + # special cases, image smaller in one dimension then thumbsize
  115 + if image_width < thumb_width
  116 + offset = (image_height / 2) - (thumb_height / 2)
  117 + command = "#{image_width}x#{thumb_height}+0+#{offset}"
  118 + elsif image_height < thumb_height
  119 + offset = (image_width / 2) - (thumb_width / 2)
  120 + command = "#{thumb_width}x#{image_height}+#{offset}+0"
  121 +
  122 + # normal thumbnail generation
  123 + # calculate height and offset y, width is fixed
  124 + elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height
  125 + height = image_width / thumb_aspect
  126 + offset = (image_height / 2) - (height / 2)
  127 + command = "#{image_width}x#{height}+0+#{offset}"
  128 + # calculate width and offset x, height is fixed
  129 + else
  130 + width = image_height * thumb_aspect
  131 + offset = (image_width / 2) - (width / 2)
  132 + command = "#{width}x#{image_height}+#{offset}+0"
  133 + end
  134 + # crop image
  135 + command
  136 + end
  137 +
  138 +
56 139 end
57 140 end
58 141 end
59 142 -end
  143 +end
60 144 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb
... ... @@ -13,6 +13,7 @@ module Technoweenie # :nodoc:
13 13 def with_image(file, &block)
14 14 begin
15 15 binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
  16 + binary_data && binary_data.auto_orient!
16 17 rescue
17 18 # Log the failure to load the image. This should match ::Magick::ImageMagickError
18 19 # but that would cause acts_as_attachment to require rmagick.
... ... @@ -42,11 +43,22 @@ module Technoweenie # :nodoc:
42 43 if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
43 44 size = [size, size] if size.is_a?(Fixnum)
44 45 img.thumbnail!(*size)
  46 + elsif size.is_a?(String) && size =~ /^c.*$/ # Image cropping - example geometry string: c75x75
  47 + dimensions = size[1..size.size].split("x")
  48 + img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
45 49 else
46   - img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) }
  50 + img.change_geometry(size.to_s) { |cols, rows, image|
  51 + image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows)
  52 + }
47 53 end
  54 + self.width = img.columns if respond_to?(:width)
  55 + self.height = img.rows if respond_to?(:height)
  56 + img = img.sharpen if attachment_options[:sharpen_on_resize] && img.changed?
48 57 img.strip! unless attachment_options[:keep_profile]
49   - self.temp_path = write_to_temp_file(img.to_blob)
  58 + quality = img.format.to_s[/JPEG/] && get_jpeg_quality
  59 + out_file = write_to_temp_file(img.to_blob { self.quality = quality if quality })
  60 + temp_paths.unshift out_file
  61 + self.size = File.size(self.temp_path)
50 62 end
51 63 end
52 64 end
... ...
vendor/plugins/attachment_fu/rackspace_cloudfiles.yml.tpl 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +development:
  2 + container_name: appname_development
  3 + username:
  4 + api_key:
  5 +
  6 +test:
  7 + container_name: appname_test
  8 + username:
  9 + api_key:
  10 +
  11 +production:
  12 + container_name: appname_production
  13 + username:
  14 + api_key:
... ...
vendor/plugins/attachment_fu/test/backends/file_system_test.rb
1 1 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +require 'digest/sha2'
2 3  
3 4 class FileSystemTest < Test::Unit::TestCase
4 5 include BaseAttachmentTests
... ... @@ -11,7 +12,7 @@ class FileSystemTest &lt; Test::Unit::TestCase
11 12 assert_equal attachment.size, File.open(attachment.full_filename).stat.size
12 13 end
13 14 end
14   -
  15 +
15 16 test_against_subclass :test_filesystem_size_for_file_attachment, FileAttachment
16 17  
17 18 def test_should_not_overwrite_file_attachment(klass = FileAttachment)
... ... @@ -21,15 +22,15 @@ class FileSystemTest &lt; Test::Unit::TestCase
21 22 assert_valid real
22 23 assert !real.new_record?, real.errors.full_messages.join("\n")
23 24 assert !real.size.zero?
24   -
  25 +
25 26 fake = upload_file :filename => '/files/fake/rails.png'
26 27 assert_valid fake
27 28 assert !fake.size.zero?
28   -
  29 +
29 30 assert_not_equal File.open(real.full_filename).stat.size, File.open(fake.full_filename).stat.size
30 31 end
31 32 end
32   -
  33 +
33 34 test_against_subclass :test_should_not_overwrite_file_attachment, FileAttachment
34 35  
35 36 def test_should_store_file_attachment_in_filesystem(klass = FileAttachment)
... ... @@ -38,13 +39,13 @@ class FileSystemTest &lt; Test::Unit::TestCase
38 39 assert_created do
39 40 attachment = upload_file :filename => '/files/rails.png'
40 41 assert_valid attachment
41   - assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
  42 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
42 43 end
43 44 attachment
44 45 end
45   -
  46 +
46 47 test_against_subclass :test_should_store_file_attachment_in_filesystem, FileAttachment
47   -
  48 +
48 49 def test_should_delete_old_file_when_updating(klass = FileAttachment)
49 50 attachment_model klass
50 51 attachment = upload_file :filename => '/files/rails.png'
... ... @@ -52,16 +53,16 @@ class FileSystemTest &lt; Test::Unit::TestCase
52 53 assert_not_created do
53 54 use_temp_file 'files/rails.png' do |file|
54 55 attachment.filename = 'rails2.png'
55   - attachment.temp_path = File.join(fixture_path, file)
  56 + attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
56 57 attachment.save!
57   - assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
  58 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
58 59 assert !File.exists?(old_filename), "#{old_filename} still exists"
59 60 end
60 61 end
61 62 end
62   -
  63 +
63 64 test_against_subclass :test_should_delete_old_file_when_updating, FileAttachment
64   -
  65 +
65 66 def test_should_delete_old_file_when_renaming(klass = FileAttachment)
66 67 attachment_model klass
67 68 attachment = upload_file :filename => '/files/rails.png'
... ... @@ -69,12 +70,74 @@ class FileSystemTest &lt; Test::Unit::TestCase
69 70 assert_not_created do
70 71 attachment.filename = 'rails2.png'
71 72 attachment.save
72   - assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
  73 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
73 74 assert !File.exists?(old_filename), "#{old_filename} still exists"
74 75 assert !attachment.reload.size.zero?
75 76 assert_equal 'rails2.png', attachment.filename
76 77 end
77 78 end
78   -
  79 +
79 80 test_against_subclass :test_should_delete_old_file_when_renaming, FileAttachment
80   -end
81 81 \ No newline at end of file
  82 +
  83 + def test_path_partitioning_works_on_integer_id(klass = FileAttachment)
  84 + attachment_model klass
  85 +
  86 + # Create a random attachment object, doesn't matter what.
  87 + attachment = upload_file :filename => '/files/rails.png'
  88 + old_id = attachment.id
  89 + attachment.id = 1
  90 +
  91 + begin
  92 + assert_equal ["0000", "0001", "bar.txt"], attachment.send(:partitioned_path, "bar.txt")
  93 + ensure
  94 + attachment.id = old_id
  95 + end
  96 + end
  97 +
  98 + test_against_subclass :test_path_partitioning_works_on_integer_id, FileAttachment
  99 +
  100 + def test_path_partitioning_with_string_id_works_by_generating_hash(klass = FileAttachmentWithStringId)
  101 + attachment_model klass
  102 +
  103 + # Create a random attachment object, doesn't matter what.
  104 + attachment = upload_file :filename => '/files/rails.png'
  105 + old_id = attachment.id
  106 + attachment.id = "hello world some long string"
  107 + hash = Digest::SHA512.hexdigest("hello world some long string")
  108 +
  109 + begin
  110 + assert_equal [
  111 + hash[0..31],
  112 + hash[32..63],
  113 + hash[64..95],
  114 + hash[96..127],
  115 + "bar.txt"
  116 + ], attachment.send(:partitioned_path, "bar.txt")
  117 + ensure
  118 + attachment.id = old_id
  119 + end
  120 + end
  121 +
  122 + test_against_subclass :test_path_partitioning_with_string_id_works_by_generating_hash, FileAttachmentWithStringId
  123 +
  124 + def test_path_partition_string_id_hashing_is_turned_off_if_id_is_uuid(klass = FileAttachmentWithUuid)
  125 + attachment_model klass
  126 +
  127 + # Create a random attachment object, doesn't matter what.
  128 + attachment = upload_file :filename => '/files/rails.png'
  129 + old_id = attachment.id
  130 + attachment.id = "0c0743b698483569dc65909a8cdb3bf9"
  131 +
  132 + begin
  133 + assert_equal [
  134 + "0c0743b698483569",
  135 + "dc65909a8cdb3bf9",
  136 + "bar.txt"
  137 + ], attachment.send(:partitioned_path, "bar.txt")
  138 + ensure
  139 + attachment.id = old_id
  140 + end
  141 + end
  142 +
  143 + test_against_subclass :test_path_partition_string_id_hashing_is_turned_off_if_id_is_uuid, FileAttachmentWithUuid
  144 +end
... ...
vendor/plugins/attachment_fu/test/backends/remote/cloudfiles_test.rb 0 → 100644
... ... @@ -0,0 +1,102 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
  2 +require 'net/http'
  3 +
  4 +class CloudfilesTest < Test::Unit::TestCase
  5 + def self.test_CloudFiles?
  6 + true unless ENV["TEST_CLOUDFILES"] == "false"
  7 + end
  8 +
  9 + if test_CloudFiles? && File.exist?(File.join(File.dirname(__FILE__), '../../rackspace_cloudfiles.yml'))
  10 + include BaseAttachmentTests
  11 + attachment_model CloudFilesAttachment
  12 +
  13 + def test_should_create_correct_container_name(klass = CloudFilesAttachment)
  14 + attachment_model klass
  15 + attachment = upload_file :filename => '/files/rails.png'
  16 + assert_equal attachment.cloudfiles_config[:container_name], attachment.container_name
  17 + end
  18 +
  19 + test_against_subclass :test_should_create_correct_container_name, CloudFilesAttachment
  20 +
  21 + def test_should_create_default_path_prefix(klass = CloudFilesAttachment)
  22 + attachment_model klass
  23 + attachment = upload_file :filename => '/files/rails.png'
  24 + assert_equal File.join(attachment_model.table_name, attachment.attachment_path_id), attachment.base_path
  25 + end
  26 +
  27 + test_against_subclass :test_should_create_default_path_prefix, CloudFilesAttachment
  28 +
  29 + def test_should_create_custom_path_prefix(klass = CloudFilesWithPathPrefixAttachment)
  30 + attachment_model klass
  31 + attachment = upload_file :filename => '/files/rails.png'
  32 + assert_equal File.join('some/custom/path/prefix', attachment.attachment_path_id), attachment.base_path
  33 + end
  34 +
  35 + test_against_subclass :test_should_create_custom_path_prefix, CloudFilesWithPathPrefixAttachment
  36 +
  37 +
  38 + def test_should_create_valid_url(klass = CloudFilesAttachment)
  39 + attachment_model klass
  40 + attachment = upload_file :filename => '/files/rails.png'
  41 + assert_match(%r!http://cdn.cloudfiles.mosso.com/(.*?)/cloud_files_attachments/1/rails.png!, attachment.cloudfiles_url)
  42 + end
  43 +
  44 + test_against_subclass :test_should_create_valid_url, CloudFilesAttachment
  45 +
  46 + def test_should_save_attachment(klass = CloudFilesAttachment)
  47 + attachment_model klass
  48 + assert_created do
  49 + attachment = upload_file :filename => '/files/rails.png'
  50 + assert_valid attachment
  51 + assert attachment.image?
  52 + assert !attachment.size.zero?
  53 + assert_kind_of Net::HTTPOK, http_response_for(attachment.cloudfiles_url)
  54 + end
  55 + end
  56 +
  57 + test_against_subclass :test_should_save_attachment, CloudFilesAttachment
  58 +
  59 + def test_should_delete_attachment_from_cloud_files_when_attachment_record_destroyed(klass = CloudFilesAttachment)
  60 + attachment_model klass
  61 + attachment = upload_file :filename => '/files/rails.png'
  62 +
  63 + urls = [attachment.cloudfiles_url] + attachment.thumbnails.collect(&:cloudfiles_url)
  64 +
  65 + urls.each {|url| assert_kind_of Net::HTTPOK, http_response_for(url) }
  66 + attachment.destroy
  67 + urls.each do |url|
  68 + begin
  69 + http_response_for(url)
  70 + rescue Net::HTTPForbidden, Net::HTTPNotFound
  71 + nil
  72 + end
  73 + end
  74 + end
  75 +
  76 + test_against_subclass :test_should_delete_attachment_from_cloud_files_when_attachment_record_destroyed, CloudFilesAttachment
  77 +
  78 +
  79 +
  80 + protected
  81 + def http_response_for(url)
  82 + url = URI.parse(url)
  83 + Net::HTTP.start(url.host, url.port) {|http| http.request_head(url.path) }
  84 + end
  85 +
  86 + def s3_protocol
  87 + Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  88 + end
  89 +
  90 + def s3_hostname
  91 + Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  92 + end
  93 +
  94 + def s3_port_string
  95 + Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  96 + end
  97 + else
  98 + def test_flunk_s3
  99 + puts "s3 config file not loaded, tests not running"
  100 + end
  101 + end
  102 +end
0 103 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/backends/remote/s3_test.rb
... ... @@ -49,6 +49,18 @@ class S3Test &lt; Test::Unit::TestCase
49 49 end
50 50  
51 51 test_against_subclass :test_should_create_authenticated_url, S3Attachment
  52 +
  53 + def test_should_create_authenticated_url_for_thumbnail(klass = S3Attachment)
  54 + attachment_model klass
  55 + attachment = upload_file :filename => '/files/rails.png'
  56 + ['large', :large].each do |thumbnail|
  57 + assert_match(
  58 + /^http.+rails_large\.png.+AWSAccessKeyId.+Expires.+Signature/,
  59 + attachment.authenticated_s3_url(thumbnail),
  60 + "authenticated_s3_url failed with #{thumbnail.class} parameter"
  61 + )
  62 + end
  63 + end
52 64  
53 65 def test_should_save_attachment(klass = S3Attachment)
54 66 attachment_model klass
... ...
vendor/plugins/attachment_fu/test/base_attachment_tests.rb
1 1 module BaseAttachmentTests
2 2 def test_should_create_file_from_uploaded_file
3 3 assert_created do
4   - attachment = upload_file :filename => '/files/foo.txt'
  4 + attachment = upload_file :filename => '/files/foo.txt', :content_type => 'text/plain'
5 5 assert_valid attachment
6 6 assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
7   - assert attachment.image?
  7 + assert !attachment.image?
  8 + assert !attachment.size.zero?
  9 + #assert_equal 3, attachment.size
  10 + assert_nil attachment.width
  11 + assert_nil attachment.height
  12 + end
  13 + end
  14 +
  15 + def test_should_create_file_from_merb_temp_file
  16 + assert_created do
  17 + attachment = upload_merb_file :filename => '/files/foo.txt', :content_type => 'text/plain'
  18 + assert_valid attachment
  19 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  20 + assert !attachment.image?
8 21 assert !attachment.size.zero?
9 22 #assert_equal 3, attachment.size
10 23 assert_nil attachment.width
... ... @@ -18,7 +31,7 @@ module BaseAttachmentTests
18 31 assert_valid attachment
19 32 assert attachment.size > 0, "no data was set"
20 33  
21   - attachment.temp_data = 'wtf'
  34 + attachment.set_temp_data 'wtf'
22 35 assert attachment.save_attachment?
23 36 attachment.save!
24 37  
... ... @@ -32,7 +45,7 @@ module BaseAttachmentTests
32 45 assert_valid attachment
33 46 assert attachment.size > 0, "no data was set"
34 47  
35   - attachment.temp_data = nil
  48 + attachment.set_temp_data nil
36 49 assert !attachment.save_attachment?
37 50 end
38 51 end
... ... @@ -42,7 +55,7 @@ module BaseAttachmentTests
42 55 assert_not_created do # no new db_file records
43 56 use_temp_file 'files/rails.png' do |file|
44 57 attachment.filename = 'rails2.png'
45   - attachment.temp_path = File.join(fixture_path, file)
  58 + attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
46 59 attachment.save!
47 60 end
48 61 end
... ... @@ -54,4 +67,11 @@ module BaseAttachmentTests
54 67 assert !attachment.save_attachment?
55 68 assert_nothing_raised { attachment.save! }
56 69 end
  70 +
  71 + def test_should_handle_nil_file_upload
  72 + attachment = attachment_model.create :uploaded_data => ''
  73 + assert_raise ActiveRecord::RecordInvalid do
  74 + attachment.save!
  75 + end
  76 + end
57 77 end
58 78 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/basic_test.rb
  1 +# -*- coding: utf-8 -*-
1 2 require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2 3  
3 4 class BasicTest < Test::Unit::TestCase
4 5 def test_should_set_default_min_size
5 6 assert_equal 1, Attachment.attachment_options[:min_size]
6 7 end
7   -
  8 +
8 9 def test_should_set_default_max_size
9 10 assert_equal 1.megabyte, Attachment.attachment_options[:max_size]
10 11 end
11   -
  12 +
12 13 def test_should_set_default_size
13 14 assert_equal (1..1.megabyte), Attachment.attachment_options[:size]
14 15 end
15   -
  16 +
16 17 def test_should_set_default_thumbnails_option
17 18 assert_equal Hash.new, Attachment.attachment_options[:thumbnails]
18 19 end
... ... @@ -20,19 +21,19 @@ class BasicTest &lt; Test::Unit::TestCase
20 21 def test_should_set_default_thumbnail_class
21 22 assert_equal Attachment, Attachment.attachment_options[:thumbnail_class]
22 23 end
23   -
  24 +
24 25 def test_should_normalize_content_types_to_array
25 26 assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type]
26 27 assert_equal %w(pdf doc txt), DocAttachment.attachment_options[:content_type]
27   - assert_equal ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg'], ImageAttachment.attachment_options[:content_type]
28   - assert_equal ['pdf', 'image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg'], ImageOrPdfAttachment.attachment_options[:content_type]
  28 + assert_equal Technoweenie::AttachmentFu.content_types, ImageAttachment.attachment_options[:content_type]
  29 + assert_equal ['pdf'] + Technoweenie::AttachmentFu.content_types, ImageOrPdfAttachment.attachment_options[:content_type]
29 30 end
30   -
  31 +
31 32 def test_should_sanitize_content_type
32 33 @attachment = Attachment.new :content_type => ' foo '
33 34 assert_equal 'foo', @attachment.content_type
34 35 end
35   -
  36 +
36 37 def test_should_sanitize_filenames
37 38 @attachment = Attachment.new :filename => 'blah/foo.bar'
38 39 assert_equal 'foo.bar', @attachment.filename
... ... @@ -42,23 +43,79 @@ class BasicTest &lt; Test::Unit::TestCase
42 43  
43 44 @attachment.filename = 'f o!O-.bar'
44 45 assert_equal 'f_o_O-.bar', @attachment.filename
  46 +
  47 +# @attachment.filename = 'sheeps_says_bææ'
  48 +# assert_equal 'sheeps_says_b__', @attachment.filename
  49 +
  50 + @attachment.filename = nil
  51 + assert_nil @attachment.filename
45 52 end
46   -
  53 +
47 54 def test_should_convert_thumbnail_name
48 55 @attachment = FileAttachment.new :filename => 'foo.bar'
49 56 assert_equal 'foo.bar', @attachment.thumbnail_name_for(nil)
50 57 assert_equal 'foo.bar', @attachment.thumbnail_name_for('')
51 58 assert_equal 'foo_blah.bar', @attachment.thumbnail_name_for(:blah)
52 59 assert_equal 'foo_blah.blah.bar', @attachment.thumbnail_name_for('blah.blah')
53   -
  60 +
54 61 @attachment.filename = 'foo.bar.baz'
55 62 assert_equal 'foo.bar_blah.baz', @attachment.thumbnail_name_for(:blah)
56 63 end
57   -
  64 +
58 65 def test_should_require_valid_thumbnails_option
59 66 klass = Class.new(ActiveRecord::Base)
60 67 assert_raise ArgumentError do
61 68 klass.has_attachment :thumbnails => []
62 69 end
63 70 end
64   -end
65 71 \ No newline at end of file
  72 +
  73 + class ::ImageWithPolymorphicThumbsAttachment
  74 + cattr_accessor :thumbnail_creations
  75 +
  76 + def create_or_update_thumbnail(path, thumb, *size)
  77 + @@thumbnail_creations[thumb] = size.size == 1 ? size.first : size
  78 + end
  79 +
  80 + def self.reset_creations
  81 + @@thumbnail_creations = {}
  82 + end
  83 + end
  84 +
  85 + def test_should_handle_polymorphic_thumbnails_option
  86 + assert_polymorphic_thumb_creation nil,
  87 + :thumb => [50, 50], :geometry => 'x50'
  88 + assert_polymorphic_thumb_creation 'Product',
  89 + :thumb => [50, 50], :geometry => 'x50', :large_thumb => '169x169!', :zoomed => '500x500>'
  90 + assert_polymorphic_thumb_creation 'Editorial',
  91 + :thumb => [50, 50], :geometry => 'x50', :fullsize => '150x100>'
  92 + assert_polymorphic_thumb_creation 'User',
  93 + :thumb => [50, 50], :geometry => 'x50', :avatar => '64x64!'
  94 + end
  95 +
  96 + def test_should_compute_per_thumbnail_jpeg_quality
  97 + assert_jpeg_quality :thumb, 90
  98 + assert_jpeg_quality :avatar, 85
  99 + assert_jpeg_quality :large, 75
  100 + assert_jpeg_quality :large, 0x200 | 75, false
  101 + assert_jpeg_quality nil, 75
  102 + end
  103 +
  104 +private
  105 + def assert_jpeg_quality(thumbnail, quality, require_0_to_100 = true)
  106 + klass = ImageWithPerThumbJpegAttachment
  107 + w, h = if thumbnail
  108 + klass.attachment_options[:thumbnails][thumbnail].scan(/\d+/)
  109 + else
  110 + klass.attachment_options[:resize_to].scan(/\d+/)
  111 + end
  112 + attachment = klass.new(:thumbnail => thumbnail, :width => w, :height => h)
  113 + assert_equal quality, attachment.send(:get_jpeg_quality, require_0_to_100)
  114 + end
  115 +
  116 + def assert_polymorphic_thumb_creation(parent, defs)
  117 + attachment_model ImageWithPolymorphicThumbsAttachment
  118 + attachment_model.reset_creations
  119 + attachment = upload_file :filename => '/files/rails.png', :imageable_type => parent.to_s.classify, :imageable_id => nil
  120 + assert_equal defs, attachment_model.thumbnail_creations
  121 + end
  122 +end
... ...
vendor/plugins/attachment_fu/test/database.yml
1 1 sqlite:
2 2 :adapter: sqlite
3   - :dbfile: attachment_fu_plugin.sqlite.db
  3 + :database: attachment_fu_plugin.sqlite.db
4 4 sqlite3:
5 5 :adapter: sqlite3
6   - :dbfile: attachment_fu_plugin.sqlite3.db
  6 + :database: attachment_fu_plugin.sqlite3.db
7 7 postgresql:
8 8 :adapter: postgresql
9 9 :username: postgres
... ...
vendor/plugins/attachment_fu/test/extra_attachment_test.rb
... ... @@ -24,6 +24,16 @@ class OrphanAttachmentTest &lt; Test::Unit::TestCase
24 24 end
25 25 end
26 26  
  27 + def test_should_create_file_from_merb_temp_file
  28 + assert_created do
  29 + attachment = upload_merb_file :filename => '/files/foo.txt'
  30 + assert_valid attachment
  31 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  32 + assert attachment.image?
  33 + assert !attachment.size.zero?
  34 + end
  35 + end
  36 +
27 37 def test_should_create_image_from_uploaded_file_with_custom_content_type
28 38 assert_created do
29 39 attachment = upload_file :content_type => 'foo/bar', :filename => '/files/rails.png'
... ...
vendor/plugins/attachment_fu/test/fixtures/attachment.rb
... ... @@ -3,11 +3,16 @@ class Attachment &lt; ActiveRecord::Base
3 3 cattr_accessor :saves
4 4 has_attachment :processor => :rmagick
5 5 validates_as_attachment
6   - after_attachment_saved do |record|
  6 + after_save do |record|
7 7 self.saves += 1
8 8 end
9 9 end
10 10  
  11 +class LowerQualityAttachment < Attachment
  12 + self.table_name = 'attachments'
  13 + has_attachment :resize_to => [55,55], :jpeg_quality => 50
  14 +end
  15 +
11 16 class SmallAttachment < Attachment
12 17 has_attachment :max_size => 1.kilobyte
13 18 end
... ... @@ -34,9 +39,26 @@ end
34 39  
35 40 class ImageWithThumbsAttachment < Attachment
36 41 has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55]
37   - after_resize do |record, img|
38   - record.aspect_ratio = img.columns.to_f / img.rows.to_f
39   - end
  42 + # after_resize do |record, img|
  43 + # record.aspect_ratio = img.columns.to_f / img.rows.to_f
  44 + # end
  45 +end
  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 + }
40 62 end
41 63  
42 64 class FileAttachment < ActiveRecord::Base
... ... @@ -44,6 +66,38 @@ class FileAttachment &lt; ActiveRecord::Base
44 66 validates_as_attachment
45 67 end
46 68  
  69 +class FileAttachmentWithStringId < ActiveRecord::Base
  70 + self.table_name = 'file_attachments_with_string_id'
  71 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
  72 + validates_as_attachment
  73 +
  74 + before_validation :auto_generate_id
  75 + before_save :auto_generate_id
  76 + @@last_id = 0
  77 +
  78 + private
  79 + def auto_generate_id
  80 + @@last_id += 1
  81 + self.id = "id_#{@@last_id}"
  82 + end
  83 +end
  84 +
  85 +class FileAttachmentWithUuid < ActiveRecord::Base
  86 + self.table_name = 'file_attachments_with_string_id'
  87 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick, :uuid_primary_key => true
  88 + validates_as_attachment
  89 +
  90 + before_validation :auto_generate_id
  91 + before_save :auto_generate_id
  92 + @@last_id = 0
  93 +
  94 + private
  95 + def auto_generate_id
  96 + @@last_id += 1
  97 + self.id = "%0127dx" % @@last_id
  98 + end
  99 +end
  100 +
47 101 class ImageFileAttachment < FileAttachment
48 102 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
49 103 :content_type => :image, :resize_to => [50,50]
... ... @@ -52,9 +106,9 @@ end
52 106 class ImageWithThumbsFileAttachment < FileAttachment
53 107 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
54 108 :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55]
55   - after_resize do |record, img|
56   - record.aspect_ratio = img.columns.to_f / img.rows.to_f
57   - end
  109 + # after_resize do |record, img|
  110 + # record.aspect_ratio = img.columns.to_f / img.rows.to_f
  111 + # end
58 112 end
59 113  
60 114 class ImageWithThumbsClassFileAttachment < FileAttachment
... ... @@ -78,7 +132,7 @@ end
78 132 class MinimalAttachment < ActiveRecord::Base
79 133 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
80 134 validates_as_attachment
81   -
  135 +
82 136 def filename
83 137 "#{id}.file"
84 138 end
... ... @@ -87,7 +141,22 @@ end
87 141 begin
88 142 class ImageScienceAttachment < ActiveRecord::Base
89 143 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
90   - :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  144 + :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
  145 + end
  146 +
  147 + class ImageScienceLowerQualityAttachment < ActiveRecord::Base
  148 + self.table_name = 'image_science_attachments'
  149 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  150 + :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  151 + :jpeg_quality => 75
  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 }
91 160 end
92 161 rescue MissingSourceFile
93 162 puts $!.message
... ... @@ -97,7 +166,21 @@ end
97 166 begin
98 167 class CoreImageAttachment < ActiveRecord::Base
99 168 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
100   - :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  169 + :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
  170 + end
  171 +
  172 + class LowerQualityCoreImageAttachment < CoreImageAttachment
  173 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  174 + :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  175 + :jpeg_quality => 50
  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 }
101 184 end
102 185 rescue MissingSourceFile
103 186 puts $!.message
... ... @@ -107,8 +190,57 @@ end
107 190 begin
108 191 class MiniMagickAttachment < ActiveRecord::Base
109 192 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
110   - :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  193 + :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
111 194 end
  195 +
  196 + class ImageThumbnailCrop < MiniMagickAttachment
  197 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  198 + :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"}
  199 +
  200 + # TODO this is a bad duplication, this method is in the MiniMagick Processor
  201 + def self.calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
  202 + # only crop if image is not smaller in both dimensions
  203 +
  204 + # special cases, image smaller in one dimension then thumbsize
  205 + if image_width < thumb_width
  206 + offset = (image_height / 2) - (thumb_height / 2)
  207 + command = "#{image_width}x#{thumb_height}+0+#{offset}"
  208 + elsif image_height < thumb_height
  209 + offset = (image_width / 2) - (thumb_width / 2)
  210 + command = "#{thumb_width}x#{image_height}+#{offset}+0"
  211 +
  212 + # normal thumbnail generation
  213 + # calculate height and offset y, width is fixed
  214 + elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height
  215 + height = image_width / thumb_aspect
  216 + offset = (image_height / 2) - (height / 2)
  217 + command = "#{image_width}x#{height}+0+#{offset}"
  218 + # calculate width and offset x, height is fixed
  219 + else
  220 + width = image_height * thumb_aspect
  221 + offset = (image_width / 2) - (width / 2)
  222 + command = "#{width}x#{image_height}+#{offset}+0"
  223 + end
  224 + # crop image
  225 + command
  226 + end
  227 + end
  228 +
  229 + class LowerQualityMiniMagickAttachment < ActiveRecord::Base
  230 + self.table_name = 'mini_magick_attachments'
  231 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  232 + :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  233 + :jpeg_quality => 50
  234 + end
  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 +
112 244 rescue MissingSourceFile
113 245 puts $!.message
114 246 puts "no Mini Magick"
... ... @@ -117,32 +249,49 @@ end
117 249 begin
118 250 class GD2Attachment < ActiveRecord::Base
119 251 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
120   - :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  252 + :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
121 253 end
122   -rescue MissingSourceFile
123   - puts $!.message
124   - puts "no GD2"
125   -end
126 254  
  255 + class LowerQualityGD2Attachment < GD2Attachment
  256 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  257 + :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  258 + :jpeg_quality => 50
  259 + end
127 260  
128   -begin
129   - class MiniMagickAttachment < ActiveRecord::Base
  261 + class GD2WithPerThumbJpegAttachment < GD2Attachment
130 262 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
131   - :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  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 }
132 267 end
133 268 rescue MissingSourceFile
  269 + puts $!.message
  270 + puts "no GD2"
134 271 end
135 272  
  273 +
136 274 begin
137 275 class S3Attachment < ActiveRecord::Base
138 276 has_attachment :storage => :s3, :processor => :rmagick, :s3_config_path => File.join(File.dirname(__FILE__), '../amazon_s3.yml')
139 277 validates_as_attachment
140 278 end
141 279  
  280 + class CloudFilesAttachment < ActiveRecord::Base
  281 + has_attachment :storage => :cloud_files, :processor => :rmagick, :cloudfiles_config_path => File.join(File.dirname(__FILE__), '../rackspace_cloudfiles.yml')
  282 + validates_as_attachment
  283 + end
  284 +
142 285 class S3WithPathPrefixAttachment < S3Attachment
143 286 has_attachment :storage => :s3, :path_prefix => 'some/custom/path/prefix', :processor => :rmagick
144 287 validates_as_attachment
145 288 end
  289 +
  290 + class CloudFilesWithPathPrefixAttachment < CloudFilesAttachment
  291 + has_attachment :storage => :cloud_files, :path_prefix => 'some/custom/path/prefix', :processor => :rmagick
  292 + validates_as_attachment
  293 + end
  294 +
146 295 rescue
147 296 puts "S3 error: #{$!}"
148 297 end
... ...
vendor/plugins/attachment_fu/test/fixtures/files/rails.jpg 0 → 100644

2.43 KB

vendor/plugins/attachment_fu/test/geometry_test.rb
... ... @@ -23,6 +23,13 @@ class GeometryTest &lt; Test::Unit::TestCase
23 23 "100" => [100, 128]
24 24 end
25 25  
  26 + def test_should_resize_no_height_with_x
  27 + assert_geometry 50, 64,
  28 + "50x" => [50, 64],
  29 + "60x" => [60, 77],
  30 + "100x" => [100, 128]
  31 + end
  32 +
26 33 def test_should_resize_with_percent
27 34 assert_geometry 50, 64,
28 35 "50x50%" => [25, 32],
... ... @@ -90,6 +97,12 @@ class GeometryTest &lt; Test::Unit::TestCase
90 97 "100>" => [50, 64]
91 98 end
92 99  
  100 + def test_should_resize_with_aspect
  101 + assert_geometry 50, 64,
  102 + "35x35!" => [35, 35],
  103 + "70x70!" => [70, 70]
  104 + end
  105 +
93 106 protected
94 107 def assert_geometry(width, height, values)
95 108 values.each do |geo, result|
... ...
vendor/plugins/attachment_fu/test/processors/core_image_test.rb
... ... @@ -14,14 +14,41 @@ class CoreImageTest &lt; Test::Unit::TestCase
14 14  
15 15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16 16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18  
18 19 # test exact resize dimensions
19 20 assert_equal 50, thumb.width
20 21 assert_equal 51, thumb.height
21 22  
22   - # test geometry string
  23 + # test geometry strings
23 24 assert_equal 31, geo.width
24 25 assert_equal 41, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 +
  29 + # This makes sure that we didn't overwrite the original file
  30 + # and will end up with a thumbnail instead of the original
  31 + assert_equal 42, attachment.width
  32 + assert_equal 55, attachment.height
  33 +
  34 + end
  35 +
  36 + def test_should_handle_jpeg_quality
  37 + attachment_model CoreImageAttachment
  38 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  39 + full_size = attachment.size
  40 + attachment_model LowerQualityCoreImageAttachment
  41 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  42 + lq_size = attachment.size
  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
25 52 end
26 53 else
27 54 def test_flunk
... ...
vendor/plugins/attachment_fu/test/processors/gd2_test.rb
... ... @@ -12,16 +12,36 @@ class GD2Test &lt; Test::Unit::TestCase
12 12 assert_equal 43, attachment.width
13 13 assert_equal 55, attachment.height
14 14  
15   - thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16   - geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  15 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  16 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18  
18 19 # test exact resize dimensions
19 20 assert_equal 50, thumb.width
20 21 assert_equal 51, thumb.height
21 22  
22   - # test geometry string
  23 + # test geometry strings
23 24 assert_equal 31, geo.width
24 25 assert_equal 40, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 + end
  29 +
  30 + def test_should_handle_jpeg_quality
  31 + attachment_model GD2Attachment
  32 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  33 + full_size = attachment.size
  34 + attachment_model LowerQualityGD2Attachment
  35 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  36 + lq_size = attachment.size
  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
25 45 end
26 46 else
27 47 def test_flunk
... ...
vendor/plugins/attachment_fu/test/processors/image_science_test.rb
... ... @@ -14,14 +14,37 @@ class ImageScienceTest &lt; Test::Unit::TestCase
14 14  
15 15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16 16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18  
18 19 # test exact resize dimensions
19 20 assert_equal 50, thumb.width
20 21 assert_equal 51, thumb.height
21 22  
22   - # test geometry string
  23 + # test geometry strings
23 24 assert_equal 31, geo.width
24 25 assert_equal 41, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 + end
  29 +
  30 + def test_should_handle_jpeg_quality
  31 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  32 + full_size = attachment.size
  33 + attachment_model ImageScienceLowerQualityAttachment
  34 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  35 + lq_size = attachment.size
  36 + if ImageScience.instance_method(:save).arity == -2 # tdd-image_science: JPEG quality processing
  37 + assert lq_size <= full_size * 0.75, 'Lower-quality JPEG filesize should be congruently smaller'
  38 + else
  39 + assert_equal full_size, lq_size, 'Unsupported lower-quality JPEG should yield exact same file size'
  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
25 48 end
26 49 else
27 50 def test_flunk
... ...
vendor/plugins/attachment_fu/test/processors/mini_magick_test.rb
... ... @@ -14,18 +14,109 @@ class MiniMagickTest &lt; Test::Unit::TestCase
14 14  
15 15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16 16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18  
18 19 # test exact resize dimensions
19 20 assert_equal 50, thumb.width
20 21 assert_equal 51, thumb.height
21 22  
22   - # test geometry string
  23 + # test geometry strings
23 24 assert_equal 31, geo.width
24 25 assert_equal 40, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 + end
  29 +
  30 + def test_should_crop_image(klass = ImageThumbnailCrop)
  31 + attachment_model klass
  32 + attachment = upload_file :filename => '/files/rails.png'
  33 + assert_valid attachment
  34 + assert attachment.image?
  35 + # has_attachment :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"}
  36 +
  37 + square = attachment.thumbnails.detect { |t| t.filename =~ /_square/ }
  38 + vertical = attachment.thumbnails.detect { |t| t.filename =~ /_vertical/ }
  39 + horizontal = attachment.thumbnails.detect { |t| t.filename =~ /_horizontal/ }
  40 +
  41 + # test excat resize
  42 + assert_equal 50, square.width
  43 + assert_equal 50, square.height
  44 +
  45 + assert_equal 30, vertical.width
  46 + assert_equal 60, vertical.height
  47 +
  48 + assert_equal 60, horizontal.width
  49 + assert_equal 30, horizontal.height
  50 + end
  51 +
  52 + # tests the first step in resize, crop the image in original size to right format
  53 + def test_should_crop_image_right(klass = ImageThumbnailCrop)
  54 + @@testcases.collect do |testcase|
  55 + image_width, image_height, thumb_width, thumb_height = testcase[:data]
  56 + image_aspect, thumb_aspect = image_width/image_height, thumb_width/thumb_height
  57 + crop_comand = klass.calculate_offset(image_width, image_height, image_aspect, thumb_width, thumb_height,thumb_aspect)
  58 + # pattern matching on crop command
  59 + if testcase.has_key?(:height)
  60 + assert crop_comand.match(/^#{image_width}x#{testcase[:height]}\+0\+#{testcase[:yoffset]}$/)
  61 + else
  62 + assert crop_comand.match(/^#{testcase[:width]}x#{image_height}\+#{testcase[:xoffset]}\+0$/)
  63 + end
  64 + end
  65 + end
  66 +
  67 + def test_should_handle_jpeg_quality
  68 + attachment_model MiniMagickAttachment
  69 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  70 + full_size = attachment.size
  71 + attachment_model LowerQualityMiniMagickAttachment
  72 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  73 + lq_size = attachment.size
  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
25 82 end
26 83 else
27 84 def test_flunk
28 85 puts "MiniMagick not loaded, tests not running"
29 86 end
30 87 end
  88 +
  89 + @@testcases = [
  90 + # image_aspect <= 1 && thumb_aspect >= 1
  91 + {:data => [10.0,40.0,2.0,1.0], :height => 5.0, :yoffset => 17.5}, # 1b
  92 + {:data => [10.0,40.0,1.0,1.0], :height => 10.0, :yoffset => 15.0}, # 1b
  93 +
  94 + # image_aspect < 1 && thumb_aspect < 1
  95 + {:data => [10.0,40.0,1.0,2.0], :height => 20.0, :yoffset => 10.0}, # 1a
  96 + {:data => [2.0,3.0,1.0,2.0], :width => 1.5, :xoffset => 0.25}, # 1a
  97 +
  98 + # image_aspect = thumb_aspect
  99 + {:data => [10.0,10.0,1.0,1.0], :height => 10.0, :yoffset => 0.0}, # QUADRAT 1c
  100 +
  101 + # image_aspect >= 1 && thumb_aspect > 1 && image_aspect < thumb_aspect
  102 + {:data => [6.0,3.0,4.0,1.0], :height => 1.5, :yoffset => 0.75}, # 2b
  103 + {:data => [6.0,6.0,4.0,1.0], :height => 1.5, :yoffset => 2.25}, # 2b
  104 +
  105 + # image_aspect > 1 && thumb_aspect > 1 && image_aspect > thumb_aspect
  106 + {:data => [9.0,3.0,2.0,1.0], :width => 6.0, :xoffset => 1.5}, # 2a
  107 +
  108 + # image_aspect > 1 && thumb_aspect < 1 && image_aspect < thumb_aspect
  109 + {:data => [10.0,5.0,0.1,2.0], :width => 0.25, :xoffset => 4.875}, # 4
  110 + {:data => [10.0,5.0,1.0,2.0], :width => 2.5, :xoffset => 3.75}, # 4
  111 +
  112 + # image_aspect > 1 && thumb_aspect > 1 && image_aspect > thumb_aspect
  113 + {:data => [9.0,3.0,2.0,1.0], :width => 6.0, :xoffset => 1.5}, # 3a
  114 + # image_aspect > 1 && thumb_aspect > 1 && image_aspect < thumb_aspect
  115 + {:data => [9.0,3.0,5.0,1.0], :height => 1.8, :yoffset => 0.6} # 3a
  116 + ]
  117 +
  118 +
  119 +
  120 +
  121 +
31 122 end
... ...
vendor/plugins/attachment_fu/test/processors/rmagick_test.rb
1 1 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
2   -
3 2 class RmagickTest < Test::Unit::TestCase
4 3 attachment_model Attachment
5 4  
... ... @@ -49,20 +48,21 @@ class RmagickTest &lt; Test::Unit::TestCase
49 48 end
50 49 end
51 50  
52   - def test_should_create_thumbnail_with_geometry_string
  51 + def test_should_create_thumbnail_with_geometry_strings
53 52 attachment = upload_file :filename => '/files/rails.png'
54 53  
55 54 assert_created do
56 55 basename, ext = attachment.filename.split '.'
57   - thumbnail = attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 'x50')
58   - assert_valid thumbnail
59   - assert !thumbnail.size.zero?
60   - #assert_equal 3915, thumbnail.size
61   - assert_equal 39, thumbnail.width
62   - assert_equal 50, thumbnail.height
63   - assert_equal [thumbnail], attachment.thumbnails
64   - assert_equal attachment.id, thumbnail.parent_id if thumbnail.respond_to?(:parent_id)
65   - assert_equal "#{basename}_thumb.#{ext}", thumbnail.filename
  56 + { 'x50' => [39, 50], '25x25!' => [25, 25] }.each do |geo, (w, h)|
  57 + thumbnail = attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', geo)
  58 + assert_valid thumbnail
  59 + assert !thumbnail.size.zero?
  60 + assert_equal w, thumbnail.width
  61 + assert_equal h, thumbnail.height
  62 + assert_equal [thumbnail], attachment.thumbnails
  63 + assert_equal attachment.id, thumbnail.parent_id if thumbnail.respond_to?(:parent_id)
  64 + assert_equal "#{basename}_thumb.#{ext}", thumbnail.filename
  65 + end
66 66 end
67 67 end
68 68  
... ... @@ -123,7 +123,7 @@ class RmagickTest &lt; Test::Unit::TestCase
123 123 assert_equal 55, attachment.width
124 124 assert_equal 55, attachment.height
125 125 assert_equal 2, attachment.thumbnails.length
126   - assert_equal 1.0, attachment.aspect_ratio
  126 + # assert_equal 1.0, attachment.aspect_ratio
127 127  
128 128 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
129 129 assert !thumb.new_record?, thumb.errors.full_messages.join("\n")
... ... @@ -131,7 +131,7 @@ class RmagickTest &lt; Test::Unit::TestCase
131 131 #assert_in_delta 4673, thumb.size, 2
132 132 assert_equal 50, thumb.width
133 133 assert_equal 50, thumb.height
134   - assert_equal 1.0, thumb.aspect_ratio
  134 + # assert_equal 1.0, thumb.aspect_ratio
135 135  
136 136 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
137 137 assert !geo.new_record?, geo.errors.full_messages.join("\n")
... ... @@ -139,7 +139,7 @@ class RmagickTest &lt; Test::Unit::TestCase
139 139 #assert_equal 3915, geo.size
140 140 assert_equal 50, geo.width
141 141 assert_equal 50, geo.height
142   - assert_equal 1.0, geo.aspect_ratio
  142 + # assert_equal 1.0, geo.aspect_ratio
143 143 end
144 144 end
145 145  
... ... @@ -181,7 +181,7 @@ class RmagickTest &lt; Test::Unit::TestCase
181 181 assert_not_created do
182 182 use_temp_file "files/rails.png" do |file|
183 183 attachment.filename = 'rails2.png'
184   - attachment.temp_path = File.join(fixture_path, file)
  184 + attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
185 185 attachment.save
186 186 new_filenames = [attachment.reload.full_filename] + attachment.thumbnails.collect { |t| t.reload.full_filename }
187 187 new_filenames.each { |f| assert File.exists?(f), "#{f} does not exist" }
... ... @@ -224,7 +224,7 @@ class RmagickTest &lt; Test::Unit::TestCase
224 224 # #temp_path calls #full_filename, which is not getting mixed into the attachment. Maybe we don't need to
225 225 # set temp_path at all?
226 226 #
227   - # attachment.temp_path = File.join(fixture_path, file)
  227 + # attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
228 228 attachment.save!
229 229 end
230 230 end
... ... @@ -247,6 +247,23 @@ class RmagickTest &lt; Test::Unit::TestCase
247 247 end
248 248  
249 249 test_against_subclass :test_should_overwrite_old_thumbnail_records_when_renaming, ImageWithThumbsAttachment
  250 +
  251 + def test_should_handle_jpeg_quality
  252 + attachment_model ImageWithThumbsAttachment
  253 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  254 + full_size = attachment.size
  255 + attachment_model LowerQualityAttachment
  256 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  257 + lq_size = attachment.size
  258 + assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
  259 +
  260 + attachment_model ImageWithPerThumbJpegAttachment
  261 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  262 + assert_file_jpeg_quality attachment, :thumb, 90
  263 + assert_file_jpeg_quality attachment, :avatar, 85
  264 + assert_file_jpeg_quality attachment, :large, 75
  265 + assert_file_jpeg_quality attachment, nil, 75
  266 + end
250 267 else
251 268 def test_flunk
252 269 puts "RMagick not installed, no tests running"
... ...
vendor/plugins/attachment_fu/test/schema.rb
... ... @@ -2,6 +2,8 @@ ActiveRecord::Schema.define(:version =&gt; 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
... ... @@ -22,6 +24,19 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
22 24 t.column :type, :string
23 25 t.column :aspect_ratio, :float
24 26 end
  27 +
  28 + create_table :file_attachments_with_string_id, :id => false, :force => true do |t|
  29 + t.column :id, :string
  30 + t.column :parent_id, :string
  31 + t.column :thumbnail, :string
  32 + t.column :filename, :string, :limit => 255
  33 + t.column :content_type, :string, :limit => 255
  34 + t.column :size, :integer
  35 + t.column :width, :integer
  36 + t.column :height, :integer
  37 + t.column :type, :string
  38 + t.column :aspect_ratio, :float
  39 + end
25 40  
26 41 create_table :gd2_attachments, :force => true do |t|
27 42 t.column :parent_id, :integer
... ... @@ -105,4 +120,17 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
105 120 t.column :type, :string
106 121 t.column :aspect_ratio, :float
107 122 end
108   -end
109 123 \ No newline at end of file
  124 +
  125 + create_table :cloud_files_attachments, :force => true do |t|
  126 + t.column :parent_id, :integer
  127 + t.column :thumbnail, :string
  128 + t.column :filename, :string, :limit => 255
  129 + t.column :content_type, :string, :limit => 255
  130 + t.column :size, :integer
  131 + t.column :width, :integer
  132 + t.column :height, :integer
  133 + t.column :type, :string
  134 + t.column :aspect_ratio, :float
  135 + end
  136 +
  137 +end
... ...
vendor/plugins/attachment_fu/test/test_helper.rb
1   -$:.unshift(File.dirname(__FILE__) + '/../lib')
  1 +$LOAD_PATH.unshift(File.dirname(__FILE__))
  2 +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2 3  
3 4 ENV['RAILS_ENV'] = 'test'
4   -ENV['Rails.root'] ||= File.dirname(__FILE__) + '/../../../..'
5 5  
  6 +require 'rails/all'
6 7 require 'test/unit'
7   -require File.expand_path(File.join(ENV['Rails.root'], 'config/environment.rb'))
8   -require 'active_record/fixtures'
9   -require 'action_controller/test_process'
10   -
11   -config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
12   -ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
13   -
14   -db_adapter = ENV['DB']
15   -
16   -# no db passed, try one of these fine config-free DBs before bombing.
17   -db_adapter ||=
18   - begin
19   - require 'rubygems'
20   - require 'sqlite'
21   - 'sqlite'
22   - rescue MissingSourceFile
23   - begin
24   - require 'sqlite3'
25   - 'sqlite3'
26   - rescue MissingSourceFile
27   - end
  8 +require 'pothoven-attachment_fu'
  9 +include ActionDispatch::TestProcess
  10 +
  11 +# Define the application and configuration
  12 +module RbConfig
  13 + class Application < ::Rails::Application
  14 + # configuration here if needed
  15 + config.active_support.deprecation = :stderr
28 16 end
29   -
30   -if db_adapter.nil?
31   - raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
32 17 end
33 18  
34   -ActiveRecord::Base.establish_connection(config[db_adapter])
  19 +# Initialize the application
  20 +RbConfig::Application.initialize!
35 21  
  22 +# Setup database
36 23 load(File.dirname(__FILE__) + "/schema.rb")
37 24  
38   -Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures"
39   -$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
  25 +FIXTURE_PATH = File.dirname(__FILE__) + "/fixtures"
  26 +$LOAD_PATH.unshift(FIXTURE_PATH)
40 27  
41 28 class Test::Unit::TestCase #:nodoc:
42   - include ActionController::TestProcess
  29 + # include ActionDispatch::TestProcess
43 30 def create_fixtures(*table_names)
44 31 if block_given?
45   - Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
  32 + Fixtures.create_fixtures(FIXTURE_PATH, table_names) { yield }
46 33 else
47   - Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
  34 + Fixtures.create_fixtures(FIXTURE_PATH, table_names)
48 35 end
49 36 end
50 37  
... ... @@ -53,16 +40,18 @@ class Test::Unit::TestCase #:nodoc:
53 40 DbFile.transaction { [Attachment, FileAttachment, OrphanAttachment, MinimalAttachment, DbFile].each { |klass| klass.delete_all } }
54 41 attachment_model self.class.attachment_model
55 42 end
56   -
  43 +
57 44 def teardown
58 45 FileUtils.rm_rf File.join(File.dirname(__FILE__), 'files')
  46 + # Files generated by random_tempfile_filename
  47 + FileUtils.rm_rf Dir['[0-9]*.{png,jpg}']
59 48 end
60 49  
61   - self.use_transactional_fixtures = true
62   - self.use_instantiated_fixtures = false
  50 + #self.use_transactional_fixtures = true
  51 + #self.use_instantiated_fixtures = false
63 52  
64 53 def self.attachment_model(klass = nil)
65   - @attachment_model = klass if klass
  54 + @attachment_model = klass if klass
66 55 @attachment_model
67 56 end
68 57  
... ... @@ -81,7 +70,18 @@ class Test::Unit::TestCase #:nodoc:
81 70 protected
82 71 def upload_file(options = {})
83 72 use_temp_file options[:filename] do |file|
84   - att = attachment_model.create :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png')
  73 +puts options
  74 + opts = { :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png') }
  75 + opts.update(options.reject { |k, v| ![:imageable_type, :imageable_id].include?(k) })
  76 + att = attachment_model.create opts
  77 + att.reload unless att.new_record?
  78 + return att
  79 + end
  80 + end
  81 +
  82 + def upload_merb_file(options = {})
  83 + use_temp_file options[:filename] do |file|
  84 + att = attachment_model.create :uploaded_data => {"size" => file.size, "content_type" => options[:content_type] || 'image/png', "filename" => file, 'tempfile' => fixture_file_upload(file, options[:content_type] || 'image/png')}
85 85 att.reload unless att.new_record?
86 86 return att
87 87 end
... ... @@ -89,11 +89,13 @@ class Test::Unit::TestCase #:nodoc:
89 89  
90 90 def use_temp_file(fixture_filename)
91 91 temp_path = File.join('/tmp', File.basename(fixture_filename))
92   - FileUtils.mkdir_p File.join(fixture_path, 'tmp')
93   - FileUtils.cp File.join(fixture_path, fixture_filename), File.join(fixture_path, temp_path)
94   - yield temp_path
  92 + temp_dir = File.join(FIXTURE_PATH, 'tmp')
  93 + use_file = File.join(FIXTURE_PATH, temp_path)
  94 + FileUtils.mkdir_p temp_dir
  95 + FileUtils.cp File.join(FIXTURE_PATH, fixture_filename), use_file
  96 + yield use_file
95 97 ensure
96   - FileUtils.rm_rf File.join(fixture_path, 'tmp')
  98 + FileUtils.rm_rf temp_dir
97 99 end
98 100  
99 101 def assert_created(num = 1)
... ... @@ -107,11 +109,36 @@ class Test::Unit::TestCase #:nodoc:
107 109 end
108 110 end
109 111 end
110   -
  112 +
  113 + def assert_valid(record)
  114 + assert record.valid?, record.errors.full_messages.join("\n")
  115 + end
  116 +
  117 + def assert_file_jpeg_quality(model, thumbnail, expected)
  118 + filename = if model.respond_to?(:full_filename)
  119 + model.full_filename(thumbnail)
  120 + else
  121 + thumb = thumbnail ? model.thumbnails.find(:first, :conditions => { :thumbnail => thumbnail.to_s }, :include => :db_file) : model
  122 + unless thumb && thumb.db_file && thumb.db_file.data && thumb.db_file.data.size > 0
  123 + STDERR.puts "Cannot find DB file data for thumbnail #{thumbnail.inspect} -> Aborting JPEG quality check."
  124 + return
  125 + end
  126 + result = Tempfile.new('dbfile_dump').path
  127 + File.open(result, 'wb') { |f| f.write(thumb.db_file.data) }
  128 + result
  129 + end
  130 + quality = %x(identify -format '%Q' "#{filename}" 2> /dev/null)
  131 + if $?.success?
  132 + assert_equal expected, quality.to_i, "Produced JPEG quality (thumbnail: #{thumbnail.inspect}) is incorrect."
  133 + else
  134 + STDERR.puts "ImageMagick's identify not found / not in PATH: can't quickly check produced image quality."
  135 + end
  136 + end
  137 +
111 138 def assert_not_created
112 139 assert_created(0) { yield }
113 140 end
114   -
  141 +
115 142 def should_reject_by_size_with(klass)
116 143 attachment_model klass
117 144 assert_not_created do
... ... @@ -121,22 +148,22 @@ class Test::Unit::TestCase #:nodoc:
121 148 assert_nil attachment.db_file if attachment.respond_to?(:db_file)
122 149 end
123 150 end
124   -
  151 +
125 152 def assert_difference(object, method = nil, difference = 1)
126 153 initial_value = object.send(method)
127 154 yield
128 155 assert_equal initial_value + difference, object.send(method)
129 156 end
130   -
  157 +
131 158 def assert_no_difference(object, method, &block)
132 159 assert_difference object, method, 0, &block
133 160 end
134   -
  161 +
135 162 def attachment_model(klass = nil)
136   - @attachment_model = klass if klass
  163 + @attachment_model = klass if klass
137 164 @attachment_model
138 165 end
139 166 end
140 167  
141 168 require File.join(File.dirname(__FILE__), 'fixtures/attachment')
142   -require File.join(File.dirname(__FILE__), 'base_attachment_tests')
143 169 \ No newline at end of file
  170 +require File.join(File.dirname(__FILE__), 'base_attachment_tests')
... ...
vendor/plugins/attachment_fu/test/validation_test.rb
... ... @@ -4,52 +4,52 @@ class ValidationTest &lt; Test::Unit::TestCase
4 4 def test_should_invalidate_big_files
5 5 @attachment = SmallAttachment.new
6 6 assert !@attachment.valid?
7   - assert @attachment.errors.on(:size)
  7 + assert @attachment.errors[:size]
8 8  
9 9 @attachment.size = 2000
10 10 assert !@attachment.valid?
11   - assert @attachment.errors.on(:size), @attachment.errors.full_messages.to_sentence
  11 + assert @attachment.errors[:size], @attachment.errors.full_messages.to_sentence
12 12  
13 13 @attachment.size = 1000
14 14 assert !@attachment.valid?
15   - assert_nil @attachment.errors.on(:size)
  15 + assert @attachment.errors[:size].empty?
16 16 end
17 17  
18 18 def test_should_invalidate_small_files
19 19 @attachment = BigAttachment.new
20 20 assert !@attachment.valid?
21   - assert @attachment.errors.on(:size)
  21 + assert @attachment.errors[:size]
22 22  
23 23 @attachment.size = 2000
24 24 assert !@attachment.valid?
25   - assert @attachment.errors.on(:size), @attachment.errors.full_messages.to_sentence
  25 + assert @attachment.errors[:size], @attachment.errors.full_messages.to_sentence
26 26  
27 27 @attachment.size = 1.megabyte
28 28 assert !@attachment.valid?
29   - assert_nil @attachment.errors.on(:size)
  29 + assert @attachment.errors[:size].empty?
30 30 end
31 31  
32 32 def test_should_validate_content_type
33 33 @attachment = PdfAttachment.new
34 34 assert !@attachment.valid?
35   - assert @attachment.errors.on(:content_type)
  35 + assert @attachment.errors[:content_type]
36 36  
37 37 @attachment.content_type = 'foo'
38 38 assert !@attachment.valid?
39   - assert @attachment.errors.on(:content_type)
  39 + assert @attachment.errors[:content_type]
40 40  
41 41 @attachment.content_type = 'pdf'
42 42 assert !@attachment.valid?
43   - assert_nil @attachment.errors.on(:content_type)
  43 + assert @attachment.errors[:content_type].empty?
44 44 end
45 45  
46 46 def test_should_require_filename
47 47 @attachment = Attachment.new
48 48 assert !@attachment.valid?
49   - assert @attachment.errors.on(:filename)
  49 + assert @attachment.errors[:filename]
50 50  
51 51 @attachment.filename = 'foo'
52 52 assert !@attachment.valid?
53   - assert_nil @attachment.errors.on(:filename)
  53 + assert @attachment.errors[:filename].empty?
54 54 end
55 55 end
56 56 \ No newline at end of file
... ...