Continuing the unexpectedly long implementation of my
Rack::ThumbNailer
Rack middleware, I next describe that the middleware needs to pull images from the target application:it "should pull the image from the target application" doI make the
Rack::ThumbNailer.should_receive(:rack_image)
get "/foo.jpg", :thumbnail => 1
end
rack_image
another class method due to my inability to stub the target application. To make that pass:def call(env)When creating the thumbnail file, the
req = Rack::Request.new(env)
if !req.params['thumbnail'].blank?
filename = "foo"
ThumbNailer.rack_image(@app, env)
ThumbNailer.mk_thumbnail(filename)
thumbnail = ::File.new(filename).read
[200, { }, thumbnail]
else
@app.call(env)
end
end
mk_thumbnail
class method should use the result of ThumbNailer.rack_image
. To describe this in RSpec, I add a default output for ThumbNailer.rack_image
in the before(:each)
setup block:context "with thumbnail param" doI can then specify that
before(:each) do
#...
Rack::ThumbNailer.
stub!(:rack_image).
and_return("image data")
end
mk_thumbnail
should use the return value of rack_image
("image data"):it "should generate a thumbnail" doTo make that pass, I add a second argument in the
Rack::ThumbNailer.
should_receive(:mk_thumbnail).
with(anything(), "image data")
get "/foo.jpg", :thumbnail => 1
end
call
method:def call(env)Rather than stick with the
req = Rack::Request.new(env)
if !req.params['thumbnail'].blank?
filename = "foo"
image = ThumbNailer.rack_image(@app, env)
ThumbNailer.mk_thumbnail(filename, image)
thumbnail = ::File.new(filename).read
[200, { }, thumbnail]
else
@app.call(env)
end
end
anthing()
matcher in the mk_thumbnail
example, I can specify the filename for the cached copy of the image. It should be a concatenation of the cache directory, plus the path_info for the image in the target application.To support this, I add a default cache directory to the constructor for
Rack::ThumbNailer
:class ThumbNailerThe make-the-thumbnail-file example can then read:
DEFAULT_CACHE_DIR = '/var/cache/rack/thumbnails'
def initialize(app, options = { })
@app = app
@options = {:cache_dir => DEFAULT_CACHE_DIR}.merge(options)
end
...
it "should generate a thumbnail" doTo make that pass, I update the
Rack::ThumbNailer.
should_receive(:mk_thumbnail).
with("/var/cache/rack/thumbnails/foo.jpg", "image data")
get "/foo.jpg", :thumbnail => 1
end
call
method to:def call(env)Nice, that is a full-featured thumbnail generating bit of Rack middleware, save for the two class methods, which I largely borrow from my spike the other day (I deleted the code, but kept the notes):
req = Rack::Request.new(env)
if !req.params['thumbnail'].blank?
filename = @options[:cache_dir] + req.path_info
image = ThumbNailer.rack_image(@app, env)
ThumbNailer.mk_thumbnail(filename, image)
thumbnail = ::File.new(filename).read
[200, { }, thumbnail]
else
@app.call(env)
end
end
privateI would prefer to test these as well, but my inability to set expectations / mock these instance method makes such an endeavor more trouble that it is worth. So I keep them small, focused and rely on the axiom that one does not test private methods.
def self.rack_image(app, env)
http_code, headers, body = app.call(env)
img_data = ''
body.each do |data|
img_data << data
end
img_data
end
def self.mk_thumbnail(filename, image_data)
path = filename.sub(/\/[^\/]+$/, '')
FileUtils.mkdir_p(path)
ImageScience.with_image_from_memory(image_data) do |img|
img.resize(200, 150) do |small|
small.save cache_filename
end
end
end
Tomorrow, I will add cache hit/miss functionality and that should finish of my middleware.
google 2522
ReplyDeletegoogle 2523
google 2524
google 2525
google 2526
google 2527