Skip to content
This repository
Browse code

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 authored October 28, 2011 mike-burns committed October 29, 2011
124  lib/paperclip/attachment.rb
... ...
@@ -1,5 +1,6 @@
1 1
 # encoding: utf-8
2 2
 require 'uri'
  3
+require 'paperclip/url_generator'
3 4
 
4 5
 module Paperclip
5 6
   # The Attachment class manages the files for a given attachment. It saves
@@ -25,7 +26,9 @@ def self.default_options
25 26
         :use_default_time_zone => true,
26 27
         :hash_digest           => "SHA1",
27 28
         :hash_data             => ":class/:attachment/:id/:style/:updated_at",
28  
-        :preserve_files        => false
  29
+        :preserve_files        => false,
  30
+        :interpolator          => Paperclip::Interpolations,
  31
+        :url_generator         => Paperclip::UrlGenerator
29 32
       }
30 33
     end
31 34
 
@@ -38,25 +41,26 @@ def self.default_options
38 41
     #
39 42
     # Options include:
40 43
     #
41  
-    #  +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
42  
-    #  +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
43  
-    #  +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
44  
-    #  +only_process+ - style args to be run through the post-processor. This defaults to the empty list
45  
-    #  +default_url+ - a URL for the missing image
46  
-    #  +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
47  
-    #  +storage+ - the storage mechanism. Defaults to :filesystem
48  
-    #  +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
49  
-    #  +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
50  
-    #  +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
51  
-    #  +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
52  
-    #  +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
53  
-    #  +hash_secret+ - a secret passed to the +hash_digest+
54  
-    #  +convert_options+ - flags passed to the +convert+ command for processing
55  
-    #  +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
56  
-    #  +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
57  
-    #  +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
58  
-    #  +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
59  
-    def initialize name, instance, options = {}
  44
+    # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
  45
+    # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
  46
+    # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
  47
+    # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
  48
+    # +default_url+ - a URL for the missing image
  49
+    # +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
  50
+    # +storage+ - the storage mechanism. Defaults to :filesystem
  51
+    # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
  52
+    # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
  53
+    # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
  54
+    # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
  55
+    # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
  56
+    # +hash_secret+ - a secret passed to the +hash_digest+
  57
+    # +convert_options+ - flags passed to the +convert+ command for processing
  58
+    # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
  59
+    # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
  60
+    # +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
  61
+    # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
  62
+    # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
  63
+    def initialize(name, instance, options = {})
60 64
       @name              = name
61 65
       @instance          = instance
62 66
 
@@ -68,7 +72,8 @@ def initialize name, instance, options = {}
68 72
       @queued_for_write      = {}
69 73
       @errors                = {}
70 74
       @dirty                 = false
71  
-      @interpolator          = (options[:interpolator] || Paperclip::Interpolations)
  75
+      @interpolator          = options[:interpolator]
  76
+      @url_generator         = options[:url_generator].new(self, @options)
72 77
 
73 78
       initialize_storage
74 79
     end
@@ -124,19 +129,36 @@ def assign uploaded_file
124 129
       uploaded_file.close if close_uploaded_file
125 130
     end
126 131
 
127  
-    # Returns the public URL of the attachment, with a given style. Note that
128  
-    # this does not necessarily need to point to a file that your web server
129  
-    # can access and can point to an action in your app, if you need fine
130  
-    # grained security.  This is not recommended if you don't need the
131  
-    # security, however, for performance reasons. Set use_timestamp to false
132  
-    # if you want to stop the attachment update time appended to the url
  132
+    # Returns the public URL of the attachment with a given style. This does
  133
+    # not necessarily need to point to a file that your Web server can access
  134
+    # and can instead point to an action in your app, for example for fine grained
  135
+    # security; this has a serious performance tradeoff.
  136
+    #
  137
+    # Options:
  138
+    #
  139
+    # +timestamp+ - Add a timestamp to the end of the URL. Default: true.
  140
+    # +escape+    - Perform URI escaping to the URL. Default: true.
  141
+    #
  142
+    # Global controls (set on has_attached_file):
  143
+    #
  144
+    # +interpolator+  - The object that fills in a URL pattern's variables.
  145
+    # +default_url+   - The image to show when the attachment has no image.
  146
+    # +url+           - The URL for a saved image.
  147
+    # +url_generator+ - The object that generates a URL. Default: Paperclip::UrlGenerator.
  148
+    #
  149
+    # As mentioned just above, the object that generates this URL can be passed
  150
+    # in, for finer control. This object must respond to two methods:
  151
+    #
  152
+    # +#new(Paperclip::Attachment, Paperclip::Options)+
  153
+    # +#for(style_name, options_hash)+
133 154
     def url(style_name = default_style, options = {})
134  
-      options = handle_url_options(options)
135  
-      url = interpolate(most_appropriate_url, style_name)
  155
+      default_options = {:timestamp => @options.use_timestamp, :escape => true}
136 156
 
137  
-      url = url_timestamp(url) if options[:timestamp]
138  
-      url = escape_url(url)    if options[:escape]
139  
-      url
  157
+      if options == true || options == false # Backwards compatibility.
  158
+        @url_generator.for(style_name, default_options.merge(:timestamp => options))
  159
+      else
  160
+        @url_generator.for(style_name, default_options.merge(options))
  161
+      end
140 162
     end
141 163
 
142 164
     # Returns the path of the attachment as defined by the :path option. If the
@@ -328,44 +350,6 @@ def instance_read(attr)
328 350
 
329 351
     private
330 352
 
331  
-    def handle_url_options(options)
332  
-      timestamp = extract_timestamp(options)
333  
-      options = {} if options == true || options == false
334  
-      options[:timestamp] = timestamp
335  
-      options[:escape] = true if options[:escape].nil?
336  
-      options
337  
-    end
338  
-
339  
-    def extract_timestamp(options)
340  
-      possibilities = [((options == true || options == false) ? options : nil),
341  
-                       (options.respond_to?(:[]) ? options[:timestamp] : nil),
342  
-                       @options.use_timestamp]
343  
-      possibilities.find{|n| !n.nil? }
344  
-    end
345  
-
346  
-    def default_url
347  
-      return @options.default_url.call(self) if @options.default_url.is_a?(Proc)
348  
-      @options.default_url
349  
-    end
350  
-
351  
-    def most_appropriate_url
352  
-      if original_filename.nil?
353  
-        default_url
354  
-      else
355  
-        @options.url
356  
-      end
357  
-    end
358  
-
359  
-    def url_timestamp(url)
360  
-      return url unless updated_at
361  
-      delimiter_char = url.include?("?") ? "&" : "?"
362  
-      "#{url}#{delimiter_char}#{updated_at.to_s}"
363  
-    end
364  
-
365  
-    def escape_url(url)
366  
-      url.respond_to?(:escape) ? url.escape : URI.escape(url)
367  
-    end
368  
-
369 353
     def ensure_required_accessors! #:nodoc:
370 354
       %w(file_name).each do |field|
371 355
         unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
3  lib/paperclip/options.rb
@@ -34,6 +34,9 @@ def initialize(attachment, hash)
34 34
       @processors            = hash[:processors]
35 35
       @preserve_files        = hash[:preserve_files]
36 36
       @http_proxy            = hash[:http_proxy]
  37
+      @interpolator          = hash[:interpolator]
  38
+      @escape                = hash[:escape]
  39
+      @url_generator         = hash[:url_generator]
37 40
 
38 41
       #s3 options
39 42
       @s3_credentials        = hash[:s3_credentials]
64  lib/paperclip/url_generator.rb
... ...
@@ -0,0 +1,64 @@
  1
+require 'uri'
  2
+
  3
+module Paperclip
  4
+  class UrlGenerator
  5
+    def initialize(attachment, attachment_options)
  6
+      @attachment = attachment
  7
+      @attachment_options = attachment_options
  8
+    end
  9
+
  10
+    def for(style_name, options)
  11
+      escape_url_as_needed(
  12
+        timestamp_as_needed(
  13
+          @attachment_options.interpolator.interpolate(most_appropriate_url, @attachment, style_name),
  14
+          options
  15
+      ), options)
  16
+    end
  17
+
  18
+    private
  19
+
  20
+    # This method is all over the place.
  21
+    def default_url
  22
+      if @attachment_options.default_url.respond_to?(:call)
  23
+        @attachment_options.default_url.call(@attachment)
  24
+      elsif @attachment_options.default_url.is_a?(Symbol)
  25
+        @attachment.instance.send(@attachment_options.default_url)
  26
+      else
  27
+        @attachment_options.default_url
  28
+      end
  29
+    end
  30
+
  31
+    def most_appropriate_url
  32
+      if @attachment.original_filename.nil?
  33
+        default_url
  34
+      else
  35
+        @attachment_options.url
  36
+      end
  37
+    end
  38
+
  39
+    def timestamp_as_needed(url, options)
  40
+      if options[:timestamp] && timestamp_possible?
  41
+        delimiter_char = url.include?('?') ? '&' : '?'
  42
+        "#{url}#{delimiter_char}#{@attachment.updated_at.to_s}"
  43
+      else
  44
+        url
  45
+      end
  46
+    end
  47
+
  48
+    def timestamp_possible?
  49
+      @attachment.respond_to?(:updated_at) && @attachment.updated_at.present?
  50
+    end
  51
+
  52
+    def escape_url_as_needed(url, options)
  53
+      if options[:escape]
  54
+        escape_url(url)
  55
+      else
  56
+        url
  57
+      end
  58
+    end
  59
+
  60
+    def escape_url(url)
  61
+      url.respond_to?(:escape) ? url.escape : URI.escape(url)
  62
+    end
  63
+  end
  64
+end
229  test/attachment_test.rb
... ...
@@ -1,46 +1,92 @@
1 1
 # encoding: utf-8
2 2
 require './test/helper'
  3
+require 'paperclip/attachment'
3 4
 
4  
-class Dummy
5  
-  # This is a dummy class
6  
-end
  5
+class Dummy; end
7 6
 
8 7
 class AttachmentTest < Test::Unit::TestCase
9  
-  should "return the path based on the url by default" do
10  
-    @attachment = attachment :url => "/:class/:id/:basename"
11  
-    @model = @attachment.instance
12  
-    @model.id = 1234
13  
-    @model.avatar_file_name = "fake.jpg"
14  
-    assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
  8
+  should "handle a boolean second argument to #url" do
  9
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  10
+    attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder)
  11
+
  12
+    attachment.url(:style_name, true)
  13
+    assert mock_url_generator_builder.has_generated_url_with_options?(:timestamp => true, :escape => true)
  14
+
  15
+    attachment.url(:style_name, false)
  16
+    assert mock_url_generator_builder.has_generated_url_with_options?(:timestamp => false, :escape => true)
15 17
   end
16 18
 
17  
-  should "return the url by interpolating the default_url option when no file assigned" do
18  
-    @attachment = attachment :default_url => ":class/blegga.png"
19  
-    @model = @attachment.instance
20  
-    assert_nil @model.avatar_file_name
21  
-    assert_equal "fake_models/blegga.png", @attachment.url
  19
+  should "pass the style and options through to the URL generator on #url" do
  20
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  21
+    attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder)
  22
+
  23
+    attachment.url(:style_name, :options => :values)
  24
+    assert mock_url_generator_builder.has_generated_url_with_options?(:options => :values)
22 25
   end
23 26
 
24  
-  should "return the url by executing and interpolating the default_url Proc when no file assigned" do
25  
-    @attachment = attachment :default_url => lambda { |a| ":class/blegga.png" }
26  
-    @model = @attachment.instance
27  
-    assert_nil @model.avatar_file_name
28  
-    assert_equal "fake_models/blegga.png", @attachment.url
  27
+  should "pass default options through when #url is given one argument" do
  28
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  29
+    attachment = Paperclip::Attachment.new(:name,
  30
+                                           :instance,
  31
+                                           :url_generator => mock_url_generator_builder,
  32
+                                           :use_timestamp => true)
  33
+
  34
+    attachment.url(:style_name)
  35
+    assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
29 36
   end
30 37
 
31  
-  should "return the url by executing and interpolating the default_url Proc with attachment arg when no file assigned" do
32  
-    @attachment = attachment :default_url => lambda { |a| a.instance.some_method_to_determine_default_url }
33  
-    @model = @attachment.instance
34  
-    @model.stubs(:some_method_to_determine_default_url).returns(":class/blegga.png")
35  
-    assert_nil @model.avatar_file_name
36  
-    assert_equal "fake_models/blegga.png", @attachment.url
  38
+  should "pass default style and options through when #url is given no arguments" do
  39
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  40
+    attachment = Paperclip::Attachment.new(:name,
  41
+                                           :instance,
  42
+                                           :default_style => 'default style',
  43
+                                           :url_generator => mock_url_generator_builder,
  44
+                                           :use_timestamp => true)
  45
+
  46
+    attachment.url
  47
+    assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
  48
+    assert mock_url_generator_builder.has_generated_url_with_style_name?('default style')
37 49
   end
38 50
 
39  
-  should "return the url by executing and interpolating the default_url when assigned with symbol as method in attachment model" do
40  
-    @attachment = attachment :default_url => :some_method_to_determine_default_url
  51
+  should "pass the option :timestamp => true if :use_timestamp is true and :timestamp is not passed" do
  52
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  53
+    attachment = Paperclip::Attachment.new(:name,
  54
+                                           :instance,
  55
+                                           :url_generator => mock_url_generator_builder,
  56
+                                           :use_timestamp => true)
  57
+
  58
+    attachment.url(:style_name)
  59
+    assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
  60
+  end
  61
+
  62
+  should "pass the option :timestamp => false if :use_timestamp is false and :timestamp is not passed" do
  63
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  64
+    attachment = Paperclip::Attachment.new(:name,
  65
+                                           :instance,
  66
+                                           :url_generator => mock_url_generator_builder,
  67
+                                           :use_timestamp => false)
  68
+
  69
+    attachment.url(:style_name)
  70
+    assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => false)
  71
+  end
  72
+
  73
+  should "not change the :timestamp if :timestamp is passed" do
  74
+    mock_url_generator_builder = MockUrlGeneratorBuilder.new
  75
+    attachment = Paperclip::Attachment.new(:name,
  76
+                                           :instance,
  77
+                                           :url_generator => mock_url_generator_builder,
  78
+                                           :use_timestamp => false)
  79
+
  80
+    attachment.url(:style_name, :timestamp => true)
  81
+    assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
  82
+  end
  83
+
  84
+  should "return the path based on the url by default" do
  85
+    @attachment = attachment :url => "/:class/:id/:basename"
41 86
     @model = @attachment.instance
42  
-    @model.stubs(:some_method_to_determine_default_url).returns(":class/female_:style_blegga.png")
43  
-    assert_equal "fake_models/female_foostyle_blegga.png", @attachment.url(:foostyle)
  87
+    @model.id = 1234
  88
+    @model.avatar_file_name = "fake.jpg"
  89
+    assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
44 90
   end
45 91
 
46 92
   context "Attachment default_options" do
@@ -160,36 +206,6 @@ class AttachmentTest < Test::Unit::TestCase
160 206
     end
161 207
   end
162 208
 
163  
-  context "An attachment" do
164  
-    setup do
165  
-      @file = StringIO.new("...")
166  
-    end
167  
-
168  
-    context "using default time zone" do
169  
-      setup do
170  
-        rebuild_model :url => "X"
171  
-        @dummy = Dummy.new
172  
-        @dummy.avatar = @file
173  
-      end
174  
-
175  
-      should "generate a url with a timestamp when passing true" do
176  
-        assert_equal "X?#{@dummy.avatar_updated_at.to_i.to_s}", @dummy.avatar.url(:style, true)
177  
-      end
178  
-
179  
-      should "not generate a url with a timestamp when passing false" do
180  
-        assert_equal "X", @dummy.avatar.url(:style, false)
181  
-      end
182  
-
183  
-      should "generate a url with a timestamp when setting a timestamp option" do
184  
-        assert_equal "X?#{@dummy.avatar_updated_at.to_i.to_s}", @dummy.avatar.url(:style, :timestamp => true)
185  
-      end
186  
-
187  
-      should "not generate a url with a timestamp when setting a timestamp option to false" do
188  
-        assert_equal "X", @dummy.avatar.url(:style, :timestamp => false)
189  
-      end
190  
-    end
191  
-  end
192  
-
193 209
   context "An attachment with :hash interpolations" do
194 210
     setup do
195 211
       @file = StringIO.new("...")
@@ -406,27 +422,6 @@ def thumb; "-thumb"; end
406 422
     end
407 423
   end
408 424
 
409  
-  context "An attachment with :url that is a proc" do
410  
-    setup do
411  
-      rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
412  
-
413  
-      @file = File.new(File.join(File.dirname(__FILE__),
414  
-                                 "fixtures",
415  
-                                 "5k.png"), 'rb')
416  
-      @dummyA = Dummy.new(:other => 'a')
417  
-      @dummyA.avatar = @file
418  
-      @dummyB = Dummy.new(:other => 'b')
419  
-      @dummyB.avatar = @file
420  
-    end
421  
-
422  
-    teardown { @file.close }
423  
-
424  
-    should "return correct url" do
425  
-      assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
426  
-      assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
427  
-    end
428  
-  end
429  
-
430 425
   geometry_specs = [
431 426
     [ lambda{|z| "50x50#" }, :png ],
432 427
     lambda{|z| "50x50#" },
@@ -552,16 +547,16 @@ class Paperclip::Test < Paperclip::Processor; end
552 547
     rebuild_model :storage => :FileSystem
553 548
     @dummy = Dummy.new
554 549
     assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
555  
-    
  550
+
556 551
     rebuild_model :storage => :Filesystem
557 552
     @dummy = Dummy.new
558 553
     assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
559 554
   end
560  
-  
  555
+
561 556
   should "convert underscored storage name to camelcase" do
562 557
     rebuild_model :storage => :not_here
563 558
     @dummy = Dummy.new
564  
-    exception = assert_raises(Paperclip::StorageMethodNotFound) do |e|
  559
+    exception = assert_raises(Paperclip::StorageMethodNotFound) do
565 560
       @dummy.avatar
566 561
     end
567 562
     assert exception.message.include?("NotHere")
@@ -770,12 +765,6 @@ def do_after_all; end
770 765
       end
771 766
     end
772 767
 
773  
-    should "return its default_url when no file assigned" do
774  
-      assert @attachment.to_file.nil?
775  
-      assert_equal "/avatars/original/missing.png", @attachment.url
776  
-      assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
777  
-    end
778  
-
779 768
     should "return nil as path when no file assigned" do
780 769
       assert @attachment.to_file.nil?
781 770
       assert_equal nil, @attachment.path
@@ -802,29 +791,6 @@ def do_after_all; end
802 791
         @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
803 792
       end
804 793
 
805  
-      should "return a correct url even if the file does not exist" do
806  
-        assert_nil @attachment.to_file
807  
-        assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
808  
-      end
809  
-
810  
-      should "make sure the updated_at mtime is in the url if it is defined" do
811  
-        assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
812  
-      end
813  
-
814  
-      should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
815  
-        assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
816  
-      end
817  
-
818  
-      context "with the updated_at field removed" do
819  
-        setup do
820  
-          @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
821  
-        end
822  
-
823  
-        should "only return the url without the updated_at when sent #url" do
824  
-          assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
825  
-        end
826  
-      end
827  
-
828 794
       should "return the proper path when filename has a single .'s" do
829 795
         assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
830 796
       end
@@ -864,14 +830,6 @@ def do_after_all; end
864 830
               @attachment.save
865 831
             end
866 832
 
867  
-            should "return the real url" do
868  
-              file = @attachment.to_file
869  
-              assert file
870  
-              assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
871  
-              assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
872  
-              file.close
873  
-            end
874  
-
875 833
             should "commit the files to disk" do
876 834
               [:large, :medium, :small].each do |style|
877 835
                 io = @attachment.to_file(style)
@@ -941,22 +899,6 @@ def do_after_all; end
941 899
       end
942 900
     end
943 901
 
944  
-    context "with a file that has space in file name" do
945  
-      setup do
946  
-        @attachment.stubs(:instance_read).with(:file_name).returns("spaced file.png")
947  
-        @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
948  
-        @attachment.stubs(:instance_read).with(:file_size).returns(12345)
949  
-        dtnow = DateTime.now
950  
-        @now = Time.now
951  
-        Time.stubs(:now).returns(@now)
952  
-        @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
953  
-      end
954  
-
955  
-      should "returns an escaped version of the URL" do
956  
-        assert_match /\/spaced%20file\.png/, @attachment.url
957  
-      end
958  
-    end
959  
-
960 902
     context "when trying a nonexistant storage type" do
961 903
       setup do
962 904
         rebuild_model :storage => :not_here
@@ -1109,21 +1051,6 @@ def do_after_all; end
1109 1051
     end
1110 1052
   end
1111 1053
 
1112  
-  context "setting an interpolation class" do
1113  
-    should "produce the URL with the given interpolations" do
1114  
-      Interpolator = Class.new do
1115  
-        def self.interpolate(pattern, attachment, style_name)
1116  
-          "hello"
1117  
-        end
1118  
-      end
1119  
-
1120  
-      instance = Dummy.new
1121  
-      attachment = Paperclip::Attachment.new(:avatar, instance, :interpolator => Interpolator)
1122  
-
1123  
-      assert_equal "hello", attachment.url
1124  
-    end
1125  
-  end
1126  
-
1127 1054
   context "An attached file" do
1128 1055
     setup do
1129 1056
       rebuild_model
4  test/helper.rb
@@ -53,6 +53,10 @@ def setup
53 53
 ActiveRecord::Base.establish_connection(config['test'])
54 54
 Paperclip.options[:logger] = ActiveRecord::Base.logger
55 55
 
  56
+Dir[File.join(File.dirname(__FILE__), 'support','*')].each do |f|
  57
+  require f
  58
+end
  59
+
56 60
 def reset_class class_name
57 61
   ActiveRecord::Base.send(:include, Paperclip::Glue)
58 62
   Object.send(:remove_const, class_name) rescue nil
8  test/options_test.rb
... ...
@@ -1,7 +1,7 @@
1 1
 # encoding: utf-8
2 2
 require './test/helper'
3 3
 
4  
-class MockAttachment < Struct.new(:one, :two)
  4
+class DSO < Struct.new(:one, :two)
5 5
   def instance
6 6
     self
7 7
   end
@@ -17,7 +17,7 @@ class OptionsTest < Test::Unit::TestCase
17 17
 
18 18
   context "#styles with a plain hash" do
19 19
     setup do
20  
-      @attachment = MockAttachment.new(nil, nil)
  20
+      @attachment = DSO.new(nil, nil)
21 21
       @options = Paperclip::Options.new(@attachment,
22 22
                                         :styles => {
23 23
                                           :something => ["400x400", :png]
@@ -35,7 +35,7 @@ class OptionsTest < Test::Unit::TestCase
35 35
 
36 36
   context "#styles is a proc" do
37 37
     setup do
38  
-      @attachment = MockAttachment.new("123x456", :doc)
  38
+      @attachment = DSO.new("123x456", :doc)
39 39
       @options = Paperclip::Options.new(@attachment,
40 40
                                         :styles => lambda {|att|
41 41
                                           {:something => {:geometry => att.one, :format => att.two}}
@@ -59,7 +59,7 @@ class OptionsTest < Test::Unit::TestCase
59 59
 
60 60
   context "#processors" do
61 61
     setup do
62  
-      @attachment = MockAttachment.new(nil, nil)
  62
+      @attachment = DSO.new(nil, nil)
63 63
     end
64 64
     should "return processors if not a proc" do
65 65
       @options = Paperclip::Options.new(@attachment, :processors => [:one])
22  test/support/mock_attachment.rb
... ...
@@ -0,0 +1,22 @@
  1
+class MockAttachment
  2
+  attr_accessor :updated_at, :original_filename
  3
+
  4
+  def initialize(options = {})
  5
+    @model = options[:model]
  6
+    @responds_to_updated_at = options[:responds_to_updated_at]
  7
+    @updated_at = options[:updated_at]
  8
+    @original_filename = options[:original_filename]
  9
+  end
  10
+
  11
+  def instance
  12
+    @model
  13
+  end
  14
+
  15
+  def respond_to?(meth)
  16
+    if meth.to_s == "updated_at"
  17
+      @responds_to_updated_at || @updated_at
  18
+    else
  19
+      super
  20
+    end
  21
+  end
  22
+end
24  test/support/mock_interpolator.rb
... ...
@@ -0,0 +1,24 @@
  1
+class MockInterpolator
  2
+  def initialize(options = {})
  3
+    @options = options
  4
+  end
  5
+
  6
+  def interpolate(pattern, attachment, style_name)
  7
+    @interpolated_pattern = pattern
  8
+    @interpolated_attachment = attachment
  9
+    @interpolated_style_name = style_name
  10
+    @options[:result]
  11
+  end
  12
+
  13
+  def has_interpolated_pattern?(pattern)
  14
+    @interpolated_pattern == pattern
  15
+  end
  16
+
  17
+  def has_interpolated_style_name?(style_name)
  18
+    @interpolated_style_name == style_name
  19
+  end
  20
+
  21
+  def has_interpolated_attachment?(attachment)
  22
+    @interpolated_attachment == attachment
  23
+  end
  24
+end
2  test/support/mock_model.rb
... ...
@@ -0,0 +1,2 @@
  1
+class MockModel
  2
+end
27  test/support/mock_url_generator_builder.rb
... ...
@@ -0,0 +1,27 @@
  1
+class MockUrlGeneratorBuilder
  2
+  def initializer
  3
+  end
  4
+
  5
+  def new(attachment, attachment_options)
  6
+    @attachment = attachment
  7
+    @attachment_options = attachment_options
  8
+    self
  9
+  end
  10
+
  11
+  def for(style_name, options)
  12
+    @generated_url_with_style_name = style_name
  13
+    @generated_url_with_options = options
  14
+    "hello"
  15
+  end
  16
+
  17
+  def has_generated_url_with_options?(options)
  18
+    # options.is_a_subhash_of(@generated_url_with_options)
  19
+    options.inject(true) do |acc,(k,v)|
  20
+      acc && @generated_url_with_options[k] == v
  21
+    end
  22
+  end
  23
+
  24
+  def has_generated_url_with_style_name?(style_name)
  25
+    @generated_url_with_style_name == style_name
  26
+  end
  27
+end
194  test/url_generator_test.rb
... ...
@@ -0,0 +1,194 @@
  1
+# encoding: utf-8
  2
+require './test/helper'
  3
+require 'paperclip/url_generator'
  4
+require 'paperclip/options'
  5
+
  6
+class UrlGeneratorTest < Test::Unit::TestCase
  7
+  should "use the given interpolator" do
  8
+    expected = "the expected result"
  9
+    mock_attachment = MockAttachment.new
  10
+    mock_interpolator = MockInterpolator.new(:result => expected)
  11
+
  12
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment,
  13
+                                     Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator))
  14
+    result = url_generator.for(:style_name, {})
  15
+
  16
+    assert_equal expected, result
  17
+    assert mock_interpolator.has_interpolated_attachment?(mock_attachment)
  18
+    assert mock_interpolator.has_interpolated_style_name?(:style_name)
  19
+  end
  20
+
  21
+  should "use the default URL when no file is assigned" do
  22
+    mock_attachment = MockAttachment.new
  23
+    mock_interpolator = MockInterpolator.new
  24
+    default_url = "the default url"
  25
+    options = Paperclip::Options.new(mock_attachment,
  26
+                                     :interpolator => mock_interpolator,
  27
+                                     :default_url => default_url)
  28
+
  29
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  30
+    url_generator.for(:style_name, {})
  31
+
  32
+    assert mock_interpolator.has_interpolated_pattern?(default_url),
  33
+      "expected the interpolator to be passed #{default_url.inspect} but it wasn't"
  34
+  end
  35
+
  36
+  should "execute the default URL lambda when no file is assigned" do
  37
+    mock_attachment = MockAttachment.new
  38
+    mock_interpolator = MockInterpolator.new
  39
+    default_url = lambda {|attachment| "the #{attachment.class.name} default url" }
  40
+    options = Paperclip::Options.new(mock_attachment,
  41
+                                     :interpolator => mock_interpolator,
  42
+                                     :default_url => default_url)
  43
+
  44
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  45
+    url_generator.for(:style_name, {})
  46
+
  47
+    assert mock_interpolator.has_interpolated_pattern?("the MockAttachment default url"),
  48
+      %{expected the interpolator to be passed "the MockAttachment default url", but it wasn't}
  49
+  end
  50
+
  51
+  should "execute the method named by the symbol as the default URL when no file is assigned" do
  52
+    mock_model = MockModel.new
  53
+    mock_attachment = MockAttachment.new(:model => mock_model)
  54
+    mock_interpolator = MockInterpolator.new
  55
+    default_url = :to_s
  56
+    options = Paperclip::Options.new(mock_attachment,
  57
+                                     :interpolator => mock_interpolator,
  58
+                                     :default_url => default_url)
  59
+
  60
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  61
+    url_generator.for(:style_name, {})
  62
+
  63
+    assert mock_interpolator.has_interpolated_pattern?(mock_model.to_s),
  64
+      %{expected the interpolator to be passed #{mock_model.to_s}, but it wasn't}
  65
+  end
  66
+
  67
+  should "URL-escape spaces if asked to" do
  68
+    expected = "the expected result"
  69
+    mock_attachment = MockAttachment.new
  70
+    mock_interpolator = MockInterpolator.new(:result => expected)
  71
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  72
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  73
+
  74
+    result = url_generator.for(:style_name, {:escape => true})
  75
+
  76
+    assert_equal "the%20expected%20result", result
  77
+  end
  78
+
  79
+  should "escape the result of the interpolator using a method on the object, if asked to escape" do
  80
+    expected = Class.new do
  81
+      def escape
  82
+        "the escaped result"
  83
+      end
  84
+    end.new
  85
+    mock_attachment = MockAttachment.new
  86
+    mock_interpolator = MockInterpolator.new(:result => expected)
  87
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  88
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  89
+
  90
+    result = url_generator.for(:style_name, {:escape => true})
  91
+
  92
+    assert_equal "the escaped result", result
  93
+  end
  94
+
  95
+  should "leave spaces unescaped as asked to" do
  96
+    expected = "the expected result"
  97
+    mock_attachment = MockAttachment.new
  98
+    mock_interpolator = MockInterpolator.new(:result => expected)
  99
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  100
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  101
+
  102
+    result = url_generator.for(:style_name, {:escape => false})
  103
+
  104
+    assert_equal "the expected result", result
  105
+  end
  106
+
  107
+  should "default to leaving spaces unescaped" do
  108
+    expected = "the expected result"
  109
+    mock_attachment = MockAttachment.new
  110
+    mock_interpolator = MockInterpolator.new(:result => expected)
  111
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  112
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  113
+
  114
+    result = url_generator.for(:style_name, {})
  115
+
  116
+    assert_equal "the expected result", result
  117
+  end
  118
+
  119
+  should "produce URLs without the updated_at value when the object does not respond to updated_at" do
  120
+    expected = "the expected result"
  121
+    mock_interpolator = MockInterpolator.new(:result => expected)
  122
+    mock_attachment = MockAttachment.new(:responds_to_updated_at => false)
  123
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  124
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  125
+
  126
+    result = url_generator.for(:style_name, {:timestamp => true})
  127
+
  128
+    assert_equal expected, result
  129
+  end
  130
+
  131
+  should "produce URLs without the updated_at value when the updated_at value is nil" do
  132
+    expected = "the expected result"
  133
+    mock_interpolator = MockInterpolator.new(:result => expected)
  134
+    mock_attachment = MockAttachment.new(:responds_to_updated_at => true, :updated_at => nil)
  135
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  136
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  137
+
  138
+    result = url_generator.for(:style_name, {:timestamp => true})
  139
+
  140
+    assert_equal expected, result
  141
+  end
  142
+
  143
+  should "produce URLs with the updated_at when it exists" do
  144
+    expected = "the expected result"
  145
+    updated_at = 1231231234
  146
+    mock_interpolator = MockInterpolator.new(:result => expected)
  147
+    mock_attachment = MockAttachment.new(:updated_at => updated_at)
  148
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  149
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  150
+
  151
+    result = url_generator.for(:style_name, {:timestamp => true})
  152
+
  153
+    assert_equal "#{expected}?#{updated_at}", result
  154
+  end
  155
+
  156
+  should "produce URLs with the updated_at when it exists, separated with a & if a ? already exists" do
  157
+    expected = "the?expected result"
  158
+    updated_at = 1231231234
  159
+    mock_interpolator = MockInterpolator.new(:result => expected)
  160
+    mock_attachment = MockAttachment.new(:updated_at => updated_at)
  161
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  162
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  163
+
  164
+    result = url_generator.for(:style_name, {:timestamp => true})
  165
+
  166
+    assert_equal "#{expected}&#{updated_at}", result
  167
+  end
  168
+
  169
+  should "produce URLs without the updated_at when told to do as much" do
  170
+    expected = "the expected result"
  171
+    updated_at = 1231231234
  172
+    mock_interpolator = MockInterpolator.new(:result => expected)
  173
+    mock_attachment = MockAttachment.new(:updated_at => updated_at)
  174
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator)
  175
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  176
+
  177
+    result = url_generator.for(:style_name, {:timestamp => false})
  178
+
  179
+    assert_equal expected, result
  180
+  end
  181
+
  182
+  should "produce the correct URL when the instance has a file name" do
  183
+    expected = "the expected result"
  184
+    mock_attachment = MockAttachment.new(:original_filename => 'exists')
  185
+    mock_interpolator = MockInterpolator.new
  186
+    options = Paperclip::Options.new(mock_attachment, :interpolator => mock_interpolator, :url => expected)
  187
+
  188
+    url_generator = Paperclip::UrlGenerator.new(mock_attachment, options)
  189
+    url_generator.for(:style_name, {})
  190
+
  191
+    assert mock_interpolator.has_interpolated_pattern?(expected),
  192
+      "expected the interpolator to be passed #{expected.inspect} but it wasn't"
  193
+  end
  194
+end

0 notes on commit bc5c51d

Please sign in to comment.
Something went wrong with that request. Please try again.