We are a software consultancy based in Berlin, Germany. We deliver
high quality web apps in short timespans.

Upstream Agile GmbH

Bilder aus dem Netz mit file_column runterladen

July 29, 2007 by alex

Hier also ein weiteres Post zum Thema file_column Hacks.

Vor kurzem mussten wir die Anforderung umsetzen, dass Bilder, die im Netz liegen, wie eine vom Client hochgeladene Datei speichern zu können. Warum wir sowas machen wollen? In diesem Fall, um Daten von einer Anwendung über http nach autoki zu importieren. Dazu gehörten auch Bilder, die über eine URL erreichbar waren.

Dafür haben wir zuerst die Zuweisung der file_column Instanz in file_column.rb so angepasst, dass zwischen einer hochgeladenen Datei und einem String, der die URL enhält, unterschieden werden kann. Diese Zuweisung erfolgt in der generierten Methode für das file_coloumn-Attribut. Passenderweise heißt die Variable für den define_method-Aufruf attr.

Die Anpassung ist im Folgenden zu sehen: Wenn ein String statt ein File-Objekt übergeben wird, rufen wir zuerst unsere Methode auf, um die Datei herunterzuladen.

define_method "#{attr}=" do |file|
  if file.is_a?(String) && !file.blank?
    file = send "#{attr}_download_file", file
  end

  state = send(state_method).assign(file)

  instance_variable_set state_attr, state

  if state.options[:after_upload] and state.just_uploaded?
   state.options[:after_upload].each do |sym|
   self.send sym
  end
end

So weit so trivial. Nun mag man denken, dass das Problem so gut wie gelöst ist, denn eine URL lässt sich Dank uri-open wie ein File-Objekt behandeln. Ruby ist es nämlich herzlich egal, ob ein Objekt die richtige Klasse hat, solange es die gerade benötigten Schnittstellen bereitstellt. Dieses Konzept wird in Ruby duck typing genannt. open-uri stellt die von File-Objekten benutzten Methoden, open und read, für URLs breit.

Das Problem ist aber, dass das von Mongrel erzeugte Objekt eine Instanz von CgiFile ist, die noch einige andere Methoden bereitstellt, die von file_column aufgerufen werden. Hier machen wir uns eine weitere Eigenschaft von Ruby zu nutze. Wir definieren die noch benötigten Methoden für das File-Objekt, indem wir das Bild mit instance_eval zur Laufzeit zwischenspeichern . Unsere dynamisch erzeugte Download-Methode, die ein File-Objekt bereitstellt, das sich wie eine durch einen Upload erzeugt CgiFile verhält, ist nachfolgend zu sehen.

define_method "#{attr}_download_file" do |url|
  file = Tempfile.new('autoki_file_column_download')
  remote_file = URI.parse(url)
  file.write remote_file.read
  file.close
  file = File.new file.path, 'r'
  file.instance_eval "def original_filename
    \"#{url.split('/')[-1]}\"
  end"
  def file.content_type
    "image/#{original_filename.split('.')[-1]}"
  end
  file
end

Wichtig ist es, die erzeugte temporäre Datei zu schließen und erneut als Read-Only zu öffnen, denn die Temporärdatei ist write-only beim anlegen.