Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Separate the UrlGenerator out from the Attachment. Some example plugi…

…ns that could be written include generating thumbnails on the fly for different thumbnail sizes, or delaying the thumbnail generation until it is first called.
  • Loading branch information...
commit bc5c51d1ece1ee45f94b056a0f5a1674d7e8cba9 1 parent af689b4
@mike-burns mike-burns authored mike-burns committed
View
124 lib/paperclip/attachment.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'uri'
+require 'paperclip/url_generator'
module Paperclip
# The Attachment class manages the files for a given attachment. It saves
@@ -25,7 +26,9 @@ def self.default_options
:use_default_time_zone => true,
:hash_digest => "SHA1",
:hash_data => ":class/:attachment/:id/:style/:updated_at",
- :preserve_files => false
+ :preserve_files => false,
+ :interpolator => Paperclip::Interpolations,
+ :url_generator => Paperclip::UrlGenerator
}
end
@@ -38,25 +41,26 @@ def self.default_options
#
# Options include:
#
- # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
- # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
- # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
- # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
- # +default_url+ - a URL for the missing image
- # +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
- # +storage+ - the storage mechanism. Defaults to :filesystem
- # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
- # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
- # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
- # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
- # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
- # +hash_secret+ - a secret passed to the +hash_digest+
- # +convert_options+ - flags passed to the +convert+ command for processing
- # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
- # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
- # +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
- # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
- def initialize name, instance, options = {}
+ # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
+ # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
+ # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
+ # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
+ # +default_url+ - a URL for the missing image
+ # +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
+ # +storage+ - the storage mechanism. Defaults to :filesystem
+ # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
+ # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
+ # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
+ # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
+ # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
+ # +hash_secret+ - a secret passed to the +hash_digest+
+ # +convert_options+ - flags passed to the +convert+ command for processing
+ # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
+ # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
+ # +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
+ # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
+ # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
+ def initialize(name, instance, options = {})
@name = name
@instance = instance
@@ -68,7 +72,8 @@ def initialize name, instance, options = {}
@queued_for_write = {}
@errors = {}
@dirty = false
- @interpolator = (options[:interpolator] || Paperclip::Interpolations)
+ @interpolator = options[:interpolator]
+ @url_generator = options[:url_generator].new(self, @options)
initialize_storage
end
@@ -124,19 +129,36 @@ def assign uploaded_file
uploaded_file.close if close_uploaded_file
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 use_timestamp to false
- # if you want to stop the attachment update time appended to the url
+ # Returns the public URL of the attachment with a given style. This does
+ # not necessarily need to point to a file that your Web server can access
+ # and can instead point to an action in your app, for example for fine grained
+ # security; this has a serious performance tradeoff.
+ #
+ # Options:
+ #
+ # +timestamp+ - Add a timestamp to the end of the URL. Default: true.
+ # +escape+ - Perform URI escaping to the URL. Default: true.
+ #
+ # Global controls (set on has_attached_file):
+ #
+ # +interpolator+ - The object that fills in a URL pattern's variables.
+ # +default_url+ - The image to show when the attachment has no image.
+ # +url+ - The URL for a saved image.
+ # +url_generator+ - The object that generates a URL. Default: Paperclip::UrlGenerator.
+ #
+ # As mentioned just above, the object that generates this URL can be passed
+ # in, for finer control. This object must respond to two methods:
+ #
+ # +#new(Paperclip::Attachment, Paperclip::Options)+
+ # +#for(style_name, options_hash)+
def url(style_name = default_style, options = {})
- options = handle_url_options(options)
- url = interpolate(most_appropriate_url, style_name)
+ default_options = {:timestamp => @options.use_timestamp, :escape => true}
- url = url_timestamp(url) if options[:timestamp]
- url = escape_url(url) if options[:escape]
- url
+ if options == true || options == false # Backwards compatibility.
+ @url_generator.for(style_name, default_options.merge(:timestamp => options))
+ else
+ @url_generator.for(style_name, default_options.merge(options))
+ end
end
# Returns the path of the attachment as defined by the :path option. If the
@@ -328,44 +350,6 @@ def instance_read(attr)
private
- def handle_url_options(options)
- timestamp = extract_timestamp(options)
- options = {} if options == true || options == false
- options[:timestamp] = timestamp
- options[:escape] = true if options[:escape].nil?
- options
- end
-
- def extract_timestamp(options)
- possibilities = [((options == true || options == false) ? options : nil),
- (options.respond_to?(:[]) ? options[:timestamp] : nil),
- @options.use_timestamp]
- possibilities.find{|n| !n.nil? }
- end
-
- def default_url
- return @options.default_url.call(self) if @options.default_url.is_a?(Proc)
- @options.default_url
- end
-
- def most_appropriate_url
- if original_filename.nil?
- default_url
- else
- @options.url
- end
- end
-
- def url_timestamp(url)
- return url unless updated_at
- delimiter_char = url.include?("?") ? "&" : "?"
- "#{url}#{delimiter_char}#{updated_at.to_s}"
- end
-
- def escape_url(url)
- url.respond_to?(:escape) ? url.escape : URI.escape(url)
- end
-
def ensure_required_accessors! #:nodoc:
%w(file_name).each do |field|
unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
View
3  lib/paperclip/options.rb
@@ -34,6 +34,9 @@ def initialize(attachment, hash)
@processors = hash[:processors]
@preserve_files = hash[:preserve_files]
@http_proxy = hash[:http_proxy]
+ @interpolator = hash[:interpolator]
+ @escape = hash[:escape]
+ @url_generator = hash[:url_generator]
#s3 options
@s3_credentials = hash[:s3_credentials]
View
64 lib/paperclip/url_generator.rb
@@ -0,0 +1,64 @@
+require 'uri'
+
+module Paperclip
+ class UrlGenerator
+ def initialize(attachment, attachment_options)
+ @attachment = attachment
+ @attachment_options = attachment_options
+ end
+
+ def for(style_name, options)
+ escape_url_as_needed(
+ timestamp_as_needed(
+ @attachment_options.interpolator.interpolate(most_appropriate_url, @attachment, style_name),
+ options
+ ), options)
+ end
+
+ private
+
+ # This method is all over the place.
+ def default_url
+ if @attachment_options.default_url.respond_to?(:call)
+ @attachment_options.default_url.call(@attachment)
+ elsif @attachment_options.default_url.is_a?(Symbol)
+ @attachment.instance.send(@attachment_options.default_url)
+ else
+ @attachment_options.default_url
+ end
+ end
+
+ def most_appropriate_url
+ if @attachment.original_filename.nil?
+ default_url
+ else
+ @attachment_options.url
+ end
+ end
+
+ def timestamp_as_needed(url, options)
+ if options[:timestamp] && timestamp_possible?
+ delimiter_char = url.include?('?') ? '&' : '?'
+ "#{url}#{delimiter_char}#{@attachment.updated_at.to_s}"
+ else
+ url
+ end
+ end
+
+ def timestamp_possible?
+ @attachment.respond_to?(:updated_at) && @attachment.updated_at.present?
+ end
+
+ def escape_url_as_needed(url, options)
+ if options[:escape]
+ escape_url(url)
+ else
+ url
+ end
+ end
+
+ def escape_url(url)
+ url.respond_to?(:escape) ? url.escape : URI.escape(url)
+ end
+ end
+end
View
229 test/attachment_test.rb
@@ -1,46 +1,92 @@
# encoding: utf-8
require './test/helper'
+require 'paperclip/attachment'
-class Dummy
- # This is a dummy class
-end
+class Dummy; end
class AttachmentTest < Test::Unit::TestCase
- should "return the path based on the url by default" do
- @attachment = attachment :url => "/:class/:id/:basename"
- @model = @attachment.instance
- @model.id = 1234
- @model.avatar_file_name = "fake.jpg"
- assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
+ should "handle a boolean second argument to #url" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder)
+
+ attachment.url(:style_name, true)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:timestamp => true, :escape => true)
+
+ attachment.url(:style_name, false)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:timestamp => false, :escape => true)
end
- should "return the url by interpolating the default_url option when no file assigned" do
- @attachment = attachment :default_url => ":class/blegga.png"
- @model = @attachment.instance
- assert_nil @model.avatar_file_name
- assert_equal "fake_models/blegga.png", @attachment.url
+ should "pass the style and options through to the URL generator on #url" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder)
+
+ attachment.url(:style_name, :options => :values)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:options => :values)
end
- should "return the url by executing and interpolating the default_url Proc when no file assigned" do
- @attachment = attachment :default_url => lambda { |a| ":class/blegga.png" }
- @model = @attachment.instance
- assert_nil @model.avatar_file_name
- assert_equal "fake_models/blegga.png", @attachment.url
+ should "pass default options through when #url is given one argument" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name,
+ :instance,
+ :url_generator => mock_url_generator_builder,
+ :use_timestamp => true)
+
+ attachment.url(:style_name)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
end
- should "return the url by executing and interpolating the default_url Proc with attachment arg when no file assigned" do
- @attachment = attachment :default_url => lambda { |a| a.instance.some_method_to_determine_default_url }
- @model = @attachment.instance
- @model.stubs(:some_method_to_determine_default_url).returns(":class/blegga.png")
- assert_nil @model.avatar_file_name
- assert_equal "fake_models/blegga.png", @attachment.url
+ should "pass default style and options through when #url is given no arguments" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name,
+ :instance,
+ :default_style => 'default style',
+ :url_generator => mock_url_generator_builder,
+ :use_timestamp => true)
+
+ attachment.url
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
+ assert mock_url_generator_builder.has_generated_url_with_style_name?('default style')
end
- should "return the url by executing and interpolating the default_url when assigned with symbol as method in attachment model" do
- @attachment = attachment :default_url => :some_method_to_determine_default_url
+ should "pass the option :timestamp => true if :use_timestamp is true and :timestamp is not passed" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name,
+ :instance,
+ :url_generator => mock_url_generator_builder,
+ :use_timestamp => true)
+
+ attachment.url(:style_name)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
+ end
+
+ should "pass the option :timestamp => false if :use_timestamp is false and :timestamp is not passed" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name,
+ :instance,
+ :url_generator => mock_url_generator_builder,
+ :use_timestamp => false)
+
+ attachment.url(:style_name)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => false)
+ end
+
+ should "not change the :timestamp if :timestamp is passed" do
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
+ attachment = Paperclip::Attachment.new(:name,
+ :instance,
+ :url_generator => mock_url_generator_builder,
+ :use_timestamp => false)
+
+ attachment.url(:style_name, :timestamp => true)
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
+ end
+
+ should "return the path based on the url by default" do
+ @attachment = attachment :url => "/:class/:id/:basename"
@model = @attachment.instance
- @model.stubs(:some_method_to_determine_default_url).returns(":class/female_:style_blegga.png")
- assert_equal "fake_models/female_foostyle_blegga.png", @attachment.url(:foostyle)
+ @model.id = 1234
+ @model.avatar_file_name = "fake.jpg"
+ assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
end
context "Attachment default_options" do
@@ -160,36 +206,6 @@ class AttachmentTest < Test::Unit::TestCase
end
end
- context "An attachment" do
- setup do
- @file = StringIO.new("...")
- end
-
- context "using default time zone" do
- setup do
- rebuild_model :url => "X"
- @dummy = Dummy.new
- @dummy.avatar = @file
- end
-
- should "generate a url with a timestamp when passing true" do
- assert_equal "X?#{@dummy.avatar_updated_at.to_i.to_s}", @dummy.avatar.url(:style, true)
- end
-
- should "not generate a url with a timestamp when passing false" do
- assert_equal "X", @dummy.avatar.url(:style, false)
- end
-
- should "generate a url with a timestamp when setting a timestamp option" do
- assert_equal "X?#{@dummy.avatar_updated_at.to_i.to_s}", @dummy.avatar.url(:style, :timestamp => true)
- end
-
- should "not generate a url with a timestamp when setting a timestamp option to false" do
- assert_equal "X", @dummy.avatar.url(:style, :timestamp => false)
- end
- end
- end
-
context "An attachment with :hash interpolations" do
setup do
@file = StringIO.new("...")
@@ -406,27 +422,6 @@ def thumb; "-thumb"; end
end
end
- context "An attachment with :url that is a proc" do
- setup do
- rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
-
- @file = File.new(File.join(File.dirname(__FILE__),
- "fixtures",
- "5k.png"), 'rb')
- @dummyA = Dummy.new(:other => 'a')
- @dummyA.avatar = @file
- @dummyB = Dummy.new(:other => 'b')
- @dummyB.avatar = @file
- end
-
- teardown { @file.close }
-
- should "return correct url" do
- assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
- assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
- end
- end
-
geometry_specs = [
[ lambda{|z| "50x50#" }, :png ],
lambda{|z| "50x50#" },
@@ -552,16 +547,16 @@ class Paperclip::Test < Paperclip::Processor; end
rebuild_model :storage => :FileSystem
@dummy = Dummy.new
assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
-
+
rebuild_model :storage => :Filesystem
@dummy = Dummy.new
assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
end
-
+
should "convert underscored storage name to camelcase" do
rebuild_model :storage => :not_here
@dummy = Dummy.new
- exception = assert_raises(Paperclip::StorageMethodNotFound) do |e|
+ exception = assert_raises(Paperclip::StorageMethodNotFound) do
@dummy.avatar
end
assert exception.message.include?("NotHere")
@@ -770,12 +765,6 @@ def do_after_all; end
end
end
- should "return its default_url when no file assigned" do
- assert @attachment.to_file.nil?
- assert_equal "/avatars/original/missing.png", @attachment.url
- assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
- end
-
should "return nil as path when no file assigned" do
assert @attachment.to_file.nil?
assert_equal nil, @attachment.path
@@ -802,29 +791,6 @@ def do_after_all; end
@attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
end
- should "return a correct url even if the file does not exist" do
- assert_nil @attachment.to_file
- assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
- end
-
- should "make sure the updated_at mtime is in the url if it is defined" do
- assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
- end
-
- should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
- assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
- end
-
- context "with the updated_at field removed" do
- setup do
- @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
- end
-
- should "only return the url without the updated_at when sent #url" do
- assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
- end
- end
-
should "return the proper path when filename has a single .'s" do
assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
end
@@ -864,14 +830,6 @@ def do_after_all; end
@attachment.save
end
- should "return the real url" do
- file = @attachment.to_file
- assert file
- assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
- assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
- file.close
- end
-
should "commit the files to disk" do
[:large, :medium, :small].each do |style|
io = @attachment.to_file(style)
@@ -941,22 +899,6 @@ def do_after_all; end
end
end
- context "with a file that has space in file name" do
- setup do
- @attachment.stubs(:instance_read).with(:file_name).returns("spaced file.png")
- @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
- @attachment.stubs(:instance_read).with(:file_size).returns(12345)
- dtnow = DateTime.now
- @now = Time.now
- Time.stubs(:now).returns(@now)
- @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
- end
-
- should "returns an escaped version of the URL" do
- assert_match /\/spaced%20file\.png/, @attachment.url
- end
- end
-
context "when trying a nonexistant storage type" do
setup do
rebuild_model :storage => :not_here
@@ -1109,21 +1051,6 @@ def do_after_all; end
end
end
- context "setting an interpolation class" do
- should "produce the URL with the given interpolations" do
- Interpolator = Class.new do
- def self.interpolate(pattern, attachment, style_name)
- "hello"
- end
- end
-
- instance = Dummy.new
- attachment = Paperclip::Attachment.new(:avatar, instance, :interpolator => Interpolator)
-
- assert_equal "hello", attachment.url
- end
- end
-
context "An attached file" do
setup do
rebuild_model
View
4 test/helper.rb
@@ -53,6 +53,10 @@ def setup
ActiveRecord::Base.establish_connection(config['test'])
Paperclip.options[:logger] = ActiveRecord::Base.logger
+Dir[File.join(File.dirname(__FILE__), 'support','*')].each do |f|
+ require f
+end
+
def reset_class class_name
ActiveRecord::Base.send(:include, Paperclip::Glue)
Object.send(:remove_const, class_name) rescue nil
View
8 test/options_test.rb
@@ -1,7 +1,7 @@
# encoding: utf-8
require './test/helper'
-class MockAttachment < Struct.new(:one, :two)
+class DSO < Struct.new(:one, :two)
def instance
self
end
@@ -17,7 +17,7 @@ class OptionsTest < Test::Unit::TestCase
context "#styles with a plain hash" do
setup do
- @attachment = MockAttachment.new(nil, nil)
+ @attachment = DSO.new(nil, nil)
@options = Paperclip::Options.new(@attachment,
:styles => {
:something => ["400x400", :png]
@@ -35,7 +35,7 @@ class OptionsTest < Test::Unit::TestCase
context "#styles is a proc" do
setup do
- @attachment = MockAttachment.new("123x456", :doc)
+ @attachment = DSO.new("123x456", :doc)
@options = Paperclip::Options.new(@attachment,
:styles => lambda {|att|
{:something => {:geometry => att.one, :format => att.two}}
@@ -59,7 +59,7 @@ class OptionsTest < Test::Unit::TestCase
context "#processors" do
setup do
- @attachment = MockAttachment.new(nil, nil)
+ @attachment = DSO.new(nil, nil)
end
should "return processors if not a proc" do
@options = Paperclip::Options.new(@attachment, :processors => [:one])
View
22 test/support/mock_attachment.rb
@@ -0,0 +1,22 @@
+class MockAttachment
+ attr_accessor :updated_at, :original_filename
+
+ def initialize(options = {})
+ @model = options[:model]
+ @responds_to_updated_at = options[:responds_to_updated_at]
+ @updated_at = options[:updated_at]
+ @original_filename = options[:original_filename]
+ end
+
+ def instance
+ @model
+ end
+
+ def respond_to?(meth)
+ if meth.to_s == "updated_at"
+ @responds_to_updated_at || @updated_at
+ else
+ super
+ end
+ end
+end
View
24 test/support/mock_interpolator.rb
@@ -0,0 +1,24 @@
+class MockInterpolator
+ def initialize(options = {})
+ @options = options
+ end
+
+ def interpolate(pattern, attachment, style_name)
+ @interpolated_pattern = pattern
+ @interpolated_attachment = attachment
+ @interpolated_style_name = style_name
+ @options[:result]
+ end
+
+ def has_interpolated_pattern?(pattern)
+ @interpolated_pattern == pattern
+ end
+
+ def has_interpolated_style_name?(style_name)
+ @interpolated_style_name == style_name
+ end
+
+ def has_interpolated_attachment?(attachment)
+ @interpolated_attachment == attachment
+ end
+end
View
2  test/support/mock_model.rb
@@ -0,0 +1,2 @@
+class MockModel
+end
View
27 test/support/mock_url_generator_builder.rb
@@ -0,0 +1,27 @@
+class MockUrlGeneratorBuilder
+ def initializer
+ end
+
+ def new(attachment, attachment_options)
+ @attachment = attachment
+ @attachment_options = attachment_options
+ self
+ end
+
+ def for(style_name, options)
+ @generated_url_with_style_name = style_name
+ @generated_url_with_options = options
+ "hello"
+ end
+
+ def has_generated_url_with_options?(options)
+ # options.is_a_subhash_of(@generated_url_with_options)
+ options.inject(true) do |acc,(k,v)|
+ acc && @generated_url_with_options[k] == v
+ end
+ end
+
+ def has_generated_url_with_style_name?(style_name)
+ @generated_url_with_style_name == style_name
+ end
+end
View
194 test/url_generator_test.rb
@@ -0,0 +1,194 @@
+# encoding: utf-8
+require './test/helper'
+require 'paperclip/url_generator'
+require 'paperclip/options'
+
+class UrlGeneratorTest < Test::Unit::TestCase
+ should "use the given interpolator" do
+ expected = "the expected result"
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new(:result => expected)
+
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment,
+ Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator))
+ result = url_generator.for(:style_name, {})
+
+ assert_equal expected, result
+ assert mock_interpolator.has_interpolated_attachment?(mock_attachment)
+ assert mock_interpolator.has_interpolated_style_name?(:style_name)
+ end
+
+ should "use the default URL when no file is assigned" do
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new
+ default_url = "the default url"
+ options = Paperclip::Options.new(mock_attachment,
+ :interpolator => mock_interpolator,
+ :default_url => default_url)
+
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+ url_generator.for(:style_name, {})
+
+ assert mock_interpolator.has_interpolated_pattern?(default_url),
+ "expected the interpolator to be passed #{default_url.inspect} but it wasn't"
+ end
+
+ should "execute the default URL lambda when no file is assigned" do
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new
+ default_url = lambda {|attachment| "the #{attachment.class.name} default url" }
+ options = Paperclip::Options.new(mock_attachment,
+ :interpolator => mock_interpolator,
+ :default_url => default_url)
+
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+ url_generator.for(:style_name, {})
+
+ assert mock_interpolator.has_interpolated_pattern?("the MockAttachment default url"),
+ %{expected the interpolator to be passed "the MockAttachment default url", but it wasn't}
+ end
+
+ should "execute the method named by the symbol as the default URL when no file is assigned" do
+ mock_model = MockModel.new
+ mock_attachment = MockAttachment.new(:model => mock_model)
+ mock_interpolator = MockInterpolator.new
+ default_url = :to_s
+ options = Paperclip::Options.new(mock_attachment,
+ :interpolator => mock_interpolator,
+ :default_url => default_url)
+
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+ url_generator.for(:style_name, {})
+
+ assert mock_interpolator.has_interpolated_pattern?(mock_model.to_s),
+ %{expected the interpolator to be passed #{mock_model.to_s}, but it wasn't}
+ end
+
+ should "URL-escape spaces if asked to" do
+ expected = "the expected result"
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:escape => true})
+
+ assert_equal "the%20expected%20result", result
+ end
+
+ should "escape the result of the interpolator using a method on the object, if asked to escape" do
+ expected = Class.new do
+ def escape
+ "the escaped result"
+ end
+ end.new
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:escape => true})
+
+ assert_equal "the escaped result", result
+ end
+
+ should "leave spaces unescaped as asked to" do
+ expected = "the expected result"
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:escape => false})
+
+ assert_equal "the expected result", result
+ end
+
+ should "default to leaving spaces unescaped" do
+ expected = "the expected result"
+ mock_attachment = MockAttachment.new
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {})
+
+ assert_equal "the expected result", result
+ end
+
+ should "produce URLs without the updated_at value when the object does not respond to updated_at" do
+ expected = "the expected result"
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ mock_attachment = MockAttachment.new(:responds_to_updated_at => false)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:timestamp => true})
+
+ assert_equal expected, result
+ end
+
+ should "produce URLs without the updated_at value when the updated_at value is nil" do
+ expected = "the expected result"
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ mock_attachment = MockAttachment.new(:responds_to_updated_at => true, :updated_at => nil)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:timestamp => true})
+
+ assert_equal expected, result
+ end
+
+ should "produce URLs with the updated_at when it exists" do
+ expected = "the expected result"
+ updated_at = 1231231234
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ mock_attachment = MockAttachment.new(:updated_at => updated_at)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:timestamp => true})
+
+ assert_equal "#{expected}?#{updated_at}", result
+ end
+
+ should "produce URLs with the updated_at when it exists, separated with a & if a ? already exists" do
+ expected = "the?expected result"
+ updated_at = 1231231234
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ mock_attachment = MockAttachment.new(:updated_at => updated_at)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:timestamp => true})
+
+ assert_equal "#{expected}&#{updated_at}", result
+ end
+
+ should "produce URLs without the updated_at when told to do as much" do
+ expected = "the expected result"
+ updated_at = 1231231234
+ mock_interpolator = MockInterpolator.new(:result => expected)
+ mock_attachment = MockAttachment.new(:updated_at => updated_at)
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+
+ result = url_generator.for(:style_name, {:timestamp => false})
+
+ assert_equal expected, result
+ end
+
+ should "produce the correct URL when the instance has a file name" do
+ expected = "the expected result"
+ mock_attachment = MockAttachment.new(:original_filename => 'exists')
+ mock_interpolator = MockInterpolator.new
+ options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator, :url => expected)
+
+ url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
+ url_generator.for(:style_name, {})
+
+ assert mock_interpolator.has_interpolated_pattern?(expected),
+ "expected the interpolator to be passed #{expected.inspect} but it wasn't"
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.