Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

merged thoughtbot/master with alainravet/keep_old_files

  • Loading branch information...
commit e8a2523da45832c3b394371a29ca727594889652 2 parents 2faf360 + 6cbd3d5
@goncalossilva authored
Showing with 2,548 additions and 1,041 deletions.
  1. +5 −0 .gitignore
  2. +8 −0 Appraisals
  3. +8 −0 Gemfile
  4. +37 −0 Gemfile.lock
  5. +10 −2 README.rdoc
  6. +16 −35 Rakefile
  7. +6 −0 cucumber/paperclip_steps.rb
  8. +17 −0 features/basic.feature
  9. +27 −0 features/s3.feature
  10. +14 −0 features/step_definitions/html_steps.rb
  11. +90 −0 features/step_definitions/rails_steps.rb
  12. +9 −0 features/step_definitions/s3_steps.rb
  13. +227 −0 features/step_definitions/web_steps.rb
  14. +3 −0  features/support/env.rb
  15. +35 −0 features/support/paths.rb
  16. +5 −0 features/support/rails.rb
  17. +25 −0 features/support/s3.rb
  18. +10 −0 gemfiles/rails2.gemfile
  19. +52 −0 gemfiles/rails2.gemfile.lock
  20. +10 −0 gemfiles/rails3.gemfile
  21. +93 −0 gemfiles/rails3.gemfile.lock
  22. +2 −2 generators/paperclip/USAGE
  23. +8 −8 generators/paperclip/paperclip_generator.rb
  24. +8 −0 lib/generators/paperclip/USAGE
  25. +31 −0 lib/generators/paperclip/paperclip_generator.rb
  26. +19 −0 lib/generators/paperclip/templates/paperclip_migration.rb.erb
  27. +107 −87 lib/paperclip.rb
  28. +89 −156 lib/paperclip/attachment.rb
  29. +50 −22 lib/paperclip/callback_compatability.rb
  30. +80 −0 lib/paperclip/command_line.rb
  31. +7 −7 lib/paperclip/geometry.rb
  32. +30 −21 lib/paperclip/interpolations.rb
  33. +11 −24 lib/paperclip/iostream.rb
  34. +29 −0 lib/paperclip/matchers.rb
  35. +8 −0 lib/paperclip/matchers/have_attached_file_matcher.rb
  36. +14 −5 lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
  37. +13 −7 lib/paperclip/matchers/validate_attachment_presence_matcher.rb
  38. +16 −4 lib/paperclip/matchers/validate_attachment_size_matcher.rb
  39. +15 −6 lib/paperclip/processor.rb
  40. +24 −0 lib/paperclip/railtie.rb
  41. +2 −237 lib/paperclip/storage.rb
  42. +73 −0 lib/paperclip/storage/filesystem.rb
  43. +192 −0 lib/paperclip/storage/s3.rb
  44. +90 −0 lib/paperclip/style.rb
  45. +23 −17 lib/paperclip/thumbnail.rb
  46. +12 −5 lib/paperclip/upfile.rb
  47. +3 −0  lib/paperclip/version.rb
  48. 0  tasks/paperclip_tasks.rake → lib/tasks/paperclip.rake
  49. +32 −34 paperclip.gemspec
  50. +2 −0  rails/init.rb
  51. +51 −1 shoulda_macros/paperclip.rb
  52. +112 −87 test/attachment_test.rb
  53. +133 −0 test/command_line_test.rb
  54. +4 −0 test/fixtures/s3.yml
  55. +2 −2 test/geometry_test.rb
  56. +71 −35 test/helper.rb
  57. +10 −11 test/integration_test.rb
  58. +16 −9 test/interpolations_test.rb
  59. +15 −15 test/iostream_test.rb
  60. +10 −7 test/matchers/have_attached_file_matcher_test.rb
  61. +26 −9 test/matchers/validate_attachment_content_type_matcher_test.rb
  62. +12 −7 test/matchers/validate_attachment_presence_matcher_test.rb
  63. +19 −18 test/matchers/validate_attachment_size_matcher_test.rb
  64. +61 −119 test/paperclip_test.rb
  65. +1 −1  test/processor_test.rb
  66. +109 −19 test/storage_test.rb
  67. +141 −0 test/style_test.rb
  68. +22 −22 test/thumbnail_test.rb
  69. +36 −0 test/upfile_test.rb
View
5 .gitignore
@@ -3,3 +3,8 @@
tmp
test/s3.yml
public
+paperclip*.gem
+capybara*.html
+*.rbc
+.bundle
+*SPIKE*
View
8 Appraisals
@@ -0,0 +1,8 @@
+appraise "rails2" do
+ gem "rails", "~>2.3.0"
+end
+
+appraise "rails3" do
+ gem "rails", "~>3.0.0"
+end
+
View
8 Gemfile
@@ -0,0 +1,8 @@
+source "http://rubygems.org"
+gem "shoulda"
+gem "mocha"
+gem "rake"
+gem "ruby-debug"
+gem "aws-s3", :require => "aws/s3"
+gem "sqlite3-ruby", "~>1.3.0"
+gem "appraisal"
View
37 Gemfile.lock
@@ -0,0 +1,37 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ appraisal (0.1)
+ bundler
+ rake
+ aws-s3 (0.6.2)
+ builder
+ mime-types
+ xml-simple
+ builder (3.0.0)
+ columnize (0.3.2)
+ linecache (0.43)
+ mime-types (1.16)
+ mocha (0.9.9)
+ rake
+ rake (0.8.7)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
+ shoulda (2.11.3)
+ sqlite3-ruby (1.3.2)
+ xml-simple (1.0.12)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ appraisal
+ aws-s3
+ mocha
+ rake
+ ruby-debug
+ shoulda
+ sqlite3-ruby (~> 1.3.0)
View
12 README.rdoc
@@ -15,6 +15,8 @@ useful defaults.
See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
more detailed options.
+The complete RDoc[http://rdoc.info/projects/thoughtbot/paperclip] is online.
+
==Quick Start
In your model:
@@ -138,7 +140,7 @@ For example, assuming we had this definition:
has_attached_file :scan, :styles => { :text => { :quality => :better } },
:processors => [:rotator, :ocr]
-then both the :rotator processor and the :ocr processor would receive the
+then both the :rotator processor and the :ocr processor would receive the
options "{ :quality => :better }". This parameter may not mean anything to one
or more or the processors, and they are expected to ignore it.
@@ -162,13 +164,19 @@ NOTE: Post processing will not even *start* if the attachment is not valid
according to the validations. Your callbacks and processors will *only* be
called with valid attachments.
+==Testing
+
+Paperclip provides rspec-compatible matchers for testing attachments. See the
+documentation on Paperclip::Shoulda::Matchers for more information.
+
==Contributing
If you'd like to contribute a feature or bugfix: Thanks! To make sure your
fix/feature has a high chance of being included, please read the following
guidelines:
-1. Ask on the mailing list, or post a new GitHub Issue.
+1. Ask on the mailing list[http://groups.google.com/group/paperclip-plugin], or
+ post a new GitHub Issue[http://github.com/thoughtbot/paperclip/issues].
2. Make sure there are tests! We will not accept any patch that is not tested.
It's a rare time when explicit tests aren't needed. If you have questions
about writing tests for paperclip, please ask the mailing list.
View
51 Rakefile
@@ -1,3 +1,7 @@
+require 'rubygems'
+require 'appraisal'
+require 'bundler/setup'
+
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
@@ -6,7 +10,12 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
require 'paperclip'
desc 'Default: run unit tests.'
-task :default => [:clean, :test]
+task :default => [:clean, :all]
+
+desc 'Test the paperclip plugin under all supported Rails versions.'
+task :all do |t|
+ exec('rake appraisal test')
+end
desc 'Test the paperclip plugin.'
Rake::TestTask.new(:test) do |t|
@@ -40,43 +49,15 @@ task :clean do |t|
FileUtils.rm_rf "doc"
FileUtils.rm_rf "tmp"
FileUtils.rm_rf "pkg"
+ FileUtils.rm_rf "public"
FileUtils.rm "test/debug.log" rescue nil
FileUtils.rm "test/paperclip.db" rescue nil
Dir.glob("paperclip-*.gem").each{|f| FileUtils.rm f }
end
-include_file_globs = ["README*",
- "LICENSE",
- "Rakefile",
- "init.rb",
- "{generators,lib,tasks,test,shoulda_macros}/**/*"]
-exclude_file_globs = ["test/s3.yml",
- "test/debug.log",
- "test/paperclip.db",
- "test/doc",
- "test/doc/*",
- "test/pkg",
- "test/pkg/*",
- "test/tmp",
- "test/tmp/*"]
-spec = Gem::Specification.new do |s|
- s.name = "paperclip"
- s.version = Paperclip::VERSION
- s.author = "Jon Yurek"
- s.email = "jyurek@thoughtbot.com"
- s.homepage = "http://www.thoughtbot.com/projects/paperclip"
- s.platform = Gem::Platform::RUBY
- s.summary = "File attachments as attributes for ActiveRecord"
- s.files = FileList[include_file_globs].to_a - FileList[exclude_file_globs].to_a
- s.require_path = "lib"
- s.test_files = FileList["test/**/test_*.rb"].to_a
- s.rubyforge_project = "paperclip"
- s.has_rdoc = true
- s.extra_rdoc_files = FileList["README*"].to_a
- s.rdoc_options << '--line-numbers' << '--inline-source'
- s.requirements << "ImageMagick"
- s.add_development_dependency 'thoughtbot-shoulda'
- s.add_development_dependency 'mocha'
+desc 'Build the gemspec.'
+task :gemspec do |t|
+ exec 'gem build paperclip.gemspec'
end
desc "Print a list of the files to be put into the gem"
@@ -85,13 +66,13 @@ task :manifest => :clean do
puts file
end
end
-
+
desc "Generate a gemspec file for GitHub"
task :gemspec => :clean do
File.open("#{spec.name}.gemspec", 'w') do |f|
f.write spec.to_ruby
end
-end
+end
desc "Build the gem into the current directory"
task :gem => :gemspec do
View
6 cucumber/paperclip_steps.rb
@@ -0,0 +1,6 @@
+When /^I attach an? "([^\"]*)" "([^\"]*)" file to an? "([^\"]*)" on S3$/ do |attachment, extension, model|
+ stub_paperclip_s3(model, attachment, extension)
+ attach_file attachment,
+ "features/support/paperclip/#{model.gsub(" ", "_").underscore}/#{attachment}.#{extension}"
+end
+
View
17 features/basic.feature
@@ -0,0 +1,17 @@
+Feature: Running paperclip in a Rails app
+
+ Scenario: Basic utilization
+ Given I have a rails application
+ And I save the following as "app/models/user.rb"
+ """
+ class User < ActiveRecord::Base
+ has_attached_file :avatar
+ end
+ """
+ When I visit /users/new
+ And I fill in "user_name" with "something"
+ And I attach the file "test/fixtures/5k.png" to "user_avatar"
+ And I press "Submit"
+ Then I should see "Name: something"
+ And I should see an image with a path of "/system/avatars/1/original/5k.png"
+ And the file at "/system/avatars/1/original/5k.png" is the same as "test/fixtures/5k.png"
View
27 features/s3.feature
@@ -0,0 +1,27 @@
+Feature: Running paperclip in a Rails app using basic S3 support
+
+ Scenario: Basic utilization
+ Given I have a rails application
+ And I save the following as "app/models/user.rb"
+ """
+ class User < ActiveRecord::Base
+ has_attached_file :avatar,
+ :storage => :s3,
+ :path => "/:attachment/:id/:style/:filename",
+ :s3_credentials => Rails.root.join("config/s3.yml")
+ end
+ """
+ And I validate my S3 credentials
+ And I save the following as "config/s3.yml"
+ """
+ bucket: <%= ENV['PAPERCLIP_TEST_BUCKET'] || 'paperclip' %>
+ access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
+ secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
+ """
+ When I visit /users/new
+ And I fill in "user_name" with "something"
+ And I attach the file "test/fixtures/5k.png" to "user_avatar"
+ And I press "Submit"
+ Then I should see "Name: something"
+ And I should see an image with a path of "http://s3.amazonaws.com/paperclip/avatars/1/original/5k.png"
+ And the file at "http://s3.amazonaws.com/paperclip/avatars/1/original/5k.png" is the same as "test/fixtures/5k.png"
View
14 features/step_definitions/html_steps.rb
@@ -0,0 +1,14 @@
+Then %r{I should see an image with a path of "([^"]*)"} do |path|
+ page.should have_css("img[src^='#{path}']")
+end
+
+Then %r{^the file at "([^"]*)" is the same as "([^"]*)"$} do |web_file, path|
+ expected = IO.read(path)
+ actual = if web_file.match %r{^https?://}
+ Net::HTTP.get(URI.parse(web_file))
+ else
+ visit(web_file)
+ page.body
+ end
+ actual.should == expected
+end
View
90 features/step_definitions/rails_steps.rb
@@ -0,0 +1,90 @@
+Given "I have a rails application" do
+ steps %{
+ Given I generate a rails application
+ And this plugin is available
+ And I have a "users" resource with "name:string"
+ And I turn off class caching
+ Given I save the following as "app/models/user.rb"
+ """
+ class User < ActiveRecord::Base
+ end
+ """
+ And I save the following as "config/s3.yml"
+ """
+ access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
+ secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
+ bucket: paperclip
+ """
+ And I save the following as "app/views/users/new.html.erb"
+ """
+ <% form_for @user, :html => { :multipart => true } do |f| %>
+ <%= f.text_field :name %>
+ <%= f.file_field :avatar %>
+ <%= submit_tag "Submit" %>
+ <% end %>
+ """
+ And I save the following as "app/views/users/show.html.erb"
+ """
+ <p>Name: <%= @user.name %></p>
+ <p>Avatar: <%= image_tag @user.avatar.url %></p>
+ """
+ And I run "script/generate paperclip user avatar"
+ And the rails application is prepped and running
+ }
+end
+
+Given %r{I generate a rails application} do
+ FileUtils.rm_rf TEMP_ROOT
+ FileUtils.mkdir_p TEMP_ROOT
+ Dir.chdir(TEMP_ROOT) do
+ `rails _2.3.8_ #{APP_NAME}`
+ end
+end
+
+When %r{I save the following as "([^"]*)"} do |path, string|
+ FileUtils.mkdir_p(File.join(CUC_RAILS_ROOT, File.dirname(path)))
+ File.open(File.join(CUC_RAILS_ROOT, path), 'w') { |file| file.write(string) }
+end
+
+When %r{I turn off class caching} do
+ Dir.chdir(CUC_RAILS_ROOT) do
+ file = "config/environments/test.rb"
+ config = IO.read(file)
+ config.gsub!(%r{^\s*config.cache_classes.*$},
+ "config.cache_classes = false")
+ File.open(file, "w"){|f| f.write(config) }
+ end
+end
+
+When %r{the rails application is prepped and running$} do
+ When "I reset the database"
+ When "the rails application is running"
+end
+
+When %r{I reset the database} do
+ When %{I run "rake db:drop db:create db:migrate"}
+end
+
+When %r{the rails application is running} do
+ Dir.chdir(CUC_RAILS_ROOT) do
+ require "config/environment"
+ require "capybara/rails"
+ end
+end
+
+When %r{this plugin is available} do
+ $LOAD_PATH << "#{PROJECT_ROOT}/lib"
+ require 'paperclip'
+ When %{I save the following as "vendor/plugins/paperclip/rails/init.rb"},
+ IO.read("#{PROJECT_ROOT}/rails/init.rb")
+end
+
+When %r{I run "([^"]*)"} do |command|
+ Dir.chdir(CUC_RAILS_ROOT) do
+ `#{command}`
+ end
+end
+
+When %r{I have a "([^"]*)" resource with "([^"]*)"} do |resource, fields|
+ When %{I run "script/generate scaffold #{resource} #{fields}"}
+end
View
9 features/step_definitions/s3_steps.rb
@@ -0,0 +1,9 @@
+Given /I validate my S3 credentials/ do
+ key = ENV['AWS_ACCESS_KEY_ID']
+ secret = ENV['AWS_SECRET_ACCESS_KEY']
+
+ key.should_not be_nil
+ secret.should_not be_nil
+
+ assert_credentials(key, secret)
+end
View
227 features/step_definitions/web_steps.rb
@@ -0,0 +1,227 @@
+# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
+# It is recommended to regenerate this file in the future when you upgrade to a
+# newer version of cucumber-rails. Consider adding your own code to a new file
+# instead of editing this one. Cucumber will automatically load all features/**/*.rb
+# files.
+
+
+require 'uri'
+require 'cgi'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
+
+module WithinHelpers
+ def with_scope(locator)
+ locator ? within(locator) { yield } : yield
+ end
+end
+World(WithinHelpers)
+
+Given /^(?:|I )am on (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )go to (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )visit (\/.+)$/ do |page_path|
+ visit page_path
+end
+
+When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
+ with_scope(selector) do
+ click_button(button)
+ end
+end
+
+When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
+ with_scope(selector) do
+ click_link(link)
+ end
+end
+
+When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector|
+ with_scope(selector) do
+ fill_in(field, :with => value)
+ end
+end
+
+When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
+ with_scope(selector) do
+ fill_in(field, :with => value)
+ end
+end
+
+# Use this to fill in an entire form with data from a table. Example:
+#
+# When I fill in the following:
+# | Account Number | 5002 |
+# | Expiry date | 2009-11-01 |
+# | Note | Nice guy |
+# | Wants Email? | |
+#
+# TODO: Add support for checkbox, select og option
+# based on naming conventions.
+#
+When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields|
+ with_scope(selector) do
+ fields.rows_hash.each do |name, value|
+ When %{I fill in "#{name}" with "#{value}"}
+ end
+ end
+end
+
+When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
+ with_scope(selector) do
+ select(value, :from => field)
+ end
+end
+
+When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ check(field)
+ end
+end
+
+When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ uncheck(field)
+ end
+end
+
+When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
+ with_scope(selector) do
+ choose(field)
+ end
+end
+
+When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"(?: within "([^"]*)")?$/ do |path, field, selector|
+ with_scope(selector) do
+ attach_file(field, path)
+ end
+end
+
+Then /^(?:|I )should see JSON:$/ do |expected_json|
+ require 'json'
+ expected = JSON.pretty_generate(JSON.parse(expected_json))
+ actual = JSON.pretty_generate(JSON.parse(response.body))
+ expected.should == actual
+end
+
+Then /^(?:|I )should see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_content(text)
+ else
+ assert page.has_content?(text)
+ end
+ end
+end
+
+Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
+ regexp = Regexp.new(regexp)
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_xpath('//*', :text => regexp)
+ else
+ assert page.has_xpath?('//*', :text => regexp)
+ end
+ end
+end
+
+Then /^(?:|I )should not see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_no_content(text)
+ else
+ assert page.has_no_content?(text)
+ end
+ end
+end
+
+Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
+ regexp = Regexp.new(regexp)
+ with_scope(selector) do
+ if page.respond_to? :should
+ page.should have_no_xpath('//*', :text => regexp)
+ else
+ assert page.has_no_xpath?('//*', :text => regexp)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value|
+ with_scope(selector) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should
+ field_value.should =~ /#{value}/
+ else
+ assert_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value|
+ with_scope(selector) do
+ field = find_field(field)
+ field_value = (field.tag_name == 'textarea') ? field.text : field.value
+ if field_value.respond_to? :should_not
+ field_value.should_not =~ /#{value}/
+ else
+ assert_no_match(/#{value}/, field_value)
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector|
+ with_scope(selector) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_true
+ else
+ assert field_checked
+ end
+ end
+end
+
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector|
+ with_scope(selector) do
+ field_checked = find_field(label)['checked']
+ if field_checked.respond_to? :should
+ field_checked.should be_false
+ else
+ assert !field_checked
+ end
+ end
+end
+
+Then /^(?:|I )should be on (.+)$/ do |page_name|
+ current_path = URI.parse(current_url).path
+ if current_path.respond_to? :should
+ current_path.should == path_to(page_name)
+ else
+ assert_equal path_to(page_name), current_path
+ end
+end
+
+Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
+ query = URI.parse(current_url).query
+ actual_params = query ? CGI.parse(query) : {}
+ expected_params = {}
+ expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
+
+ if actual_params.respond_to? :should
+ actual_params.should == expected_params
+ else
+ assert_equal expected_params, actual_params
+ end
+end
+
+Then /^I save and open the page$/ do
+ save_and_open_page
+end
+
+Then /^show me the page$/ do
+ save_and_open_page
+end
View
3  features/support/env.rb
@@ -0,0 +1,3 @@
+require 'capybara/cucumber'
+require 'test/unit/assertions'
+World(Test::Unit::Assertions)
View
35 features/support/paths.rb
@@ -0,0 +1,35 @@
+module NavigationHelpers
+ # Maps a name to a path. Used by the
+ #
+ # When /^I go to (.+)$/ do |page_name|
+ #
+ # step definition in web_steps.rb
+ #
+ def path_to(page_name)
+ case page_name
+
+ when /the new user page/
+ '/users/new'
+ when /the home\s?page/
+ '/'
+
+ # Add more mappings here.
+ # Here is an example that pulls values out of the Regexp:
+ #
+ # when /^(.*)'s profile page$/i
+ # user_profile_path(User.find_by_login($1))
+
+ else
+ begin
+ page_name =~ /the (.*) page/
+ path_components = $1.split(/\s+/)
+ self.send(path_components.push('path').join('_').to_sym)
+ rescue Object => e
+ raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
+ "Now, go and add a mapping in #{__FILE__}"
+ end
+ end
+ end
+end
+
+World(NavigationHelpers)
View
5 features/support/rails.rb
@@ -0,0 +1,5 @@
+PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
+TEMP_ROOT = File.join(PROJECT_ROOT, 'tmp').freeze
+APP_NAME = 'testapp'.freeze
+CUC_RAILS_ROOT = File.join(TEMP_ROOT, APP_NAME).freeze
+ENV['RAILS_ENV'] = 'test'
View
25 features/support/s3.rb
@@ -0,0 +1,25 @@
+module AWSS3Methods
+ def load_s3
+ begin
+ require 'aws/s3'
+ rescue LoadError => e
+ fail "You do not have aws-s3 installed."
+ end
+ end
+
+ def assert_credentials(key, secret)
+ load_s3
+ begin
+ AWS::S3::Base.establish_connection!(
+ :access_key_id => key,
+ :secret_access_key => secret
+ )
+ AWS::S3::Service.buckets
+ rescue AWS::S3::ResponseError => e
+ fail "Could not connect using AWS credentials in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. " +
+ "Please make sure these are set in your environment."
+ end
+ end
+end
+
+World(AWSS3Methods)
View
10 gemfiles/rails2.gemfile
@@ -0,0 +1,10 @@
+# This file was generated by Appraisal
+
+source "http://rubygems.org"
+gem "ruby-debug"
+gem "rails", "~>2.3.0"
+gem "rake"
+gem "sqlite3-ruby", "~>1.3.0"
+gem "shoulda"
+gem "mocha"
+gem "aws-s3", {:require=>"aws/s3"}
View
52 gemfiles/rails2.gemfile.lock
@@ -0,0 +1,52 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ actionmailer (2.3.10)
+ actionpack (= 2.3.10)
+ actionpack (2.3.10)
+ activesupport (= 2.3.10)
+ rack (~> 1.1.0)
+ activerecord (2.3.10)
+ activesupport (= 2.3.10)
+ activeresource (2.3.10)
+ activesupport (= 2.3.10)
+ activesupport (2.3.10)
+ aws-s3 (0.6.2)
+ builder
+ mime-types
+ xml-simple
+ builder (3.0.0)
+ columnize (0.3.2)
+ linecache (0.43)
+ mime-types (1.16)
+ mocha (0.9.9)
+ rake
+ rack (1.1.0)
+ rails (2.3.10)
+ actionmailer (= 2.3.10)
+ actionpack (= 2.3.10)
+ activerecord (= 2.3.10)
+ activeresource (= 2.3.10)
+ activesupport (= 2.3.10)
+ rake (>= 0.8.3)
+ rake (0.8.7)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
+ shoulda (2.11.3)
+ sqlite3-ruby (1.3.2)
+ xml-simple (1.0.12)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ aws-s3
+ mocha
+ rails (~> 2.3.0)
+ rake
+ ruby-debug
+ shoulda
+ sqlite3-ruby (~> 1.3.0)
View
10 gemfiles/rails3.gemfile
@@ -0,0 +1,10 @@
+# This file was generated by Appraisal
+
+source "http://rubygems.org"
+gem "ruby-debug"
+gem "rails", "~>3.0.0"
+gem "rake"
+gem "sqlite3-ruby", "~>1.3.0"
+gem "shoulda"
+gem "mocha"
+gem "aws-s3", {:require=>"aws/s3"}
View
93 gemfiles/rails3.gemfile.lock
@@ -0,0 +1,93 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ abstract (1.0.0)
+ actionmailer (3.0.3)
+ actionpack (= 3.0.3)
+ mail (~> 2.2.9)
+ actionpack (3.0.3)
+ activemodel (= 3.0.3)
+ activesupport (= 3.0.3)
+ builder (~> 2.1.2)
+ erubis (~> 2.6.6)
+ i18n (~> 0.4)
+ rack (~> 1.2.1)
+ rack-mount (~> 0.6.13)
+ rack-test (~> 0.5.6)
+ tzinfo (~> 0.3.23)
+ activemodel (3.0.3)
+ activesupport (= 3.0.3)
+ builder (~> 2.1.2)
+ i18n (~> 0.4)
+ activerecord (3.0.3)
+ activemodel (= 3.0.3)
+ activesupport (= 3.0.3)
+ arel (~> 2.0.2)
+ tzinfo (~> 0.3.23)
+ activeresource (3.0.3)
+ activemodel (= 3.0.3)
+ activesupport (= 3.0.3)
+ activesupport (3.0.3)
+ arel (2.0.4)
+ aws-s3 (0.6.2)
+ builder
+ mime-types
+ xml-simple
+ builder (2.1.2)
+ columnize (0.3.2)
+ erubis (2.6.6)
+ abstract (>= 1.0.0)
+ i18n (0.4.2)
+ linecache (0.43)
+ mail (2.2.10)
+ activesupport (>= 2.3.6)
+ i18n (~> 0.4.1)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.16)
+ mocha (0.9.9)
+ rake
+ polyglot (0.3.1)
+ rack (1.2.1)
+ rack-mount (0.6.13)
+ rack (>= 1.0.0)
+ rack-test (0.5.6)
+ rack (>= 1.0)
+ rails (3.0.3)
+ actionmailer (= 3.0.3)
+ actionpack (= 3.0.3)
+ activerecord (= 3.0.3)
+ activeresource (= 3.0.3)
+ activesupport (= 3.0.3)
+ bundler (~> 1.0)
+ railties (= 3.0.3)
+ railties (3.0.3)
+ actionpack (= 3.0.3)
+ activesupport (= 3.0.3)
+ rake (>= 0.8.7)
+ thor (~> 0.14.4)
+ rake (0.8.7)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
+ shoulda (2.11.3)
+ sqlite3-ruby (1.3.2)
+ thor (0.14.6)
+ treetop (1.4.9)
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.23)
+ xml-simple (1.0.12)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ aws-s3
+ mocha
+ rails (~> 3.0.0)
+ rake
+ ruby-debug
+ shoulda
+ sqlite3-ruby (~> 1.3.0)
View
4 generators/paperclip/USAGE
@@ -1,5 +1,5 @@
Usage:
script/generate paperclip Class attachment1 (attachment2 ...)
-
-This will create a migration that will add the proper columns to your class's table.
+
+This will create a migration that will add the proper columns to your class's table.
View
16 generators/paperclip/paperclip_generator.rb
@@ -1,12 +1,12 @@
class PaperclipGenerator < Rails::Generator::NamedBase
attr_accessor :attachments, :migration_name
-
+
def initialize(args, options = {})
super
@class_name, @attachments = args[0], args[1..-1]
end
-
- def manifest
+
+ def manifest
file_name = generate_file_name
@migration_name = file_name.camelize
record do |m|
@@ -14,14 +14,14 @@ def manifest
File.join('db', 'migrate'),
:migration_file_name => file_name
end
- end
-
- private
-
+ end
+
+ private
+
def generate_file_name
names = attachments.map{|a| a.underscore }
names = names[0..-2] + ["and", names[-1]] if names.length > 1
"add_attachments_#{names.join("_")}_to_#{@class_name.underscore}"
end
-
+
end
View
8 lib/generators/paperclip/USAGE
@@ -0,0 +1,8 @@
+Description:
+ Explain the generator
+
+Example:
+ rails generate paperclip Thing
+
+ This will create:
+ what/will/it/create
View
31 lib/generators/paperclip/paperclip_generator.rb
@@ -0,0 +1,31 @@
+require 'rails/generators/active_record'
+
+class PaperclipGenerator < ActiveRecord::Generators::Base
+ desc "Create a migration to add paperclip-specific fields to your model."
+
+ argument :attachment_names, :required => true, :type => :array, :desc => "The names of the attachment(s) to add.",
+ :banner => "attachment_one attachment_two attachment_three ..."
+
+ def self.source_root
+ @source_root ||= File.expand_path('../templates', __FILE__)
+ end
+
+ def generate_migration
+ migration_template "paperclip_migration.rb.erb", "db/migrate/#{migration_file_name}"
+ end
+
+ protected
+
+ def migration_name
+ "add_attachment_#{attachment_names.join("_")}_to_#{name.underscore}"
+ end
+
+ def migration_file_name
+ "#{migration_name}.rb"
+ end
+
+ def migration_class_name
+ migration_name.camelize
+ end
+
+end
View
19 lib/generators/paperclip/templates/paperclip_migration.rb.erb
@@ -0,0 +1,19 @@
+class <%= migration_class_name %> < ActiveRecord::Migration
+ def self.up
+<% attachment_names.each do |attachment| -%>
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at, :datetime
+<% end -%>
+ end
+
+ def self.down
+<% attachment_names.each do |attachment| -%>
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at
+<% end -%>
+ end
+end
View
194 lib/paperclip.rb
@@ -25,17 +25,24 @@
#
# See the +has_attached_file+ documentation for more details.
+require 'erb'
+require 'digest'
require 'tempfile'
+require 'paperclip/version'
require 'paperclip/upfile'
require 'paperclip/iostream'
require 'paperclip/geometry'
require 'paperclip/processor'
require 'paperclip/thumbnail'
-require 'paperclip/storage'
require 'paperclip/interpolations'
+require 'paperclip/style'
require 'paperclip/attachment'
-if defined? RAILS_ROOT
- Dir.glob(File.join(File.expand_path(RAILS_ROOT), "lib", "paperclip_processors", "*.rb")).each do |processor|
+require 'paperclip/storage'
+require 'paperclip/callback_compatability'
+require 'paperclip/command_line'
+require 'paperclip/railtie'
+if defined?(Rails.root) && Rails.root
+ Dir.glob(File.join(File.expand_path(Rails.root), "lib", "paperclip_processors", "*.rb")).each do |processor|
require processor
end
end
@@ -44,16 +51,14 @@
# documentation for Paperclip::ClassMethods for more useful information.
module Paperclip
- VERSION = "2.3.0"
-
class << self
# Provides configurability to Paperclip. There are a number of options available, such as:
- # * whiny: Will raise an error if Paperclip cannot process thumbnails of
+ # * whiny: Will raise an error if Paperclip cannot process thumbnails of
# an uploaded image. Defaults to true.
# * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
# log levels, etc. Defaults to true.
# * command_path: Defines the path at which to find the command line
- # programs if they are not visible to Rails the system's search path. Defaults to
+ # programs if they are not visible to Rails the system's search path. Defaults to
# nil, which uses the first executable found in the user's search path.
# * image_magick_path: Deprecated alias of command_path.
def options
@@ -62,24 +67,20 @@ def options
:image_magick_path => nil,
:command_path => nil,
:log => true,
- :log_command => false,
+ :log_command => true,
:swallow_stderr => true
}
end
- def path_for_command command #:nodoc:
- if options[:image_magick_path]
- warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
- end
- path = [options[:command_path] || options[:image_magick_path], command].compact
- File.join(*path)
+ def configure
+ yield(self) if block_given?
end
def interpolates key, &block
Paperclip::Interpolations[key] = block
end
- # The run method takes a command to execute and a string of parameters
+ # The run method takes a command to execute and an array of parameters
# that get passed to it. The command is prefixed with the :command_path
# option from Paperclip.options. If you have many commands to run and
# they are in different paths, the suggested course of action is to
@@ -88,37 +89,26 @@ def interpolates key, &block
# If the command returns with a result code that is not one of the
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
# a code of 0 is expected, but a list of codes may be passed if necessary.
+ # These codes should be passed as a hash as the last argument, like so:
+ #
+ # Paperclip.run("echo", "something", :expected_outcodes => [0,1,2,3])
#
- # This method can log the command being run when
+ # This method can log the command being run when
# Paperclip.options[:log_command] is set to true (defaults to false). This
# will only log if logging in general is set to true as well.
- def run cmd, params = "", expected_outcodes = 0
- command = %Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")
- command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
- Paperclip.log(command) if Paperclip.options[:log_command]
- output = `#{command}`
- unless [expected_outcodes].flatten.include?($?.exitstatus)
- raise PaperclipCommandLineError, "Error while running #{cmd}"
- end
- output
- end
-
- def bit_bucket #:nodoc:
- File.exists?("/dev/null") ? "/dev/null" : "NUL"
- end
-
- def included base #:nodoc:
- base.extend ClassMethods
- unless base.respond_to?(:define_callbacks)
- base.send(:include, Paperclip::CallbackCompatability)
+ def run cmd, *params
+ if options[:image_magick_path]
+ Paperclip.log("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
end
+ CommandLine.path = options[:command_path] || options[:image_magick_path]
+ CommandLine.new(cmd, *params).run
end
def processor name #:nodoc:
name = name.to_s.camelize
processor = Paperclip.const_get(name)
unless processor.ancestors.include?(Paperclip::Processor)
- raise PaperclipError.new("Processor #{name} was not found")
+ raise PaperclipError.new("Processor #{name} was not found")
end
processor
end
@@ -141,59 +131,81 @@ def logging? #:nodoc:
class PaperclipError < StandardError #:nodoc:
end
- class PaperclipCommandLineError < StandardError #:nodoc:
+ class PaperclipCommandLineError < PaperclipError #:nodoc:
+ attr_accessor :output
+ def initialize(msg = nil, output = nil)
+ super(msg)
+ @output = output
+ end
+ end
+
+ class StorageMethodNotFound < PaperclipError
+ end
+
+ class CommandNotFoundError < PaperclipError
end
class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
end
-
+
class InfiniteInterpolationError < PaperclipError #:nodoc:
end
+ module Glue
+ def self.included base #:nodoc:
+ base.extend ClassMethods
+ if base.respond_to?("set_callback")
+ base.send :include, Paperclip::CallbackCompatability::Rails3
+ else
+ base.send :include, Paperclip::CallbackCompatability::Rails21
+ end
+ end
+ end
+
module ClassMethods
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
- # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
+ # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
# The attribute returns a Paperclip::Attachment object which handles the management of
- # that file. The intent is to make the attachment as much like a normal attribute. The
- # thumbnails will be created when the new file is assigned, but they will *not* be saved
- # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
- # called on it, the attachment will *not* be deleted until +save+ is called. See the
- # Paperclip::Attachment documentation for more specifics. There are a number of options
+ # that file. The intent is to make the attachment as much like a normal attribute. The
+ # thumbnails will be created when the new file is assigned, but they will *not* be saved
+ # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
+ # called on it, the attachment will *not* be deleted until +save+ is called. See the
+ # Paperclip::Attachment documentation for more specifics. There are a number of options
# you can set to change the behavior of a Paperclip attachment:
# * +url+: The full URL of where the attachment is publically accessible. This can just
# as easily point to a directory served directly through Apache as it can to an action
# that can control permissions. You can specify the full domain and path, but usually
- # just an absolute path is sufficient. The leading slash *must* be included manually for
- # absolute paths. The default value is
+ # just an absolute path is sufficient. The leading slash *must* be included manually for
+ # absolute paths. The default value is
# "/system/:attachment/:id/:style/:filename". See
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
# :url => "/:class/:attachment/:id/:style_:filename"
# :url => "http://some.other.host/stuff/:class/:id_:extension"
- # * +default_url+: The URL that will be returned if there is no attachment assigned.
- # This field is interpolated just as the url is. The default value is
+ # * +default_url+: The URL that will be returned if there is no attachment assigned.
+ # This field is interpolated just as the url is. The default value is
# "/:attachment/:style/missing.png"
# has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
- # * +styles+: A hash of thumbnail styles and their geometries. You can find more about
- # geometry strings at the ImageMagick website
+ # * +styles+: A hash of thumbnail styles and their geometries. You can find more about
+ # geometry strings at the ImageMagick website
# (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
- # also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
- # inside the dimensions and then crop the rest off (weighted at the center). The
+ # also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
+ # inside the dimensions and then crop the rest off (weighted at the center). The
# default value is to generate no thumbnails.
- # * +default_style+: The thumbnail style that will be used by default URLs.
+ # * +default_style+: The thumbnail style that will be used by default URLs.
# Defaults to +original+.
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
# :default_style => :normal
# user.avatar.url # => "/avatars/23/normal_me.png"
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
- # to a command line error. This will override the global setting for this attachment.
+ # to a command line error. This will override the global setting for this attachment.
# Defaults to true. This option used to be called :whiny_thumbanils, but this is
# deprecated.
# * +keep_old_files+: Keep the existing attachment files (original + resized) from
# being automatically deleted when an attachment is cleared or updated.
# Defaults to +false+.
# * +convert_options+: When creating thumbnails, use this free-form options
- # field to pass in various convert command options. Typical options are "-strip" to
+ # array to pass in various convert command options. Typical options are "-strip" to
# remove all Exif data from the image (save space for thumbnails and avatars) or
# "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
# convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
@@ -210,6 +222,9 @@ module ClassMethods
# NOTE: While not deprecated yet, it is not recommended to specify options this way.
# It is recommended that :convert_options option be included in the hash passed to each
# :styles for compatability with future versions.
+ # NOTE: Strings supplied to :convert_options are split on space in order to undergo
+ # shell quoting for safety. If your options require a space, please pre-split them
+ # and pass an array to :convert_options instead.
# * +storage+: Chooses the storage backend where the files will be stored. The current
# choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
# documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
@@ -223,9 +238,8 @@ def has_attached_file name, options = {}
after_save :save_attached_files
before_destroy :destroy_attached_files
- define_callbacks :before_post_process, :after_post_process
- define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
-
+ define_paperclip_callbacks :post_process, :"#{name}_post_process"
+
define_method name do |*args|
a = attachment_for(name)
(args.length > 0) ? a.to_s(args.first) : a
@@ -241,7 +255,7 @@ def has_attached_file name, options = {}
validates_each(name) do |record, attr, value|
attachment = record.attachment_for(name)
- attachment.send(:flush_errors) unless attachment.valid?
+ attachment.send(:flush_errors)
end
end
@@ -259,11 +273,14 @@ def validates_attachment_size name, options = {}
max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
range = (min..max)
message = options[:message] || "file size must be between :min and :max bytes."
-
- attachment_definitions[name][:validations] << [:size, {:range => range,
- :message => message,
- :if => options[:if],
- :unless => options[:unless]}]
+ message = message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
+
+ validates_inclusion_of :"#{name}_file_size",
+ :in => range,
+ :message => message,
+ :if => options[:if],
+ :unless => options[:unless],
+ :allow_nil => true
end
# Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
@@ -281,18 +298,19 @@ def validates_attachment_thumbnails name, options = {}
# * +unless+: Same as +if+ but validates if lambda or method returns false.
def validates_attachment_presence name, options = {}
message = options[:message] || "must be set."
- attachment_definitions[name][:validations] << [:presence, {:message => message,
- :if => options[:if],
- :unless => options[:unless]}]
+ validates_presence_of :"#{name}_file_name",
+ :message => message,
+ :if => options[:if],
+ :unless => options[:unless]
end
-
+
# Places ActiveRecord-style validations on the content type of the file
- # assigned. The possible options are:
- # * +content_type+: Allowed content types. Can be a single content type
- # or an array. Each type can be a String or a Regexp. It should be
- # noted that Internet Explorer upload files with content_types that you
- # may not expect. For example, JPEG images are given image/pjpeg and
- # PNGs are image/x-png, so keep that in mind when determining how you
+ # assigned. The possible options are:
+ # * +content_type+: Allowed content types. Can be a single content type
+ # or an array. Each type can be a String or a Regexp. It should be
+ # noted that Internet Explorer upload files with content_types that you
+ # may not expect. For example, JPEG images are given image/pjpeg and
+ # PNGs are image/x-png, so keep that in mind when determining how you
# match. Allows all by default.
# * +message+: The message to display when the uploaded file has an invalid
# content type.
@@ -303,10 +321,18 @@ def validates_attachment_presence name, options = {}
# model, content_type validation will work _ONLY upon assignment_ and
# re-validation after the instance has been reloaded will always succeed.
def validates_attachment_content_type name, options = {}
- attachment_definitions[name][:validations] << [:content_type, {:content_type => options[:content_type],
- :message => options[:message],
- :if => options[:if],
- :unless => options[:unless]}]
+ validation_options = options.dup
+ allowed_types = [validation_options[:content_type]].flatten
+ validates_each(:"#{name}_content_type", validation_options) do |record, attr, value|
+ if !allowed_types.any?{|t| t === value } && !(value.nil? || value.blank?)
+ if record.errors.method(:add).arity == -2
+ message = options[:message] || "is not one of #{allowed_types.join(", ")}"
+ record.errors.add(:"#{name}_content_type", message)
+ else
+ record.errors.add(:"#{name}_content_type", :inclusion, :default => options[:message], :value => value)
+ end
+ end
+ end
end
# Returns the attachment definitions defined by each call to
@@ -321,7 +347,7 @@ def attachment_for name
@_paperclip_attachments ||= {}
@_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
end
-
+
def each_attachment
self.class.attachment_definitions.each do |name, definition|
yield(name, attachment_for(name))
@@ -329,14 +355,14 @@ def each_attachment
end
def save_attached_files
- logger.info("[paperclip] Saving attachments.")
+ Paperclip.log("Saving attachments.")
each_attachment do |name, attachment|
attachment.send(:save)
end
end
def destroy_attached_files
- logger.info("[paperclip] Deleting attachments.")
+ Paperclip.log("Deleting attachments.")
each_attachment do |name, attachment|
attachment.send(:queue_existing_for_delete)
attachment.send(:flush_deletes)
@@ -345,9 +371,3 @@ def destroy_attached_files
end
end
-
-# Set it all up.
-if Object.const_defined?("ActiveRecord")
- ActiveRecord::Base.send(:include, Paperclip)
- File.send(:include, Paperclip::Upfile)
-end
View
245 lib/paperclip/attachment.rb
@@ -4,22 +4,25 @@ module Paperclip
# when the model saves, deletes when the model is destroyed, and processes
# the file upon assignment.
class Attachment
-
+ include IOStream
+
def self.default_options
@default_options ||= {
- :url => "/system/:attachment/:id/:style/:filename",
- :path => ":rails_root/public:url",
- :styles => {},
- :default_url => "/:attachment/:style/missing.png",
- :default_style => :original,
- :validations => [],
- :storage => :filesystem,
- :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
- :keep_old_files=> false
+ :url => "/system/:attachment/:id/:style/:filename",
+ :path => ":rails_root/public:url",
+ :styles => {},
+ :processors => [:thumbnail],
+ :convert_options => {},
+ :default_url => "/:attachment/:style/missing.png",
+ :default_style => :original,
+ :storage => :filesystem,
+ :use_timestamp => true,
+ :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
+ :keep_old_files => false
}
end
- attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write, :options
+ attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options
# Creates an Attachment object. +name+ is the name of the attachment,
# +instance+ is the ActiveRecord object instance it's attached to, and
@@ -35,34 +38,44 @@ def initialize name, instance, options = {}
@path = options[:path]
@path = @path.call(self) if @path.is_a?(Proc)
@styles = options[:styles]
- @styles = @styles.call(self) if @styles.is_a?(Proc)
+ @normalized_styles = nil
@default_url = options[:default_url]
- @validations = options[:validations]
@default_style = options[:default_style]
@storage = options[:storage]
+ @use_timestamp = options[:use_timestamp]
@whiny = options[:whiny_thumbnails] || options[:whiny]
@keep_old_files = options[:keep_old_files]
- @convert_options = options[:convert_options] || {}
- @processors = options[:processors] || [:thumbnail]
+ @convert_options = options[:convert_options]
+ @processors = options[:processors]
@options = options
@queued_for_delete = []
@queued_for_write = {}
@errors = {}
- @validation_errors = nil
@dirty = false
- normalize_style_definition
initialize_storage
end
+ def styles
+ unless @normalized_styles
+ @normalized_styles = {}
+ (@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
+ @normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
+ end
+ end
+ @normalized_styles
+ end
+
+ def processors
+ @processors.respond_to?(:call) ? @processors.call(instance) : @processors
+ end
+
# What gets called when you call instance.attachment = File. It clears
- # errors, assigns attributes, processes the file, and runs validations. It
+ # errors, assigns attributes, and processes the file. It
# also queues up the previous file for deletion, to be flushed away on
# #save of its host. In addition to form uploads, you can also assign
- # another Paperclip attachment:
+ # another Paperclip attachment:
# new_user.avatar = old_user.avatar
- # If the file that is assigned is not valid, the processing (i.e.
- # thumbnailing, etc) will NOT be run.
def assign uploaded_file
ensure_required_accessors!
@@ -78,52 +91,46 @@ def assign uploaded_file
return nil if uploaded_file.nil?
- @queued_for_write[:original] = uploaded_file.to_tempfile
- instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^A-Za-z\d\.\-_]+/, '_'))
+ @queued_for_write[:original] = to_tempfile(uploaded_file)
+ instance_write(:file_name, uploaded_file.original_filename.strip)
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
instance_write(:file_size, uploaded_file.size.to_i)
+ instance_write(:fingerprint, generate_fingerprint(uploaded_file))
instance_write(:updated_at, Time.now)
@dirty = true
- post_process if valid?
-
+ post_process
+
# Reset the file size if the original file was reprocessed.
- instance_write(:file_size, @queued_for_write[:original].size.to_i)
+ instance_write(:file_size, @queued_for_write[:original].size.to_i)
+ instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original]))
ensure
uploaded_file.close if close_uploaded_file
- validate
end
# Returns the public URL of the attachment, with a given style. Note that
# this does not necessarily need to point to a file that your web server
# can access and can point to an action in your app, if you need fine
# grained security. This is not recommended if you don't need the
- # security, however, for performance reasons. set
- # include_updated_timestamp to false if you want to stop the attachment
- # update time appended to the url
- def url style = default_style, include_updated_timestamp = true
- url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
- include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
+ # security, however, for performance reasons. Set use_timestamp to false
+ # if you want to stop the attachment update time appended to the url
+ def url(style_name = default_style, use_timestamp = @use_timestamp)
+ url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
+ use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
end
# Returns the path of the attachment as defined by the :path option. If the
# file is stored in the filesystem the path refers to the path of the file
# on disk. If the file is stored in S3, the path is the "key" part of the
# URL, and the :bucket option refers to the S3 bucket.
- def path style = default_style
- original_filename.nil? ? nil : interpolate(@path, style)
+ def path style_name = default_style
+ original_filename.nil? ? nil : interpolate(@path, style_name)
end
# Alias to +url+
- def to_s style = nil
- url(style)
- end
-
- # Returns true if there are no errors on this attachment.
- def valid?
- validate
- errors.empty?
+ def to_s style_name = nil
+ url(style_name)
end
# Returns an array containing the errors on this attachment.
@@ -139,15 +146,10 @@ def dirty?
# Saves the file, if there are no errors. If there are, it flushes them to
# the instance's errors and returns false, cancelling the save.
def save
- if valid?
- flush_deletes
- flush_writes
- @dirty = false
- true
- else
- flush_errors
- false
- end
+ flush_deletes
+ flush_writes
+ @dirty = false
+ true
end
# Clears out the attachment. Has the same effect as previously assigning
@@ -156,7 +158,6 @@ def save
def clear
queue_existing_for_delete
@errors = {}
- @validation_errors = nil
end
# Destroys the attachment. Has the same effect as previously assigning
@@ -179,17 +180,29 @@ def size
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
end
+ # Returns the hash of the file as originally assigned, and lives in the
+ # <attachment>_fingerprint attribute of the model.
+ def fingerprint
+ instance_read(:fingerprint) || (@queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original]))
+ end
+
# Returns the content_type of the file as originally assigned, and lives
# in the <attachment>_content_type attribute of the model.
def content_type
instance_read(:content_type)
end
-
- # Returns the last modified time of the file as originally assigned, and
+
+ # Returns the last modified time of the file as originally assigned, and
# lives in the <attachment>_updated_at attribute of the model.
def updated_at
time = instance_read(:updated_at)
- time && time.to_i
+ time && time.to_f.to_i
+ end
+
+ def generate_fingerprint(source)
+ data = source.read
+ source.rewind if source.respond_to?(:rewind)
+ Digest::MD5.hexdigest(data)
end
# Paths and URLs can have a number of variables interpolated into them
@@ -212,7 +225,7 @@ def reprocess!
new_original = Tempfile.new("paperclip-reprocess")
new_original.binmode
if old_original = to_file(:original)
- new_original.write( old_original.read )
+ new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
new_original.rewind
@queued_for_write = { :original => new_original }
@@ -225,7 +238,7 @@ def reprocess!
true
end
end
-
+
# Returns true if a file has been assigned.
def file?
!original_filename.blank?
@@ -269,84 +282,13 @@ def valid_assignment? file #:nodoc:
file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
end
- def validate #:nodoc:
- unless @validation_errors
- @validation_errors = @validations.inject({}) do |errors, validation|
- name, options = validation
- errors[name] = send(:"validate_#{name}", options) if allow_validation?(options)
- errors
- end
- @validation_errors.reject!{|k,v| v == nil }
- @errors.merge!(@validation_errors)
- end
- @validation_errors
- end
-
- def allow_validation? options #:nodoc:
- (options[:if].nil? || check_guard(options[:if])) && (options[:unless].nil? || !check_guard(options[:unless]))
- end
-
- def check_guard guard #:nodoc:
- if guard.respond_to? :call
- guard.call(instance)
- elsif ! guard.blank?
- instance.send(guard.to_s)
- end
- end
-
- def validate_size options #:nodoc:
- if file? && !options[:range].include?(size.to_i)
- options[:message].gsub(/:min/, options[:min].to_s).gsub(/:max/, options[:max].to_s)
- end
- end
-
- def validate_presence options #:nodoc:
- options[:message] unless file?
- end
-
- def validate_content_type options #:nodoc:
- valid_types = [options[:content_type]].flatten
- unless original_filename.blank?
- unless valid_types.blank?
- content_type = instance_read(:content_type)
- unless valid_types.any?{|t| content_type.nil? || t === content_type }
- options[:message] || "is not one of the allowed file types."
- end
- end
- end
- end
-
- def normalize_style_definition #:nodoc:
- @styles.each do |name, args|
- unless args.is_a? Hash
- dimensions, format = [args, nil].flatten[0..1]
- format = nil if format.blank?
- @styles[name] = {
- :processors => @processors,
- :geometry => dimensions,
- :format => format,
- :whiny => @whiny,
- :convert_options => extra_options_for(name)
- }
- else
- @styles[name] = {
- :processors => @processors,
- :whiny => @whiny,
- :convert_options => extra_options_for(name)
- }.merge(@styles[name])
- end
- end
- end
-
- def solidify_style_definitions #:nodoc:
- @styles.each do |name, args|
- @styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
- @styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
- end
- end
-
def initialize_storage #:nodoc:
- @storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
+ storage_class_name = @storage.to_s.capitalize
+ begin
+ @storage_module = Paperclip::Storage.const_get(storage_class_name)
+ rescue NameError
+ raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
+ end
self.extend(@storage_module)
end
@@ -361,27 +303,19 @@ def extra_options_for(style) #:nodoc:
def post_process #:nodoc:
return if @queued_for_write[:original].nil?
- solidify_style_definitions
- return if fire_events(:before)
- post_process_styles
- return if fire_events(:after)
- end
-
- def fire_events(which) #:nodoc:
- return true if callback(:"#{which}_post_process") == false
- return true if callback(:"#{which}_#{name}_post_process") == false
- end
-
- def callback which #:nodoc:
- instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
+ instance.run_paperclip_callbacks(:post_process) do
+ instance.run_paperclip_callbacks(:"#{name}_post_process") do
+ post_process_styles
+ end
+ end
end
def post_process_styles #:nodoc:
- @styles.each do |name, args|
+ styles.each do |name, style|
begin
- raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
- @queued_for_write[name] = args[:processors].inject(@queued_for_write[:original]) do |file, processor|
- Paperclip.processor(processor).make(file, args, self)
+ raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
+ @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
+ Paperclip.processor(processor).make(file, style.processor_options, self)
end
rescue PaperclipError => e
log("An error was received while processing: #{e.inspect}")
@@ -390,14 +324,14 @@ def post_process_styles #:nodoc:
end
end
- def interpolate pattern, style = default_style #:nodoc:
- Paperclip::Interpolations.interpolate(pattern, self, style)
+ def interpolate pattern, style_name = default_style #:nodoc:
+ Paperclip::Interpolations.interpolate(pattern, self, style_name)
end
def queue_existing_for_delete #:nodoc:
return unless file?
unless @keep_old_files
- @queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
+ @queued_for_delete += [:original, *styles.keys].uniq.map do |style|
path(style) if exists?(style)
end.compact
end
@@ -415,4 +349,3 @@ def flush_errors #:nodoc:
end
end
-
View
72 lib/paperclip/callback_compatability.rb
@@ -1,33 +1,61 @@
module Paperclip
- # This module is intended as a compatability shim for the differences in
- # callbacks between Rails 2.0 and Rails 2.1.
module CallbackCompatability
- def self.included(base)
- base.extend(ClassMethods)
- base.send(:include, InstanceMethods)
- end
+ module Rails21
+ def self.included(base)
+ base.extend(Defining)
+ base.send(:include, Running)
+ end
- module ClassMethods
- # The implementation of this method is taken from the Rails 1.2.6 source,
- # from rails/activerecord/lib/active_record/callbacks.rb, line 192.
- def define_callbacks(*args)
- args.each do |method|
- self.class_eval <<-"end_eval"
- def self.#{method}(*callbacks, &block)
- callbacks << block if block_given?
- write_inheritable_array(#{method.to_sym.inspect}, callbacks)
- end
- end_eval
+ module Defining
+ def define_paperclip_callbacks(*args)
+ args.each do |callback|
+ define_callbacks("before_#{callback}")
+ define_callbacks("after_#{callback}")
+ end
+ end
+ end
+
+ module Running
+ def run_paperclip_callbacks(callback, opts = nil, &blk)
+ # The overall structure of this isn't ideal since after callbacks run even if
+ # befores return false. But this is how rails 3's callbacks work, unfortunately.
+ if run_callbacks(:"before_#{callback}"){ |result, object| result == false } != false
+ blk.call
+ end
+ run_callbacks(:"after_#{callback}"){ |result, object| result == false }
end
end
end
- module InstanceMethods
- # The callbacks in < 2.1 don't worry about the extra options or the
- # block, so just run what we have available.
- def run_callbacks(meth, opts = nil, &blk)
- callback(meth)
+ module Rails3
+ def self.included(base)
+ base.extend(Defining)
+ base.send(:include, Running)
end
+
+ module Defining
+ def define_paperclip_callbacks(*callbacks)
+ define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
+ callbacks.each do |callback|
+ eval <<-end_callbacks
+ def before_#{callback}(*args, &blk)
+ set_callback(:#{callback}, :before, *args, &blk)
+ end
+ def after_#{callback}(*args, &blk)
+ set_callback(:#{callback}, :after, *args, &blk)
+ end
+ end_callbacks
+ end
+ end
+ end
+
+ module Running
+ def run_paperclip_callbacks(callback, opts = nil, &block)
+ run_callbacks(callback, opts, &block)
+ end
+ end
+
end
+
end
end
View
80 lib/paperclip/command_line.rb
@@ -0,0 +1,80 @@
+module Paperclip
+ class CommandLine
+ class << self
+ attr_accessor :path
+ end
+
+ def initialize(binary, params = "", options = {})
+ @binary = binary.dup
+ @params = params.dup
+ @options = options.dup
+ @swallow_stderr = @options.has_key?(:swallow_stderr) ? @options.delete(:swallow_stderr) : Paperclip.options[:swallow_stderr]
+ @expected_outcodes = @options.delete(:expected_outcodes)
+ @expected_outcodes ||= [0]
+ end
+
+ def command
+ cmd = []
+ cmd << full_path(@binary)
+ cmd << interpolate(@params, @options)
+ cmd << bit_bucket if @swallow_stderr
+ cmd.join(" ")
+ end
+
+ def run
+ Paperclip.log(command)
+ begin
+ output = self.class.send(:'`', command)
+ rescue Errno::ENOENT
+ raise Paperclip::CommandNotFoundError
+ end
+ if $?.exitstatus == 127
+ raise Paperclip::CommandNotFoundError
+ end
+ unless @expected_outcodes.include?($?.exitstatus)
+ raise Paperclip::PaperclipCommandLineError, "Command '#{command}' returned #{$?.exitstatus}. Expected #{@expected_outcodes.join(", ")}"
+ end
+ output
+ end
+
+ private
+
+ def full_path(binary)
+ [self.class.path, binary].compact.join("/")
+ end
+
+ def interpolate(pattern, vars)
+ # interpolates :variables and :{variables}
+ pattern.gsub(%r#:(?:\w+|\{\w+\})#) do |match|
+ key = match[1..-1]
+ key = key[1..-2] if key[0,1] == '{'
+ if invalid_variables.include?(key)
+ raise PaperclipCommandLineError,
+ "Interpolation of #{key} isn't allowed."
+ end
+ shell_quote(vars[key.to_sym])
+ end
+ end
+
+ def invalid_variables
+ %w(expected_outcodes swallow_stderr)
+ end
+
+ def shell_quote(string)
+ return "" if string.nil? or string.blank?
+ if self.class.unix?
+ string.split("'").map{|m| "'#{m}'" }.join("\\'")
+ else
+ %{"#{string}"}
+ end
+ end
+
+ def bit_bucket
+ self.class.unix? ? "2>/dev/null" : "2>NUL"
+ end
+
+ def self.unix?
+ File.exist?("/dev/null")
+ end
+ end
+end
View
14 lib/paperclip/geometry.rb
@@ -16,7 +16,7 @@ def initialize width = nil, height = nil, modifier = nil
def self.from_file file
file = file.path if file.respond_to? "path"
geometry = begin
- Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"[0]])
+ Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
rescue PaperclipCommandLineError
""
end
@@ -75,12 +75,12 @@ def inspect
to_s
end
- # Returns the scaling and cropping geometries (in string-based ImageMagick format)
- # neccessary to transform this Geometry into the Geometry given. If crop is true,
- # then it is assumed the destination Geometry will be the exact final resolution.
- # In this case, the source Geometry is scaled so that an image containing the
- # destination Geometry would be completely filled by the source image, and any
- # overhanging image would be cropped. Useful for square thumbnail images. The cropping
+ # Returns the scaling and cropping geometries (in string-based ImageMagick format)
+ # neccessary to transform this Geometry into the Geometry given. If crop is true,
+ # then it is assumed the destination Geometry will be the exact final resolution.
+ # In this case, the source Geometry is scaled so that an image containing the
+ # destination Geometry would be completely filled by the source image, and any
+ # overhanging image would be cropped. Useful for square thumbnail images. The cropping
# is weighted at the center of the Geometry.
def transformation_to dst, crop = false
if crop
View
51 lib/paperclip/interpolations.rb
@@ -34,72 +34,81 @@ def self.interpolate pattern, *args
end
# Returns the filename, the same way as ":basename.:extension" would.
- def filename attachment, style
- "#{basename(attachment, style)}.#{extension(attachment, style)}"
+ def filename attachment, style_name
+ "#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
end
# Returns the interpolated URL. Will raise an error if the url itself
# contains ":url" to prevent infinite recursion. This interpolation
# is used in the default :path to ease default specifications.
- def url attachment, style
- raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
- attachment.url(style, false)
+ RIGHT_HERE = "#{__FILE__.gsub(%r{^\./}, "")}:#{__LINE__ + 3}"
+ def url attachment, style_name
+ raise InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }
+ attachment.url(style_name, false)
end
# Returns the timestamp as defined by the <attachment>_updated_at field
- def timestamp attachment, style
+ def timestamp attachment, style_name
attachment.instance_read(:updated_at).to_s
end
- # Returns the RAILS_ROOT constant.
- def rails_root attachment, style
- RAILS_ROOT
+ # Returns the Rails.root constant.
+ def rails_root attachment, style_name
+ Rails.root
end
- # Returns the RAILS_ENV constant.
- def rails_env attachment, style
- RAILS_ENV
+ # Returns the Rails.env constant.
+ def rails_env attachment, style_name
+ Rails.env
end
# Returns the underscored, pluralized version of the class name.
# e.g. "users" for the User class.
- def class attachment, style
+ # NOTE: The arguments need to be optional, because some tools fetch
+ # all class names. Calling #class will return the expected class.
+ def class attachment = nil, style_name = nil
+ return super() if attachment.nil? && style_name.nil?
attachment.instance.class.to_s.underscore.pluralize
end
# Returns the basename of the file. e.g. "file" for "file.jpg"
- def basename attachment, style
+ def basename attachment, style_name
attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
end
# Returns the extension of the file. e.g. "jpg" for "file.jpg"
# If the style has a format defined, it will return the format instead
# of the actual extension.
- def extension attachment, style
- ((style = attachment.styles[style]) && style[:format]) ||
+ def extension attachment, style_name
+ ((style = attachment.styles[style_name]) && style[:format]) ||
File.extname(attachment.original_filename).gsub(/^\.+/, "")
end
# Returns the id of the instance.
- def id attachment, style
+ def id attachment, style_name
attachment.instance.id
end
+ # Returns the fingerprint of the instance.
+ def fingerprint attachment, style_name
+ attachment.fingerprint
+ end
+
# Returns the id of the instance in a split path form. e.g. returns
# 000/001/234 for an id of 1234.
- def id_partition attachment, style
+ def id_partition attachment, style_name
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
end
# Returns the pluralized form of the attachment name. e.g.
# "avatars" for an attachment of :avatar
- def attachment attachment, style
+ def attachment attachment, style_name
attachment.name.to_s.downcase.pluralize
end
# Returns the style, or the default style if nil is supplied.
- def style attachment, style
- style || attachment.default_style
+ def style attachment, style_name
+ style_name || attachment.default_style
end
end
end
View
35 lib/paperclip/iostream.rb
@@ -1,47 +1,34 @@
# Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying
# and Tempfile conversion.
module IOStream
-
# Returns a Tempfile containing the contents of the readable object.
- def to_tempfile
- tempfile = Tempfile.new("stream")
+ def to_tempfile(object)
+ return object.to_tempfile if object.respond_to?(:to_tempfile)
+ name = object.respond_to?(:original_filename) ? object.original_filename : (object.respond_to?(:path) ? object.path : "stream")
+ tempfile = Paperclip::Tempfile.new(["stream", File.extname(name)])
tempfile.binmode
- self.stream_to(tempfile)
+ stream_to(object, tempfile)
end
# Copies one read-able object from one place to another in blocks, obviating the need to load
- # the whole thing into memory. Defaults to 8k blocks. If this module is included in both
- # StringIO and Tempfile, then either can have its data copied anywhere else without typing
- # worries or memory overhead worries. Returns a File if a String is passed in as the destination
- # and returns the IO or Tempfile as passed in if one is sent as the destination.
- def stream_to path_or_file, in_blocks_of = 8192
+ # the whole thing into memory. Defaults to 8k blocks. Returns a File if a String is passed
+ # in as the destination and returns the IO or Tempfile as passed in if one is sent as the destination.
+ def stream_to object, path_or_file, in_blocks_of = 8192
dstio = case path_or_file
when String then File.new(path_or_file, "wb+")
when IO then path_or_file
when Tempfile then path_or_file
end
buffer = ""
- self.rewind
- while self.read(in_blocks_of, buffer) do
+ object.rewind
+ while object.read(in_blocks_of, buffer) do
dstio.write(buffer)
end
- dstio.rewind
+ dstio.rewind
dstio
end
end
-class IO #:nodoc:
- include IOStream
-end