You are here: Home > Latest news from Darcs > Attachment Fu and MiniMagick libraries for image uploading

Revision 20080728221315-9043f-cff771...

Attachment Fu and MiniMagick libraries for image uploading

vendor/mini_magick-1.2.3/History.txt
vendor/mini_magick-1.2.3/MIT-LICENSE
vendor/mini_magick-1.2.3/Manifest.txt
vendor/mini_magick-1.2.3/README.txt
vendor/mini_magick-1.2.3/Rakefile
vendor/mini_magick-1.2.3/init.rb
vendor/mini_magick-1.2.3/lib/image_temp_file.rb
vendor/mini_magick-1.2.3/lib/mini_magick.rb
vendor/mini_magick.rb
vendor/plugins/attachment_fu/.gitignore
vendor/plugins/attachment_fu/CHANGELOG
vendor/plugins/attachment_fu/README
vendor/plugins/attachment_fu/Rakefile
vendor/plugins/attachment_fu/amazon_s3.yml.tpl
vendor/plugins/attachment_fu/init.rb
vendor/plugins/attachment_fu/install.rb
vendor/plugins/attachment_fu/lib/geometry.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/db_file_backend.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/file_system_backend.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/core_image_processor.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/gd2_processor.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/image_science_processor.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/color.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/effects.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/perspective.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/quality.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/scale.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/watermark.rb
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/processor.rb

Changes to History.txt

 
== 1.2.2 / 2007-06-01
1
 
2
 
# 1.) all image commands return the image object (The output of the last command is saved in @output)
3
 
# 2.) identify doesn't trip over strangley named files
4
 
# 3.) TempFile uses file extention now (Thanks http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions)
5
 
# 4.) identify commands escape output path correctly
6
 
7
 
== 1.2.3 / 2007-06-15
8
 
9
 
# 1.) Image::from_file doesn't drop the file extension anymore, it and use the image_temp_file correctly
10
 
# 4.) TempFiles are stored as an instance variable in Image instances so they don't get cleaned up prematurely via garbage collection
11

Changes to MIT-LICENSE

 
Copyright (c) 2005 Corey Johnson probablycorey@gmail.com
1
 
2
 
Permission is hereby granted, free of charge, to any person obtaining
3
 
a copy of this software and associated documentation files (the
4
 
"Software"), to deal in the Software without restriction, including
5
 
without limitation the rights to use, copy, modify, merge, publish,
6
 
distribute, sublicense, and/or sell copies of the Software, and to
7
 
permit persons to whom the Software is furnished to do so, subject to
8
 
the following conditions:
9
 
10
 
The above copyright notice and this permission notice shall be
11
 
included in all copies or substantial portions of the Software.
12
 
13
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
 
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
 
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17
 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18
 
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
 
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
 
21

Changes to Manifest.txt

 
MIT-LICENSE
1
 
History.txt
2
 
Manifest.txt
3
 
README.txt
4
 
Rakefile
5
 
init.rb
6
 
lib/image_temp_file.rb
7
 
lib/mini_magick.rb
8
 
test/actually_a_gif.jpg
9
 
test/not_an_image.php
10
 
test/simple.gif
11
 
test/test_image_temp_file.rb
12
 
test/test_mini_magick_test.rb
13
 
test/trogdor.jpg
14

Changes to README.txt

 
mini_magick
1
 
    by Coery Johnson
2
 
    FIX (url)
3
 
4
 
== DESCRIPTION:
5
 
  
6
 
A ruby wrapper for ImageMagick command line.
7
 
8
 
- Why?
9
 
10
 
I was using RMagick and loving it, but it was eating up huge amounts of memory. A simple script like this...
11
 
12
 
Magick::read("image.jpg") do |f|
13
 
 f.write("manipulated.jpg")
14
 
end
15
 
16
 
...would use over 100 Megs of Ram. On my local machine this wasn't a problem, but on my hosting server the ruby apps would crash because of their 100 Meg memory limit.
17
 
18
 
- Solution!
19
 
20
 
Using MiniMagick the ruby processes memory remains small (it spawns ImageMagick's command line program mogrify which takes up some memory as well, but is much smaller compared to RMagick)
21
 
22
 
23
 
== FEATURES/PROBLEMS:
24
 
  
25
 
MiniMagick gives you access to all the commandline options ImageMagick has (Found here http://www.imagemagick.org/script/mogrify.php)
26
 
27
 
== SYNOPSIS:
28
 
29
 
Want to make a thumbnail from a file...
30
 
31
 
image = MiniMagick::Image.from_file("input.jpg")
32
 
image.resize "100x100"
33
 
image.write("output.jpg")
34
 
35
 
Want to make a thumbnail from a blob...
36
 
37
 
image = MiniMagick::Image.from_blob(blob)
38
 
image.resize "100x100"
39
 
image.write("output.jpg")
40
 
41
 
Need to combine several options?
42
 
43
 
image = MiniMagick::Image.from_file("input.jpg")
44
 
image.combine_options do |c|
45
 
  c.sample "50%"
46
 
  c.rotate "-90>"
47
 
end
48
 
image.write("output.jpg")
49
 
50
 
Want to manipulate an image at its source (You won't have to write it out because the transformations are done on that file)
51
 
52
 
image = MiniMagick::Image.new("input.jpg")
53
 
image.resize "100x100"
54
 
55
 
Want to get some meta-information out?
56
 
57
 
image = MiniMagick::Image.from_file("input.jpg")
58
 
image[:width] # will get the width (you can also use :height and :format)
59
 
image["EXIF:BitsPerSample"] # It also can get all the EXIF tags
60
 
image["%m:%f %wx%h"] # Or you can use one of the many options of the format command found here http://www.imagemagick.org/script/command-line-options.php#format
61
 
62
 
63
 
== REQUIREMENTS:
64
 
65
 
You must have ImageMagick installed.
66
 
67
 
== INSTALL:
68
 
69
 
If you downloaded the plugin version, just drop the plugin into RAILS_ROOT/plugins/
70
 
71
 
If you installed this as a gem, then to get it to work add <require "mini_magick"> to RAILS_ROOT/config/environment.rb
72
 
73
 
If you have just downloaded this files then copy the mini_magick.rb file into your RAILS_ROOT/lib directory and add <require "mini-magick"> to RAILS_ROOT/config/environment.rb
74
 
75
 
MiniMagick does NOT require rails though. All the code you need to use MiniMagick is located in the mini_magick/lib/mini_magick.rb file.
76
 
77
 
== LICENSE:
78
 
79
 
(The MIT License)
80
 
81
 
Copyright (c) 2007 FIX
82
 
83
 
Permission is hereby granted, free of charge, to any person obtaining
84
 
a copy of this software and associated documentation files (the
85
 
'Software'), to deal in the Software without restriction, including
86
 
without limitation the rights to use, copy, modify, merge, publish,
87
 
distribute, sublicense, and/or sell copies of the Software, and to
88
 
permit persons to whom the Software is furnished to do so, subject to
89
 
the following conditions:
90
 
91
 
The above copyright notice and this permission notice shall be
92
 
included in all copies or substantial portions of the Software.
93
 
94
 
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
95
 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
96
 
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
97
 
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
98
 
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
99
 
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
100
 
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
101

Changes to Rakefile

 
# -*- ruby -*-
1
 
2
 
require 'rubygems'
3
 
require 'hoe'
4
 
require './lib/mini_magick.rb'
5
 
6
 
Hoe.new('mini_magick', MiniMagick::VERSION) do |p|
7
 
  p.rubyforge_name = 'mini_magick'
8
 
  p.author = 'Corey Johnson'
9
 
  p.email = 'probablycorey+ruby@gmail.com'
10
 
  p.summary = 'A simple image manipulation library based on ImageMagick.'
11
 
  p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
12
 
  #p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
13
 
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
 
end
15
 
16
 
# vim: syntax=Ruby
17

Changes to init.rb

 
require 'mini_magick'
1
 
require 'image_temp_file'
2

Changes to image_temp_file.rb

 
require "tempfile"
1
 
2
 
module MiniMagick
3
 
  class ImageTempFile < Tempfile
4
 
    def make_tmpname(basename, n)
5
 
      # force tempfile to use basename's extension if provided
6
 
      ext = File.extname(basename)
7
 
    
8
 
      # force hyphens instead of periods in name
9
 
      sprintf('%s%d-%d%s', File.basename(basename, ext), $$, n, ext)
10
 
    end
11
 
  end
12
 
end
13

Changes to mini_magick.rb

 
require "open-uri"
1
 
require "stringio"
2
 
require "fileutils"
3
 
4
 
require File.join(File.dirname(__FILE__), '/image_temp_file')
5
 
6
 
module MiniMagick
7
 
  class MiniMagickError < RuntimeError; end
8
 
9
 
  VERSION = '1.2.3'
10
 
11
 
  class Image
12
 
    attr :path
13
 
    attr :tempfile
14
 
    attr :output
15
 
16
 
    # Class Methods
17
 
    # -------------
18
 
    class <<self
19
 
      def from_blob(blob, extension=nil)
20
 
        begin
21
 
          tempfile = ImageTempFile.new("minimagick#{extension}")
22
 
          tempfile.binmode
23
 
          tempfile.write(blob)
24
 
        ensure
25
 
          tempfile.close
26
 
        end
27
 
        
28
 
        return self.new(tempfile.path, tempfile)
29
 
      end
30
 
31
 
      # Use this if you don't want to overwrite the image file
32
 
      def from_file(image_path)
33
 
        File.open(image_path, "rb") do |f|
34
 
          self.from_blob(f.read, File.extname(image_path))
35
 
        end
36
 
      end
37
 
    end
38
 
39
 
    # Instance Methods
40
 
    # ----------------
41
 
    def initialize(input_path, tempfile=nil)
42
 
      @path = input_path
43
 
      @tempfile = tempfile # ensures that the tempfile will stick around until this image is garbage collected.
44
 
45
 
      # Ensure that the file is an image
46
 
      run_command("identify", @path)
47
 
    end
48
 
49
 
    # For reference see http://www.imagemagick.org/script/command-line-options.php#format
50
 
    def [](value)
51
 
      # Why do I go to the trouble of putting in newlines? Because otherwise animated gifs screw everything up
52
 
      case value.to_s
53
 
      when "format"
54
 
        run_command("identify", "-format", format_option("%m"), @path).split("\n")[0]
55
 
      when "height"
56
 
        run_command("identify", "-format", format_option("%h"), @path).split("\n")[0].to_i
57
 
      when "width"
58
 
        run_command("identify", "-format", format_option("%w"), @path).split("\n")[0].to_i
59
 
      when "original_at"
60
 
        # Get the EXIF original capture as a Time object
61
 
        Time.local(*self["EXIF:DateTimeOriginal"].split(/:|\s+/)) rescue nil
62
 
      when /^EXIF\:/i
63
 
        run_command('identify', '-format', "\"%[#{value}]\"", @path).chop
64
 
      else
65
 
        run_command('identify', '-format', "\"#{value}\"", @path).split("\n")[0]
66
 
      end
67
 
    end
68
 
69
 
    # This is a 'special' command because it needs to change @path to reflect the new extension
70
 
    def format(format)
71
 
      run_command("mogrify", "-format", format, @path)
72
 
      @path = @path.sub(/(\.\w+)?$/, ".#{format}")
73
 
      
74
 
      raise "Unable to format to #{format}" unless File.exists?(@path)
75
 
    end
76
 
77
 
    # Writes the temporary image that we are using for processing to the output path
78
 
    def write(output_path)
79
 
      FileUtils.copy_file @path, output_path
80
 
      run_command "identify", output_path # Verify that we have a good image
81
 
    end
82
 
83
 
    # Give you raw data back
84
 
    def to_blob
85
 
      File.read @path
86
 
    end
87
 
88
 
    # If an unknown method is called then it is sent through the morgrify program
89
 
    # Look here to find all the commands (http://www.imagemagick.org/script/mogrify.php)
90
 
    def method_missing(symbol, *args)
91
 
      args.push(@path) # push the path onto the end
92
 
      run_command("mogrify", "-#{symbol}", *args)
93
 
      self
94
 
    end
95
 
96
 
    # You can use multiple commands together using this method
97
 
    def combine_options(&block)
98
 
      c = CommandBuilder.new
99
 
      block.call c
100
 
      run_command("mogrify", *c.args << @path)
101
 
    end
102
 
103
 
    # Check to see if we are running on win32 -- we need to escape things differently
104
 
    def windows?
105
 
      !(RUBY_PLATFORM =~ /win32/).nil?
106
 
    end
107
 
108
 
    # Outputs a carriage-return delimited format string for Unix and Windows
109
 
    def format_option(format)
110
 
      windows? ? "#{format}\\n" : "#{format}\\\\n"
111
 
    end
112
 
113
 
    def run_command(command, *args)
114
 
      args.collect! do |arg|
115
 
        arg = arg.to_s
116
 
        arg = %|"#{arg}"| unless arg[0] == ?- # values quoted because they can contain characters like '>', but don't quote switches          
117
 
        arg
118
 
      end
119
 
120
 
      @output = `#{command} #{args.join(' ')}`
121
 
122
 
      if $? != 0
123
 
        raise MiniMagickError, "ImageMagick command (#{command} #{args.join(' ')}) failed: Error Given #{$?}"
124
 
      else
125
 
        @output
126
 
      end
127
 
    end
128
 
  end
129
 
130
 
  class CommandBuilder
131
 
    attr :args
132
 
133
 
    def initialize
134
 
      @args = []
135
 
    end
136
 
137
 
    def method_missing(symbol, *args)
138
 
      @args << "-#{symbol}"
139
 
      @args += args
140
 
    end
141
 
    
142
 
    def +(value)
143
 
      @args << "+#{value}"
144
 
    end
145
 
  end
146
 
end
147

Changes to mini_magick.rb

 
require File.join(File.dirname(__FILE__), 'mini_magick-1.2.3/lib/mini_magick')
1

Changes to .gitignore

 
test/amazon_s3.yml
1
 
test/debug.log
2

Changes to CHANGELOG

 
* Apr 17 2008 *
1
 
* amazon_s3.yml is now passed through ERB before being passed to AWS::S3 [François Beausoleil]
2
 
3
 
* Mar 22 2008 *
4
 
* Some tweaks to support Rails 2.0 and Rails 2.1 due to ActiveSupport::Callback changes.  
5
 
  Thanks to http://blog.methodmissing.com/2008/1/19/edge-callback-refactorings-attachment_fu/
6
 
7
 
* Feb. 26, 2008 *
8
 
* remove breakpoint from test_helper, makes test suite crazy (at least Rails 2+) [Rob Sanheim]
9
 
* make S3 test really optional [Rob Sanheim]
10
 
11
 
* Nov 27, 2007 *
12
 
* Handle properly ImageScience thumbnails resized from a gif file [Matt Aimonetti]
13
 
* Save thumbnails file size properly when using ImageScience [Matt Aimonetti]
14
 
* fixed s3 config file loading with latest versions of Rails [Matt Aimonetti]
15
 
16
 
* April 2, 2007 *
17
 
18
 
* don't copy the #full_filename to the default #temp_paths array if it doesn't exist
19
 
* add default ID partitioning for attachments
20
 
* add #binmode call to Tempfile (note: ruby should be doing this!) [Eric Beland]
21
 
* Check for current type of :thumbnails option.
22
 
* allow customization of the S3 configuration file path with the :s3_config_path option.
23
 
* Don't try to remove thumbnails if there aren't any.  Closes #3 [ben stiglitz]
24
 
25
 
* BC * (before changelog)
26
 
27
 
* add default #temp_paths entry [mattly]
28
 
* add MiniMagick support to attachment_fu [Isacc]
29
 
* update #destroy_file to clear out any empty directories too [carlivar]
30
 
* fix references to S3Backend module [Hunter Hillegas]
31
 
* make #current_data public with db_file and s3 backends [ebryn]
32
 
* oops, actually svn add the files for s3 backend. [Jeffrey Hardy]
33
 
* experimental s3 support, egad, no tests.... [Jeffrey Hardy]
34
 
* doh, fix a few bad references to ActsAsAttachment [sixty4bit]
35

Changes to README

 
attachment-fu
1
 
=============
2
 
3
 
attachment_fu is a plugin by Rick Olson (aka technoweenie <http://techno-weenie.net>) and is the successor to acts_as_attachment.  To get a basic run-through of its capabilities, check out Mike Clark's tutorial <http://clarkware.com/cgi/blosxom/2007/02/24#FileUploadFu>.
4
 
5
 
6
 
attachment_fu functionality
7
 
===========================
8
 
9
 
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.
10
 
11
 
There are three storage options for files uploaded through attachment_fu:
12
 
  File system
13
 
  Database file
14
 
  Amazon S3
15
 
16
 
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.
17
 
18
 
19
 
attachment_fu models
20
 
====================
21
 
22
 
For all three of these storage options a table of metadata is required.  This table will contain information about the file (hence the 'meta') and its location.  This table has no restrictions on naming, unlike the extra table required for database storage, which must have a table name of db_files (and by convention a model of DbFile).
23
 
  
24
 
In the model there are two methods made available by this plugins: has_attachment and validates_as_attachment.
25
 
26
 
has_attachment(options = {})
27
 
  This method accepts the options in a hash:
28
 
    :content_type     # Allowed content types.
29
 
                      # Allows all by default.  Use :image to allow all standard image types.
30
 
    :min_size         # Minimum size allowed.
31
 
                      # 1 byte is the default.
32
 
    :max_size         # Maximum size allowed.
33
 
                      # 1.megabyte is the default.
34
 
    :size             # Range of sizes allowed.
35
 
                      # (1..1.megabyte) is the default.  This overrides the :min_size and :max_size options.
36
 
    :resize_to        # Used by RMagick to resize images.
37
 
                      # Pass either an array of width/height, or a geometry string.
38
 
    :thumbnails       # Specifies a set of thumbnails to generate.
39
 
                      # This accepts a hash of filename suffixes and RMagick resizing options.
40
 
                      # This option need only be included if you want thumbnailing.
41
 
    :thumbnail_class  # Set which model class to use for thumbnails.
42
 
                      # This current attachment class is used by default.
43
 
    :path_prefix      # path to store the uploaded files.
44
 
                      # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the S3 backend.  
45
 
                      # Setting this sets the :storage to :file_system.
46
 
    :storage          # Specifies the storage system to use..
47
 
                      # Defaults to :db_file.  Options are :file_system, :db_file, and :s3.
48
 
    :processor        # Sets the image processor to use for resizing of the attached image.
49
 
                      # Options include ImageScience, Rmagick, and MiniMagick.  Default is whatever is installed.
50
 
    
51
 
52
 
  Examples:
53
 
    has_attachment :max_size => 1.kilobyte
54
 
    has_attachment :size => 1.megabyte..2.megabytes
55
 
    has_attachment :content_type => 'application/pdf'
56
 
    has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain']
57
 
    has_attachment :content_type => :image, :resize_to => [50,50]
58
 
    has_attachment :content_type => ['application/pdf', :image], :resize_to => 'x50'
59
 
    has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
60
 
    has_attachment :storage => :file_system, :path_prefix => 'public/files'
61
 
    has_attachment :storage => :file_system, :path_prefix => 'public/files', 
62
 
                   :content_type => :image, :resize_to => [50,50]
63
 
    has_attachment :storage => :file_system, :path_prefix => 'public/files',
64
 
                   :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
65
 
    has_attachment :storage => :s3
66
 
67
 
validates_as_attachment
68
 
  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.
69
 
  
70
 
  Example:
71
 
    validates_as_attachment
72
 
73
 
74
 
attachment_fu migrations
75
 
========================
76
 
77
 
Fields for attachment_fu metadata tables...
78
 
  in general:
79
 
    size,         :integer  # file size in bytes
80
 
    content_type, :string   # mime type, ex: application/mp3
81
 
    filename,     :string   # sanitized filename
82
 
  that reference images:
83
 
    height,       :integer  # in pixels
84
 
    width,        :integer  # in pixels
85
 
  that reference images that will be thumbnailed:
86
 
    parent_id,    :integer  # id of parent image (on the same table, a self-referencing foreign-key).
87
 
                            # Only populated if the current object is a thumbnail.
88
 
    thumbnail,    :string   # the 'type' of thumbnail this attachment record describes.  
89
 
                            # Only populated if the current object is a thumbnail.
90
 
                            # Usage:
91
 
                            # [ In Model 'Avatar' ]
92
 
                            #   has_attachment :content_type => :image, 
93
 
                            #                  :storage => :file_system, 
94
 
                            #                  :max_size => 500.kilobytes,
95
 
                            #                  :resize_to => '320x200>',
96
 
                            #                  :thumbnails => { :small => '10x10>',
97
 
                            #                                   :thumb => '100x100>' }
98
 
                            # [ Elsewhere ]
99
 
                            # @user.avatar.thumbnails.first.thumbnail #=> 'small'
100
 
  that reference files stored in the database (:db_file):
101
 
    db_file_id,   :integer  # id of the file in the database (foreign key)
102
 
    
103
 
Field for attachment_fu db_files table:
104
 
  data, :binary # binary file data, for use in database file storage
105
 
106
 
107
 
attachment_fu views
108
 
===================
109
 
110
 
There are two main views tasks that will be directly affected by attachment_fu: upload forms and displaying uploaded images.
111
 
112
 
There are two parts of the upload form that differ from typical usage.
113
 
  1. Include ':multipart => true' in the html options of the form_for tag.
114
 
    Example:
115
 
      <% form_for(:attachment_metadata, :url => { :action => "create" }, :html => { :multipart => true }) do |form| %>
116
 
      
117
 
  2. Use the file_field helper with :uploaded_data as the field name.
118
 
    Example:
119
 
      <%= form.file_field :uploaded_data %>
120
 
121
 
Displaying uploaded images is made easy by the public_filename method of the ActiveRecord attachment objects using file system and s3 storage.
122
 
123
 
public_filename(thumbnail = nil)
124
 
  Returns the public path to the file.  If a thumbnail prefix is specified it will return the public file path to the corresponding thumbnail.
125
 
  Examples:
126
 
    attachment_obj.public_filename          #=> /attachments/2/file.jpg
127
 
    attachment_obj.public_filename(:thumb)  #=> /attachments/2/file_thumb.jpg
128
 
    attachment_obj.public_filename(:small)  #=> /attachments/2/file_small.jpg
129
 
130
 
When serving files from database storage, doing more than simply downloading the file is beyond the scope of this document.
131
 
132
 
133
 
attachment_fu controllers
134
 
=========================
135
 
136
 
There are two considerations to take into account when using attachment_fu in controllers.
137
 
138
 
The first is when the files have no publicly accessible path and need to be downloaded through an action.
139
 
140
 
Example:
141
 
  def readme
142
 
    send_file '/path/to/readme.txt', :type => 'plain/text', :disposition => 'inline'
143
 
  end
144
 
  
145
 
See the possible values for send_file for reference.
146
 
147
 
148
 
The second is when saving the file when submitted from a form.
149
 
Example in view:
150
 
 <%= form.file_field :attachable, :uploaded_data %>
151
 
152
 
Example in controller:
153
 
  def create
154
 
    @attachable_file = AttachmentMetadataModel.new(params[:attachable])
155
 
    if @attachable_file.save
156
 
      flash[:notice] = 'Attachment was successfully created.'
157
 
      redirect_to attachable_url(@attachable_file)     
158
 
    else
159
 
      render :action => :new
160
 
    end
161
 
  end
162

Changes to Rakefile

 
require 'rake'
1
 
require 'rake/testtask'
2
 
require 'rake/rdoctask'
3
 
4
 
desc 'Default: run unit tests.'
5
 
task :default => :test
6
 
7
 
desc 'Test the attachment_fu plugin.'
8
 
Rake::TestTask.new(:test) do |t|
9
 
  t.libs << 'lib'
10
 
  t.pattern = 'test/**/*_test.rb'
11
 
  t.verbose = true
12
 
end
13
 
14
 
desc 'Generate documentation for the attachment_fu plugin.'
15
 
Rake::RDocTask.new(:rdoc) do |rdoc|
16
 
  rdoc.rdoc_dir = 'rdoc'
17
 
  rdoc.title    = 'ActsAsAttachment'
18
 
  rdoc.options << '--line-numbers --inline-source'
19
 
  rdoc.rdoc_files.include('README')
20
 
  rdoc.rdoc_files.include('lib/**/*.rb')
21
 
end
22

Changes to amazon_s3.yml.tpl

 
development:
1
 
  bucket_name: appname_development
2
 
  access_key_id: 
3
 
  secret_access_key: 
4
 
5
 
test:
6
 
  bucket_name: appname_test
7
 
  access_key_id: 
8
 
  secret_access_key: 
9
 
10
 
production:
11
 
  bucket_name: appname
12
 
  access_key_id: 
13
 
  secret_access_key: 
14

Changes to init.rb

 
require 'tempfile'
1
 
2
 
Tempfile.class_eval do
3
 
  # overwrite so tempfiles use the extension of the basename.  important for rmagick and image science
4
 
  def make_tmpname(basename, n)
5
 
    ext = nil
6
 
    sprintf("%s%d-%d%s", basename.to_s.gsub(/\.\w+$/) { |s| ext = s; '' }, $$, n, ext)
7
 
  end
8
 
end
9
 
10
 
require 'geometry'
11
 
ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
12
 
Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
13
 
FileUtils.mkdir_p Technoweenie::AttachmentFu.tempfile_path
14
 
15
 
$:.unshift(File.dirname(__FILE__) + '/vendor')
16

Changes to install.rb

 
require 'fileutils'
1
 
2
 
s3_config = File.dirname(__FILE__) + '/../../../config/amazon_s3.yml'
3
 
FileUtils.cp File.dirname(__FILE__) + '/amazon_s3.yml.tpl', s3_config unless File.exist?(s3_config)
4
 
puts IO.read(File.join(File.dirname(__FILE__), 'README'))
5

Changes to geometry.rb

 
# This Geometry class was yanked from RMagick.  However, it lets ImageMagick handle the actual change_geometry.
1
 
# Use #new_dimensions_for to get new dimensons
2
 
# Used so I can use spiffy RMagick geometry strings with ImageScience
3
 
class Geometry
4
 
  # ! and @ are removed until support for them is added
5
 
  FLAGS = ['', '%', '<', '>']#, '!', '@']
6
 
  RFLAGS = { '%' => :percent,
7
 
             '!' => :aspect,
8
 
             '<' => :>,
9
 
             '>' => :<,
10
 
             '@' => :area }
11
 
12
 
  attr_accessor :width, :height, :x, :y, :flag
13
 
14
 
  def initialize(width=nil, height=nil, x=nil, y=nil, flag=nil)
15
 
    # Support floating-point width and height arguments so Geometry
16
 
    # objects can be used to specify Image#density= arguments.
17
 
    raise ArgumentError, "width must be >= 0: #{width}"   if width < 0
18
 
    raise ArgumentError, "height must be >= 0: #{height}" if height < 0
19
 
    @width  = width.to_f
20
 
    @height = height.to_f
21
 
    @x      = x.to_i
22
 
    @y      = y.to_i
23
 
    @flag   = flag
24
 
  end
25
 
26
 
  # Construct an object from a geometry string
27
 
  RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
28
 
29
 
  def self.from_s(str)
30
 
    raise(ArgumentError, "no geometry string specified") unless str
31
 
  
32
 
    if m = RE.match(str)
33
 
      new(m[1].to_i, m[2].to_i, m[3].to_i, m[4].to_i, RFLAGS[m[5]])
34
 
    else
35
 
      raise ArgumentError, "invalid geometry format"
36
 
    end
37
 
  end
38
 
39
 
  # Convert object to a geometry string
40
 
  def to_s
41
 
    str = ''
42
 
    str << "%g" % @width if @width > 0
43
 
    str << 'x' if (@width > 0 || @height > 0)
44
 
    str << "%g" % @height if @height > 0
45
 
    str << "%+d%+d" % [@x, @y] if (@x != 0 || @y != 0)
46
 
    str << FLAGS[@flag.to_i]
47
 
  end
48
 
  
49
 
  # attempts to get new dimensions for the current geometry string given these old dimensions.
50
 
  # This doesn't implement the aspect flag (!) or the area flag (@).  PDI
51
 
  def new_dimensions_for(orig_width, orig_height)
52
 
    new_width  = orig_width
53
 
    new_height = orig_height
54
 
55
 
    case @flag
56
 
      when :percent
57
 
        scale_x = @width.zero?  ? 100 : @width
58
 
        scale_y = @height.zero? ? @width : @height
59
 
        new_width    = scale_x.to_f * (orig_width.to_f  / 100.0)
60
 
        new_height   = scale_y.to_f * (orig_height.to_f / 100.0)
61
 
      when :<, :>, nil
62
 
        scale_factor =
63
 
          if new_width.zero? || new_height.zero?
64
 
            1.0
65
 
          else
66
 
            if @width.nonzero? && @height.nonzero?
67
 
              [@width.to_f / new_width.to_f, @height.to_f / new_height.to_f].min
68
 
            else
69
 
              @width.nonzero? ? (@width.to_f / new_width.to_f) : (@height.to_f / new_height.to_f)
70
 
            end
71
 
          end
72
 
        new_width  = scale_factor * new_width.to_f
73
 
        new_height = scale_factor * new_height.to_f
74
 
        new_width  = orig_width  if @flag && orig_width.send(@flag,  new_width)
75
 
        new_height = orig_height if @flag && orig_height.send(@flag, new_height)
76
 
    end
77
 
78
 
    [new_width, new_height].collect! { |v| v.round }
79
 
  end
80
 
end
81
 
82
 
class Array
83
 
  # allows you to get new dimensions for the current array of dimensions with a given geometry string
84
 
  #
85
 
  #   [50, 64] / '40>' # => [40, 51]
86
 
  def /(geometry)
87
 
    raise ArgumentError, "Only works with a [width, height] pair" if size != 2
88
 
    raise ArgumentError, "Must pass a valid geometry string or object" unless geometry.is_a?(String) || geometry.is_a?(Geometry)
89
 
    geometry = Geometry.from_s(geometry) if geometry.is_a?(String)
90
 
    geometry.new_dimensions_for first, last
91
 
  end
92
 
end
93

Changes to db_file_backend.rb

 
module Technoweenie # :nodoc:
1
 
  module AttachmentFu # :nodoc:
2
 
    module Backends
3
 
      # Methods for DB backed attachments
4
 
      module DbFileBackend
5
 
        def self.included(base) #:nodoc:
6
 
          Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
7
 
          base.belongs_to  :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
8
 
        end
9
 
10
 
        # Creates a temp file with the current db data.
11
 
        def create_temp_file
12
 
          write_to_temp_file current_data
13
 
        end
14
 
        
15
 
        # Gets the current data from the database
16
 
        def current_data
17
 
          db_file.data
18
 
        end
19
 
        
20
 
        protected
21
 
          # Destroys the file.  Called in the after_destroy callback
22
 
          def destroy_file
23
 
            db_file.destroy if db_file
24
 
          end
25
 
          
26
 
          # Saves the data to the DbFile model
27
 
          def save_to_storage
28
 
            if save_attachment?
29
 
              (db_file || build_db_file).data = temp_data
30
 
              db_file.save!
31
 
              self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
32
 
            end
33
 
            true
34
 
          end
35
 
      end
36
 
    end
37
 
  end
38
 
end
39

Changes to file_system_backend.rb

 
require 'ftools'
1
 
module Technoweenie # :nodoc:
2
 
  module AttachmentFu # :nodoc:
3
 
    module Backends
4
 
      # Methods for file system backed attachments
5
 
      module FileSystemBackend
6
 
        def self.included(base) #:nodoc:
7
 
          base.before_update :rename_file
8
 
        end
9
 
      
10
 
        # Gets the full path to the filename in this format:
11
 
        #
12
 
        #   # 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
15
 
        #
16
 
        # Overwrite this method in your model to customize the filename.
17
 
        # The optional thumbnail argument will output the thumbnail's filename.
18
 
        def full_filename(thumbnail = nil)
19
 
          file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
20
 
          File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
21
 
        end
22
 
      
23
 
        # Used as the base path that #public_filename strips off full_filename to create the public path
24
 
        def base_path
25
 
          @base_path ||= File.join(RAILS_ROOT, 'public')
26
 
        end
27
 
      
28
 
        # The attachment ID used in the full path of a file
29
 
        def attachment_path_id
30
 
          ((respond_to?(:parent_id) && parent_id) || id).to_i
31
 
        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
 
        def partitioned_path(*args)
36
 
          ("%08d" % attachment_path_id).scan(/..../) + args
37
 
        end
38
 
      
39
 
        # Gets the public path to the file
40
 
        # The optional thumbnail argument will output the thumbnail's filename.
41
 
        def public_filename(thumbnail = nil)
42
 
          full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
43
 
        end
44
 
      
45
 
        def filename=(value)
46
 
          @old_filename = full_filename unless filename.nil? || @old_filename
47
 
          write_attribute :filename, sanitize_filename(value)
48
 
        end
49
 
50
 
        # Creates a temp file from the currently saved file.
51
 
        def create_temp_file
52
 
          copy_to_temp_file full_filename
53
 
        end
54
 
55
 
        protected
56
 
          # Destroys the file.  Called in the after_destroy callback
57
 
          def destroy_file
58
 
            FileUtils.rm full_filename
59
 
            # remove directory also if it is now empty
60
 
            Dir.rmdir(File.dirname(full_filename)) if (Dir.entries(File.dirname(full_filename))-['.','..']).empty?
61
 
          rescue
62
 
            logger.info "Exception destroying  #{full_filename.inspect}: [#{$!.class.name}] #{$1.to_s}"
63
 
            logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("\n")
64
 
          end
65
 
66
 
          # Renames the given file before saving
67
 
          def rename_file
68
 
            return unless @old_filename && @old_filename != full_filename
69
 
            if save_attachment? && File.exists?(@old_filename)
70
 
              FileUtils.rm @old_filename
71
 
            elsif File.exists?(@old_filename)
72
 
              FileUtils.mv @old_filename, full_filename
73
 
            end
74
 
            @old_filename =  nil
75
 
            true
76
 
          end
77
 
          
78
 
          # Saves the file to the file system
79
 
          def save_to_storage
80
 
            if save_attachment?
81
 
              # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
82
 
              FileUtils.mkdir_p(File.dirname(full_filename))
83
 
              File.cp(temp_path, full_filename)
84
 
              File.chmod(attachment_options[:chmod] || 0644, full_filename)
85
 
            end
86
 
            @old_filename = nil
87
 
            true
88
 
          end
89
 
          
90
 
          def current_data
91
 
            File.file?(full_filename) ? File.read(full_filename) : nil
92
 
          end
93
 
      end
94
 
    end
95
 
  end
96
 
end
97

Changes to s3_backend.rb

 
module Technoweenie # :nodoc:
1
 
  module AttachmentFu # :nodoc:
2
 
    module Backends
3
 
      # = AWS::S3 Storage Backend
4
 
      #
5
 
      # Enables use of {Amazon's Simple Storage Service}[http://aws.amazon.com/s3] as a storage mechanism
6
 
      #
7
 
      # == Requirements
8
 
      #
9
 
      # Requires the {AWS::S3 Library}[http://amazon.rubyforge.org] for S3 by Marcel Molina Jr. installed either
10
 
      # as a gem or a as a Rails plugin.
11
 
      #
12
 
      # == Configuration
13
 
      #
14
 
      # Configuration is done via <tt>RAILS_ROOT/config/amazon_s3.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>.
15
 
      # The minimum connection options that you must specify are a bucket name, your access key id and your secret access key.
16
 
      # If you don't already have your access keys, all you need to sign up for the S3 service is an account at Amazon.
17
 
      # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
18
 
      #
19
 
      # Example configuration (RAILS_ROOT/config/amazon_s3.yml)
20
 
      # 
21
 
      #   development:
22
 
      #     bucket_name: appname_development
23
 
      #     access_key_id: <your key>
24
 
      #     secret_access_key: <your key>
25
 
      #   
26
 
      #   test:
27
 
      #     bucket_name: appname_test
28
 
      #     access_key_id: <your key>
29
 
      #     secret_access_key: <your key>
30
 
      #   
31
 
      #   production:
32
 
      #     bucket_name: appname
33
 
      #     access_key_id: <your key>
34
 
      #     secret_access_key: <your key>
35
 
      #
36
 
      # You can change the location of the config path by passing a full path to the :s3_config_path option.
37
 
      #
38
 
      #   has_attachment :storage => :s3, :s3_config_path => (RAILS_ROOT + '/config/s3.yml')
39
 
      #
40
 
      # === Required configuration parameters
41
 
      #
42
 
      # * <tt>:access_key_id</tt> - The access key id for your S3 account. Provided by Amazon.
43
 
      # * <tt>:secret_access_key</tt> - The secret access key for your S3 account. Provided by Amazon.
44
 
      # * <tt>:bucket_name</tt> - A unique bucket name (think of the bucket_name as being like a database name).
45
 
      #
46
 
      # If any of these required arguments is missing, a MissingAccessKey exception will be raised from AWS::S3.
47
 
      #
48
 
      # == About bucket names
49
 
      #
50
 
      # Bucket names have to be globaly unique across the S3 system. And you can only have up to 100 of them,
51
 
      # so it's a good idea to think of a bucket as being like a database, hence the correspondance in this
52
 
      # implementation to the development, test, and production environments.
53
 
      #
54
 
      # The number of objects you can store in a bucket is, for all intents and purposes, unlimited.
55
 
      #
56
 
      # === Optional configuration parameters
57
 
      #
58
 
      # * <tt>:server</tt> - The server to make requests to. Defaults to <tt>s3.amazonaws.com</tt>.
59
 
      # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if <tt>:use_ssl</tt> is set.
60
 
      # * <tt>:use_ssl</tt> - If set to true, <tt>:port</tt> will be implicitly set to 443, unless specified otherwise. Defaults to false.
61
 
      #
62
 
      # == Usage
63
 
      #
64
 
      # To specify S3 as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:s3</tt>.
65
 
      #
66
 
      #   class Photo < ActiveRecord::Base
67
 
      #     has_attachment :storage => :s3
68
 
      #   end
69
 
      #
70
 
      # === Customizing the path
71
 
      #
72
 
      # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
73
 
      # in S3 urls that look like: http(s)://:server/:bucket_name/:table_name/:id/:filename with :table_name
74
 
      # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
75
 
      # option:
76
 
      #
77
 
      #   class Photo < ActiveRecord::Base
78
 
      #     has_attachment :storage => :s3, :path_prefix => 'my/custom/path'
79
 
      #   end
80
 
      #
81
 
      # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt>
82
 
      #
83
 
      # === Permissions
84
 
      #
85
 
      # By default, files are stored on S3 with public access permissions. You can customize this using
86
 
      # the <tt>:s3_access</tt> option to <tt>has_attachment</tt>. Available values are 
87
 
      # <tt>:private</tt>, <tt>:public_read_write</tt>, and <tt>:authenticated_read</tt>.
88
 
      #
89
 
      # === Other options
90
 
      #
91
 
      # Of course, all the usual configuration options apply, such as content_type and thumbnails:
92
 
      #
93
 
      #   class Photo < ActiveRecord::Base
94
 
      #     has_attachment :storage => :s3, :content_type => ['application/pdf', :image], :resize_to => 'x50'
95
 
      #     has_attachment :storage => :s3, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
96
 
      #   end
97
 
      #
98
 
      # === Accessing S3 URLs
99
 
      #
100
 
      # You can get an object's URL using the s3_url accessor. For example, assuming that for your postcard app
101
 
      # you had a bucket name like 'postcard_world_development', and an attachment model called Photo:
102
 
      #
103
 
      #   @postcard.s3_url # => http(s)://s3.amazonaws.com/postcard_world_development/photos/1/mexico.jpg
104
 
      #
105
 
      # The resulting url is in the form: http(s)://:server/:bucket_name/:table_name/:id/:file.
106
 
      # The optional thumbnail argument will output the thumbnail's filename (if any).
107
 
      #
108
 
      # Additionally, you can get an object's base path relative to the bucket root using
109
 
      # <tt>base_path</tt>:
110
 
      #
111
 
      #   @photo.file_base_path # => photos/1
112
 
      #
113
 
      # And the full path (including the filename) using <tt>full_filename</tt>:
114
 
      #
115
 
      #   @photo.full_filename # => photos/
116
 
      #
117
 
      # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the bucket name as part of the path.
118
 
      # You can retrieve the bucket name using the <tt>bucket_name</tt> method.
119
 
      module S3Backend
120
 
        class RequiredLibraryNotFoundError < StandardError; end
121
 
        class ConfigFileNotFoundError < StandardError; end
122
 
123
 
        def self.included(base) #:nodoc:
124
 
          mattr_reader :bucket_name, :s3_config
125
 
          
126
 
          begin
127
 
            require 'aws/s3'
128
 
            include AWS::S3
129
 
          rescue LoadError
130
 
            raise RequiredLibraryNotFoundError.new('AWS::S3 could not be loaded')
131
 
          end
132
 
133
 
          begin
134
 
            @@s3_config_path = base.attachment_options[:s3_config_path] || (RAILS_ROOT + '/config/amazon_s3.yml')
135
 
            @@s3_config = @@s3_config = YAML.load(ERB.new(File.read(@@s3_config_path)).result)[RAILS_ENV].symbolize_keys
136
 
          #rescue
137
 
          #  raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path)
138
 
          end
139
 
140
 
          @@bucket_name = s3_config[:bucket_name]
141
 
142
 
          Base.establish_connection!(s3_config.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy))
143
 
144
 
          # Bucket.create(@@bucket_name)
145
 
146
 
          base.before_update :rename_file
147
 
        end
148
 
149
 
        def self.protocol
150
 
          @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
151
 
        end
152
 
        
153
 
        def self.hostname
154
 
          @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
155
 
        end
156
 
        
157
 
        def self.port_string
158
 
          @port_string ||= (s3_config[:port].nil? || s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80)) ? '' : ":#{s3_config[:port]}"
159
 
        end
160
 
161
 
        module ClassMethods
162
 
          def s3_protocol
163
 
            Technoweenie::AttachmentFu::Backends::S3Backend.protocol
164
 
          end
165
 
          
166
 
          def s3_hostname
167
 
            Technoweenie::AttachmentFu::Backends::S3Backend.hostname
168
 
          end
169
 
          
170
 
          def s3_port_string
171
 
            Technoweenie::AttachmentFu::Backends::S3Backend.port_string
172
 
          end
173
 
        end
174
 
175
 
        # Overwrites the base filename writer in order to store the old filename
176
 
        def filename=(value)
177
 
          @old_filename = filename unless filename.nil? || @old_filename
178
 
          write_attribute :filename, sanitize_filename(value)
179
 
        end
180
 
181
 
        # The attachment ID used in the full path of a file
182
 
        def attachment_path_id
183
 
          ((respond_to?(:parent_id) && parent_id) || id).to_s
184
 
        end
185
 
186
 
        # The pseudo hierarchy containing the file relative to the bucket name
187
 
        # Example: <tt>:table_name/:id</tt>
188
 
        def base_path
189
 
          File.join(attachment_options[:path_prefix], attachment_path_id)
190
 
        end
191
 
192
 
        # The full path to the file relative to the bucket name
193
 
        # Example: <tt>:table_name/:id/:filename</tt>
194
 
        def full_filename(thumbnail = nil)
195
 
          File.join(base_path, thumbnail_name_for(thumbnail))
196
 
        end
197
&