March 11, 2007 by alex
FileColumn ist ein wirkliches zauberhaftes Plugin fuer Ruby on Rails. All unsere Bilder-Uploads haben wir bisher damit umgesetzt. FileColumn bietet Helper, um Datei-Uploads zu bauen und kann die hochgeladenen Bilder natuerlich auch wieder anzeigen. Das eigentlich schoene ist aber die Integration mit RMagick, einem Ruby-Binding fuer ImageMagick, wodurch sich Bilder on the fly skalieren sowie beschneiden lassen und die verschiedenen Versionen dann automatisch im Dateisystem landen. Alles, was man dazu braucht, ist eine Zeile Code im Model, z.B.
class User
file_column :photo, :magick => { :versions => {
:normal => "238x",
:tiny => "26x35!",
:small => {:crop => "1:1", :size => "73x73!", :name => "small"},
}}
end
Dieser Code erzeugt neben der Originalversion 3 Kopien: Eine 238 Pixel breite und entsprechend des Bildformats hohe, eine gestauchte, genau 23 x 35 Pixel grosse und eine genau 73 x 73 Pixel grosse, beschnittene Version.
So weit alles schoen. Das einzige Problem war, dass unsere Unit-Tests durch FileColumn ganz schoen langsam wurden. Bei jedem Testdurchlauf, der irgendwas mit Bilder zu tun hatte, wurden jedes mal mehrere skalierte Versionen erzeugt, was natuerlich Rechenzeit kostete. Die Loesung haben wir letzte Woche dann endlich mal zusammengehackt, und sie sieht so aus: Ein Modul, was von FileColumn installierte Callbacks ausser Gefecht setzt, wird in ein Mock des Models eingebunden, aber der Reihe nach. Hier das Modul, gelegen in test/file_column_mock.rb:
module FileColumnMock
def mock_file_column(attr)
define_method "#{attr}=" do |value|
if value.is_a?(File)
write_attribute attr, value.path
else
write_attribute attr, value
end
end
define_method "#{attr}_after_assign" do
end
define_method "#{attr}_after_save" do
end
define_method "#{attr}_state" do
DummyState.new self.send(:read_attribute, attr)
end
define_method "#{attr}_after_destroy" do
end
end
class DummyState
def initialize(file_name)
@file_name = file_name
end
def has_magick_errors?
false
end
def absolute_path(*args)
@file_name
end
def relative_path(subdir = nil)
@file_name
end
def temp_path
@file_name
end
def assign_temp(temp_path)
nil
end
end
end
In unserem Beispiel wollen wir die Klasse User von ihrer FIleColumn-Last befreien. Dazu legen wir in test/mocks/test eine Datei user.rb an und fuellen sie mit folgendem Inhalt:
require 'models/user'
require File.dirname(__FILE__) + '/../../file_column_mock'
class User
extend FileColumnMock
mock_file_column :photo
end
Der Effekt des ganzen ist, dass die Original User-Klasse aus app/models nochmals geoeffnet und mehrere Methoden, die normalerweise automatisch beim Setzen des photo-Attributs loslaufen, umd die skalierten Versionen zu erzeugen, durch Ueberschreiben ausser Gefecht gesetzt werden.
Das war’s. Und schon fliegen die Tests wieder mit voller Geschwindigkeit.