About Coding

This page contains an archive of all entries posted to /blog/wvk in the Coding category. They are listed from oldest to newest.

Arbeiten und Arbeitsweise is the previous category.

Design is the next category.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.31

Main

Coding Archives

April 14, 2008

Javascript + ActiveRecord + ActiveResource

Dieses Wochenende stand wieder einmal ganz im Zeichen des freien Programmierens und ich muss sagen, dass ich mit den Ergebnissen doch ganz zufrieden bin.

Der Hintergrund: Ich hatte schon länger ein angefangenes Projekt mit unter anderem einem vollständig in JavaScript implementierten Verfügbarkeitsplaner für vermietete Gegenstände (siehe Bild).

snap17.png

Die Implementierung dieses Planers umfasste rund 800 Zeilen Code, im wesentlichen war das eine gigantische Planer-Klasse und zwei kleinere, jeweils für die Repräsentation eines vermieteten Gegenstandes sowie dessen (Nicht-)Verfügbarkeitszeiträumen. Das alles war nicht sonderlich aufgeräumt und insbesondere was die Verwendung von Event-Handlern betraf alles andere als einfach erweiterbar. Deswegen sollte das neu geschrieben werden.

Die Vorarbeit: Am Samstagmorgen hatte ich "eben schnell" das in PHP geschriebene serverseitige Backend neu in Rails implementiert und dadurch auch eine schöne REST-Schnittstelle erhalten. Anschließend ging es darum, die serverseitigen Ressourcen 1:1 auf Clientseite abzubilden. Rails bietet hervorragende Voraussetzungen dazu: im Controllercode erweitert man seine respond_to-Blöcke einfach um ein format.json { render :text => @obj.to_json }.Statement. Dann kriegt der Client beim Aufruf von beispielsweise /clients/2.json die Repräsentation eines Client"-Objektes als JSON (JavaScript Object Notation) geliefert, was sofort evaluiert werden kann und dann als Javascript-Object zur Verfügung steht. Aber: was ist mit Assoziationen? Was mit dynamischem Nachladen von Objekten? Objekt-Caching etc.?

Der Plan: Also ward die Idee geboren, eine Art Mischung aus ActiveRecord und ActiveResource in Javascript zu implementieren. das prototype.js-Framework bietet eine hervorragende Grundlage für solche Unterfangen, und ist mit seiner (nicht ganz zufälligerweise) an die RoR-Idiome angelehnte Namensgebung auch sehr angenehm zu verwenden. Das Ziel sollte nun sein, mit Hilfe von prototype.js ein kleines Framework zu schreiben, eine ähnlich minimalistische Klassendefinition erlauben würde wie ActiveRecord bzw, ActiveResource, inklusive der Generierung von Methoden zur Handhabung der Assoziationen.

Um eine lange Geschichte etwas zu kürzen, werde ich an dieser Stelle nicht konkret auf die Implementierung der ganzen Geschichte eingehen, sondern das in einem späteren Post tun. Hier erstmal ein Beispiel:

// ActiveResource.inherit ist die "magische" Methode, die jede Menge Code generiert...
var Trailer = ActiveResource.inherit({
 // ein paar Assoziationen:
  has_many: {
    'rental_periods': {},
    'occupations': {
      resource: 'occupation_dates',
    }
  },
  has_one: {
    'owner': {
      'class_name': 'TrailerOwner',
      'resource': 'people'
    }
  },
  // der Name der Ressource auf dem Server
  resource: 'trailers'
});

Folgende Methoden stehen nun zur Verfügung:

// holt sich das Trailer-Objekt (synchron) mit der ID 'id' und schreibt es nach {tr}
var tr  = Trailer.findOne(id);
// holt sich alle Trailer-Objekte (synchron) vom Server und schreibt sie nach {trs}
var trs = Trailer.findAll();
// holt Trailer {id} asynchron vom Server und übergibt das Resultat der Callback-Methode
Trailer.findOne(id, callback);
// dito für alle Trailer-Objekte
Trailer.findAlll(callback);
// liefert nach obiger Definition "/trailers"
var url = Trailer.resource_url();
// liefert nach obiger Definition "/trailers/{id}"
var url = Trailer.element_url(id);
// liefert nach obiger Definition "/trailers/{trailer_id}/rental_periods"
var url = tr.rental_periods_url();
// liefert nach obiger Definition "/trailers/{trailer_id}/rental_periods/{id}"
var url = tr.rental_period_url(id);
// liefert nach obiger Definition "/trailers/{trailer_id}/occupation_dates/{id}"
var url = tr.occupation_url(id);
...
// holt das OccupationDate-Objekt mit der ID {id}
var oc = tr.occupation(id);
// holt das Owner-Objekt
var ow = tr.owner();
...

Alle Objekte, die (synchron oder asynchron) vom Server geholt werden, werden in einem internen Cache zwischengespeichert. Momentan greift der Cache nur beim lesen von Einzelobjekten, sprich ein Aufruf von Trailer.rental_periods() würde immer einen HTTP-Request an den Server schicken. Aber daran wird noch gearbeitet :)

Auch ist das Speichern von Objekten noch nicht möglich, aber die Implementierung der Funktionalität dürfte nur unwesentlich mehr als 30 Minuten in Anspruch nehmen...

Die mittels ActiveResource.inherit() generierte "Klasse" (Javascript kennt ja keine wirklichen Klassen, sondern arbeiten Prototyp-basiert) kann ohne weiteres mit der von prototype.js bereitgestellten Methode Object.extend() um weitere Methoden erweitert werden. Auch kann die "Vererbungs"-Methode Class.create(Elternklasse, Kindsklasse) angewendet werden. (Genau genommen tut ActiveRecord.inherit() genau dies: Es erstellt eine neue Kindsklasse mit Class.create(ActiveRecord, MeineKlasse) und erweitert MeineKlasse mit den diversen assoziationsbezogenen Methoden). Es ist auch möglich, Kindsklassen von einer von ActiveResource erbenden Klasse zu erstellen und diese wiederum mit Assoziazionsdefinitionen zu versehen. Allerdings nicht mit Class.create(), sondern wiederum mit ActiveResource.inherit():

var Trailer = ActiveResource.inherit({
  resource: 'trailers'
  has_many: {...},
  has_one: {...},
});

var TruckTrailer = ActiveResource.inherit(Trailer, {
  resource: 'truck_trailers',
  has_many: {...}
});

Es ist theoretisch sogar möglich, beliebige "Klassen" mit der ActiveResource-Funktionalität "nachzurüsten":

var Person = Class.create({...});
var Person = ActiveResource.inherit(Person, {...definitionen der Ressourcen...});

Aber das ist eher eine akademische Überlegung, getestet hab ich das nicht ;-)

Den Quelltext findet ihr, meine werten Leser, hier: active_resource.js. In einem nächsten Post erzähle ich dann mehr zur internen Funktionsweise des Codes. Stay tuned!

May 5, 2008

Das "Geheimnis" von deklarativen Assoziationsdefinitionen

Da ich nunmehr in zwei verschiedenen Sprachen mich mit dem Thema der deklarativen Art der Definition von Assoziationen zwischen Klassen beschäftige, schien es mir sinnvoll, darüber an dieser Stelle mal etwas zu schreiben.

Erstmal: was meine ich mit diesem toll klingenden Konzept überhaupt? Ich meine damit das, was jeder Rails-Mensch kennt:

class Foo < ActiveRecord::Base
  has_many :bars
  belongs_to :baz
end

oder, mit dem in meinem letzten Post beschriebenen JS-Framewörkchen:

var Foo = ActiveResource.inherit({
  has_many: {
    bars: {}
  }
  has_one: {
    baz: {}
  }
});

Was man erwartet, ist dass ein Aufruf von foo.bars() die Liste aller zu foo gehörenden Bar-Instanzen liefert. Das zu bewerkstelligen ist in jeder Scriptsprache, die so etwas wie eine eval()-Methode oder gar ein ausgefeiltes Mataprogrammier-Konzept wie Ruby kennt, an sich relativ einfach. Das Rezept (am Beispiel von has_many):

  • man definiere eine Methode, welche die find-Methode der assoziierten Klasse mit einem Kontext definierenden Argument wie "für foo_id=self.id" auf. Ich schreibe das bewusst so allgemein, weil die konkrete Implementierung stark von der zu Grunde liegenden Persistenz abhängt -- es könnte (im Falle von ActiveResource) die Generierung einer URL mitsamt eines HTTP-Requests, oder (im Falle von ActiveRecord) die Generierung einer SQL-Query oder einer Suche-in-Textdatei-Methode oder oder... sein.
  • man definiere eine Klassenmethode namens has_many, die als Argument mindestens den Namen der Assoziation entgegennimmt (und daraus ggf. den Klassennamen der assoziierten Klasse herleitet)
  • man generiere eine Methode mit dem Namen des an has_many übergebenen Arguments (z.B. foos), die die erstgenannte Methode kapselt. In Javascript kann man z.B. mit sowas wie eval('Foo.prototype.' + association_name + '=function(){....}') arbeiten. In Ruby würde man die Methode eher gleich mittels define_method dynamisch generieren (define_method association_name do ... end).
  • man freue sich, dass bei einem Aufruf von foo.bars() nun das gleiche zurückgegeben wird, wie es (im Falle von RoR) bei Bar.find(:all, :conditions => "foo_id=#{foo.id}") der Fall wäre.

Aaaaber: das kann doch noch nicht der ganze Zauber gewesen sein? Und in der Tat, das ist erst ein kleiner, naiver Anfang. Wer Rails genauer kennt, weiß dass auch Konstrukte wie foo.bars.find_by_name_and_trallalla() ohne weiteres möglich sind. Dabei ist diese find_by...-Methode nicht etwa eine Methode der zurückgegebenen Collection (ich nenne das mal so, weil es je nach Sprache ein Array, ein Hash oder sonst eine iterierbare Objektsammlung sein kann), sondern wenn dann eine Klassenmethode von (in diesem Falle) Bar. In A Nutshell: Was auch immer beim Aufruf von foo.bars() zurückgegeben wird, es implementiert sowohl die Schnittstelle einer Collection (Array,...) als auch der Klasse, zu der die erwarteten Objektinstanzen gehören. (Genau genommen stimmt das nicht 100%ig, aber an dieser Stelle reicht diese Annahme).

Der geneigte Leser möge sich einstweilen den Rails-Code zu Gemüte führen und dabei feststellen, dass foo.bars() ein "Association Proxy"-Objekt zurückliefert. Dieses Objekt beinhaltet sämtliche Informationen darüber, wie die beiden Klassen zueinander in Beziehung stehen, wie die Instanzen der assoziierten Klasse geholt werden können und stellt Methoden bereit, um auf diese wie in einem Array zuzugreifen. Etwas genauer gesprochen erstellt die has_many-Methode ein Objekt, in dem die Informationen zu der Assoziation zwischen den Klassen gehalten wird und beim Aufruf der entsprechenden Objektmethode (foo.bars()) wird ein Objekt mit Informationen zu den Instanzen der referenzierten Klasse erstellt und zurückgegeben.

Mit dieser Information im Hinterkopf und etwas Geschicklichkeit ist es relativ einfach, miteinander in Beziehung stehende Resourcen jeder Art umzusetzen. Die Details wie man innerhalb des einen Objektes an das jeweils andere Objekt heran kommt, können hinter einer einheitlichen Schnittstelle (die des "Association Proxies") gekapselt werden. Somit ist es dann auch wenig umständlich, z.B. in RoR ActiveRecord- und ActiveResource-Objekte miteinander zu verknüpfen, über die gleiche deklarative Syntax wie man ActiveRecord-Objekte untereinander verknüpft. Ich arbeite gerade noch an einer halbwegs vollständigen, jedenfalls aber brauchbaren Implementierung dafür, denn ich sehe nun schon bald eine Handvoll Projekten, wo das zumindest Hilfreich wäre (Stichwort: Anwendung mit Benutzerverwaltung in einer anderen Anwendung, Kommunikation über HTTP/XML). Sobald das veröffentlichungswürdig ist, wird es selbstverständlich hier erscheinen.

October 4, 2008

Das leidige Thema 'Ruby und Strings'

Ruby kennt keine Strings.

Traurig, aber wahr. Ruby kennt nur irgendwie vermurkste Byte-Arrays, und deswegen musste ich leider heute bei einer formatierten Ausgabe wie dieser

User.find(:all).each do |user|
  if user.valid_system_user?
    puts "#{user.name.ljust(12, '.')} #{user.real_name[0..24].ljust(25, '.')} " +
         "#{user.uid.to_s.rjust(5)} #{user.gid.to_s.rjust(5)} " +
         "#{user.home_dir.ljust(23, '.')} #{File.exists?(user.home_dir).to_s.rjust(5)}"
  end
end

folgendes feststellen:

name         real name                   UID   GID home dir                exists?
wvk......... Xxxxxx xxx Xxxxxxx.......  1001  1001 /home/wvk..............  true
phl......... Xxxxxxx Xxxßxxxxxx......  1078   100 /home/phl.............. false
ua.......... Xxx Xxxxxxxxx............   510   100 /home/ua............... false
pg.......... Xxxxxxx Xxxxxx...........   505   505 /home/pg............... false
...

Na, wem fällt's auf? Genau, dem Kollegen mit dem "ß" im Namen fehlt ein '.'. Unschwer zu erraten, woran das liegt: rjust und ljust verwenden intern natürlich die length-Methode von Ruby, um die Länge des darzustellenden Strings zu ermitteln. Diese liefert aber nur bei den Standard-ASCII-Zeichen das richtige Ergebnis, bei anderen UTF8-Codierten Zeichen zwischen 1 und 3 zu viel:

~# irb
irb(main):001:0> "ß".length
=> 2
irb(main):002:0> "€".length
=> 3

Nanu. Um das Problem mit der falsch erkannten Stringlänge zu umgehen, könnte man folgendes verwenden:

$KCODE = 'u'
require 'jcode'

und dann, statt length, jlength verwenden. Dieser hässlichster aller Workarounds die mir diese Woche begegnet sind, funktioniert wie man es von jeder String-Funktion erwarten würde:

irb
irb(main):001:0> $KCODE = 'u'
=> "u"
irb(main):002:0> require 'jcode'
=> true
irb(main):003:0> 'äöü'.jlength
=> 3
irb(main):004:0> '€'.jlength
=> 1

Na bitte, wer sagt's denn. Aber was bringt das jetzt in Verbindung mit rjust? Nur so viel dass man beim selber neu Implementieren der Methoden eine zuverlässige Längenangebe hat (die Methoden jljust bzw. jrjust stellt jcode nämlich nicht zur Verfügung):

class String
  def jljust(len, char = ' ')
    if len > self.jlength
      self + char * (len - self.jlength)
    else
      self
    end
  end

  def jrjust(len, char = ' ')
    if len > self.jlength
      char * (len - self.jlength) + self
    else
      self
    end
  end
end

In verbindung mit dem neuen Code

User.find(:all).each do |user|
  if user.valid_system_user?
    puts "#{user.name.jljust(12, '.')} #{user.real_name[0..24].jljust(25, '.')} " +
         "#{user.uid.to_s.jrjust(5)} #{user.gid.to_s.jrjust(5)} " +
         "#{user.home_dir.jljust(23, '.')} #{File.exists?(user.home_dir).to_s.jrjust(5)}"
  end
end

sieht das Ergebnis dann auch so aus wie es aussehen sollte:

name         real name                   UID   GID home dir                exists?
wvk......... Xxxxxx xxx Xxxxxxx.......  1001  1001 /home/wvk..............  true
phl......... Xxxxxxx Xxxßxxxxxx.......  1078   100 /home/phl.............. false
ua.......... Xxx Xxxxxxxxx............   510   100 /home/ua............... false
pg.......... Xxxxxxx Xxxxxx...........   505   505 /home/pg............... false
...

Mehr zum Thema Unicode-Horror in Verbindung mit Rails gibt's unter http://wiki.rubyonrails.org/rails/pages/HowToUseUnicodeStrings zu lesen.

January 20, 2009

@shop.products.published.for_category(@category).since(3.years.ago).bought_by(@me)

Wenn es etwas gibt, was uns im aktuellen Projekt die Haut gerettet hat, dann ist es vermutlich named_scope. Gerade in dem Moment, wo Vladimir und ich eigentlich entschieden hatten, dass wie die Grenze des mit ActiveRecord elegant lösbaren überschritten hatten, sprang es mir ins Gesicht.

Hintergrund: Hat man ein komplexes Datenmodell mit vielen n-ären Abhängigkeiten und einer entsprechend kombinatorischen Anzahl an interessanten Abfragen, so stößt man mit find_by_x_and_y(:conditions => '...', :joins => '...') doch schnell an seine Grenze. Beispiel:

Category.all(
  :select     => 'categories.*, COUNT(DISTINCT categories_products.product_id) AS product_count,
                  AVG(product_reports.popularity) AS category_popularity',
  :joins      => 'INNER JOIN categories_products
                    ON categories_products.category_id=categories.id
                  INNER JOIN products
                    ON categories_products.product_id=products.id
                  INNER JOIN product_groups
                    ON products.group_id=product_group.id
                  INNER JOIN product_reports
                    ON product_reports.product_tribe_id=product_groups.id
                  INNER JOIN shop_products
                    ON shop_products.product_id=products.id',
  :group      => 'category_id',
  :conditions => 'shop_products.workflow_state=\'published\' AND categories.shop_id=42',
  :order      => 'category_popularity')

Wer findet heraus, was diese Query liefert?

Die Lösung für dieses Problem lautet, wie schon gesagt, Named Scopes. Diese erlauben es, Bedingungen bei Abfragen beliebig zu kombinieren. Ein Beispiel:

Category.for_shop(42).published.by_popularity

das liefert das gleiche Ergebnis wie die erste Query und ist gleichzeitig um längen flexibler. Was steckt dahinter? Dieses:

class Category
  named_scope :published,
    :conditions => "shop_products.workflow_state='published'"

  named_scope :by_popularity,
    :order  => 'AVG(product_reports.popularity)',
    :group  => 'category_id',
    :joins  => 'INNER JOIN categories_products
                  ON categories_products.category_id=categories.id
                INNER JOIN products
                  ON categories_products.product_id=products.id
                INNER JOIN product_groups
                  ON products.group_id=product_group.id
                INNER JOIN product_reports
                  ON product_reports.product_tribe_id=product_groups.id
                INNER JOIN shop_products
                  ON shop_products.product_id=products.id'

  named_scope :for_shop, lambda {|shop_id|
    {:conditions => ['categories.shop_id=?', shop_id]}
  }
end

Ein Named Scope ist also eine Art abgespeckter Beziehungsdefinition (has_many, has_one etc. definieren intern auch Scopes), die zur Generierung einer komplexeren SQL-Query herangezogen werden kann. Somit lässt sich die Komplexität der Queries wieder schön kapseln und außer an der Stelle ihrer Definition hat man nirgends in der Anwendung mit SQL zu tun, sondern mit gut lesbaren Konstrukten wie @shop.products.published.for_current_shop.since(3.years.ago).bought_by(@current_user). Außerdem spart man sich das händische Erstellen vieler spezialisierter find-Methoden!

Lang lebe ActiveRecord ;)

*) Anmerkung: Beim Projekt handelt es sich nicht, wie vllt angedeutet, um ein Shopsystem. Der Code wurde quasi anonymisiert.

February 17, 2009

Die Grenzen von Scopes in ActiveRecord

Nach anfänglicher Euphorie über die fantastischen Möglichkeiten von ActiveRecord's Scopes musste ich feststellen, dass diese leider kein Allheilmittel sind (ne, wirklich?).

Leider lassen sich in der Rails-Version 2.1.0 keine Scopes kombinieren, die alle eine :joins-Option definieren. Was sich als mehr als nur ein kleinwenig nervenaufreibend erweist, wenn man an diese Version gebunden ist.

[ein beispiel folgt vllt. später]

Glücklicherweise ändert(e) sich die Situation in Rails 2.2.2. Neben der Möglichkeit, ein default_scope für eine Modellklasse anzugeben (ähnlich wie es auch bei horherigen Versionen schon bei Assoziationen möglich war), werden auch verschachtelte Scopes mit mehreren :joins-Optionen korrekt verschmolzen. Deswegen habe ich heute ein paar Stunden damit verbracht, die ActiveRecord::Base#find-Implementierung aus 2.2.2 nach 2.1.0 rückzuportieren. Und siehe da: im Großen und Ganzen funktioniert es wie gewünscht. Dennoch kann es zu unerwarteten Ergebnissen kommen, wenn zwei verschiedene Join-Typen verwendet werden. So kann es vorkommen, dass die Reihenfolge der Scopes relevant ist, weil ihre Optionen von innen (ich rede von Verschachtelung...) nach außen hin ausgewertet wird. Also landen z.B. die Join-Anweisungen des innersten Scopes als erste in der SQL-Query und nicht als letztes. Das bedeutet bei LEFT OUTER JOINS vs. INNER JOINS Verwirrung, die hierher stammt:

class ActiveRecord::Base
  class << self

  def merge_joins(*joins)
    if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
      joins = joins.collect do |join|
        # ...      ^ da müsste ein .reverse stehen!
      end
    # ...
    end
  end

In meinem heutigen Backport verwende ich also an der markierten Stelle ein joins.reverse.collect ..., was den Schmerz etwas lindert.

Um sicher zu gehen, dass die eigenen Scopes auch wirklich so funktionieren, wie sie sollen, ist es vermutlich das beste, nicht voreilig möglichst viele Optionen in find-Aufrufen wegzukapseln. Besser warten, bis die Applikation halbwegs ausgereift ist. Dann erst macht es Sinn (und das Leben leichter) alle Stellen mit find-Aufrufen über zwei Zeilen zu identifizieren und die Schnittmenge der dort auftretenden Argumente Scopes zusammenzufassen.

Was wohl leider bei komplexeren Datenmodellen unmöglich bleiben wird, ist, dass jeder Scope für sich genommen genauso sinnvollen Output liefert wie kombiniert mit anderen. Da hilft nur eines: Dokumentation!

Die Grenzen von Scopes in ActiveRecord

Nach anfänglicher Euphorie über die fantastischen Möglichkeiten von ActiveRecord's Scopes musste ich feststellen, dass diese leider kein Allheilmittel sind (ne, wirklich?).

Leider lassen sich in der Rails-Version 2.1.0 keine Scopes kombinieren, die alle eine :joins-Option definieren. Was sich als mehr als nur ein kleinwenig nervenaufreibend erweist, wenn man an diese Version gebunden ist.

[ein beispiel folgt vllt. später]

Glücklicherweise ändert(e) sich die Situation in Rails 2.2.2. Neben der Möglichkeit, ein default_scope für eine Modellklasse anzugeben (ähnlich wie es auch bei horherigen Versionen schon bei Assoziationen möglich war), werden auch verschachtelte Scopes mit mehreren :joins-Optionen korrekt verschmolzen. Deswegen habe ich heute ein paar Stunden damit verbracht, die ActiveRecord::Base#find-Implementierung aus 2.2.2 nach 2.1.0 rückzuportieren. Und siehe da: im Großen und Ganzen funktioniert es wie gewünscht. Dennoch kann es zu unerwarteten Ergebnissen kommen, wenn zwei verschiedene Join-Typen verwendet werden. So kann es vorkommen, dass die Reihenfolge der Scopes relevant ist, weil ihre Optionen von innen (ich rede von Verschachtelung...) nach außen hin ausgewertet wird. Also landen z.B. die Join-Anweisungen des innersten Scopes als erste in der SQL-Query und nicht als letztes. Das bedeutet bei LEFT OUTER JOINS vs. INNER JOINS Verwirrung, die hierher stammt:

class ActiveRecord::Base
  class << self

  def merge_joins(*joins)
    if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
      joins = joins.collect do |join|
        # ...      ^ da müsste ein .reverse stehen!
      end
    # ...
    end
  end

In meinem heutigen Backport verwende ich also an der markierten Stelle ein joins.reverse.collect ..., was den Schmerz etwas lindert.

Um sicher zu gehen, dass die eigenen Scopes auch wirklich so funktionieren, wie sie sollen, ist es vermutlich das beste, nicht voreilig möglichst viele Optionen in find-Aufrufen wegzukapseln. Besser warten, bis die Applikation halbwegs ausgereift ist. Dann erst macht es Sinn (und das Leben leichter) alle Stellen mit find-Aufrufen über zwei Zeilen zu identifizieren und die Schnittmenge der dort auftretenden Argumente Scopes zusammenzufassen.

Was wohl leider bei komplexeren Datenmodellen unmöglich bleiben wird, ist, dass jeder Scope für sich genommen genauso sinnvollen Output liefert wie kombiniert mit anderen. Da hilft nur eines: Dokumentation!

May 7, 2009

Buttons oder Links?

vs. Delete

Wenn wir alle vorgeben, das Prinzip von REST verstanden zu haben, warum taucht dann immer wieder die Frage auf, warum (besonders zu Zeiten von Javascript, Ajax und Web 2.0) Links als Verweise auf Aktionen ("lösche Blogeintrag", "sende Formular ab"...) so schlimm seien.

Dabei ist es doch nur eine Hilfe für den Benutzer UND den Entwickler:

  • Links kann man (noch!) ohne Gefahr zu laufen, unerwünschte Nebeneffekte zu erzeugen, folgen. In REST-Sprech: Links verweisen auf Repräsentationen von Ressourcen.
  • Buttons verweisen auf Aktionen, die per definitionem (?) Nebeneffekte aufweisen. In REST-Sprech: Ihre Betätigung leitet Veränderungen an Ressourcen ein.

Will ich einem Benutzer also zeigen, was er sich alles ansehen kann, so biete ich ihm eine Liste von Links an. Will ich ihm hingegen die Möglichkeiten zur Veränderung der dargestellten Ressource(n) anbieten, so biete ich ihm eine Sammlung an Buttons.

Dieses Prinzip konsequent angewendet nimmt dem Entwickler m.E. sowohl im UI-Design als auch bei der Umsetzung einer RESTful Webanwendung (Denk-)Arbeit ab. Dem Benutzer der Anwendung macht es die Interpretation der UI ebenfalls erheblich leichter.

Was die Implementierung selber angeht, also ob Buttons nun Formulare abschicken oder ob die Aktionen via JavaScript eingeleitet werden, ist bei dieser Betrachtung erst einmal unerheblich. Ersteres ist der Accessibility von Webanweniungen natürlich bedeutend zuträglicher, zweiteres ist für "flashy" Web 2.0-Anwendungen wohl das Mittel der Wahl. Den Königsweg zu beschreiten und die Formulare per "unobtrusive Javascript" nach dem Laden der HTML-Seite zu verändern, um sie durch Ajax-getriebenes Verhalten zu ersetzen, ist aber auch kein bedeutender Mehraufwand (der sich aber auszahlt).

Leider wird o.g. Verhalten durch gewisse Standardmethoden in Ruby on Rails nicht unbedingt gefördert: link_to_remote setzt per default einen POST-Request ab, link_to_function existiert und button_to_remote existiert erst seit Rails 2.0 oder 2.1.

Sieht das jemand anders? Was sind dafür die Gründe? Diskussionsbeiträge sind willkommen!

Anmerkungen zu Michaels Kommentar (Danke dafür!): Ich gebe dier zu 100% recht, dass einen die richtige technische Lösung nicht davon entbindet, das Interface-Design derart zu gestalten, dass der Benutzer klar sieht wo er ohne Nachzudenken hinklicken darf und wo Vorsicht angebracht ist. Jedoch führt meines Erachtens die o.g. Regel, konsequent umgesetzt, automatisch zu besserem Interfacedesign: einfach nur deswegen, weil man eine einfache Regel umsetzt, an die sich der Benutzer gewöhnen kann. Siehe dazu auch die immer wieder geführte Diskussion, ob in einem Dialog der mit einem [OK] und einem [Abbrechen]-Button [OK] links oder rechts von [Abbrechen] angebracht werden sollte: es spielt keine Rolle, Hauptsache man wendet ein einziges Schema konsequent an.

Ich behaupte jedenfalls, dass es keinem Entwickler weh tut, konsequent Buttons für Aktionen und Links für "Ansichten" zu verwenden. Spätestens seit CSS 2.0 lassen sich beide ohnehin beliebig stylen. Und dann gibt's ja noch die Textbrowser... (nein, die sind beileibe nicht tot ;-))

June 14, 2009

Ruby + Qt4

Ich habe heute wieder einmal ein Wenig mit Qt4 gespielt, diesmal jedoch nicht mehr mit C++, sondern Ruby. Die Voraussetzungen dazu beschränken sich auf die zu installierende Bibliothek libqt4-ruby und natürlich die Qt4 API-Referenz. Fazit: Sehr cool, die Eleganz von Ruby gepaart mit der mächtigen Qt-Bibliothek macht richtig Lust auf schöne Multiplatform-Desktop-Applikationen.

Die von QtRuby (libqt4-ruby) zur Verfügung gestellten Qt-Bindings orientieren sich zu sagen wir mal 98% an den Original-APIs, mit der Ausnahme dass das "Q" in denn C++-Klasssennamen durch ein "Qt::"-Prefix ersetz wurde. Was ich allerdings als weniger hübsch empfinde, ist die konsequente Weiterverwendung von camelCase auch bei Methodennamen -- es steht keine Rubyesquere under_score-Variante zur Verfügung.

Anders sieht das aus bei den Qt-/KDE-Ruby-Bindings in der Korundum-Bibliothek. korundum4 ist eine m.E. etwas weiter fortgeschrittene Qt-Schnittstelle für Ruby, die zusätzlich die umfangreichen KDE-Erweiterungen der Qt-Bibliotheken zur Verfügung stellt. Außerdem wird Camelcase und Underscore-Schreibweise bei allen Methodennamen gleichermaßen unterstützt und "Setter" werden wie gewohnt über "value = " (und zusätzlich über "setValue()") angeboten.

Während QtRuby konsequent auf Ruby-Datentypen setzt, verwendet Korundum (noch) eigene Wrapper-Datentypen an Stellen, wo die C++ API einen Pointer erwartet. So würde ein Aufruf von Qt::FontDialog.getFont(my_boolean, Qt::Font.new("Helvetica", 10), self) in Korundum so aussehen: Qt::FontDialog.getFont(Qt::Boolean.new, Qt::Font.new("Helvetica", 10), self). Dies soll sich laut Dokumentation aber in Zukunft ändern. Bis auf diese Dinge lassen sich Programme, die für QtRuby geschrieben wurden, auch mit Korundum weitestgehend ohne Änderungen auch mit Korundum betreiben.

Ein paar hilfreiche HowTos:

Happy Hacking!

September 26, 2009

LaTeX und Regexen

Bei der Vorbereitung zu meinem nächsten Vortrag zum Thema Regular Expressions musste ich schmerzerfüllt feststellen, dass LaTeX beim Versuch, Regexen vernünftig zu formatieren, ein ganz schön widerwärtiges Biest sein kann. Um diese Aufgabe etwas zu erleichtern und außerdem den Aufbau der Regexen dem Publikum etwas strukturierter vorstellen zu können, bediene ich mich nun folgender LaTeX-Kommandos:

\definecolor{White}{rgb}{1,1,1}
\definecolor{Grey}{rgb}{0.5,0.5,0.5}
\definecolor{Brown}{rgb}{0.3,0.3,0}
\definecolor{Red}{rgb}{0.7,0,0}
\definecolor{Green}{rgb}{0,0.7,0}
\definecolor{Darkblue}{rgb}{0,0,0.5}
\definecolor{Violet}{rgb}{0.8,0,0.9}
\definecolor{Orange}{rgb}{0.8,0.4,0}

\newcommand{\RegEx}[1]{/#1/}
\newcommand{\LargeRegEx}[1]{{\large /#1/}}
\newcommand{\Or}{\textcolor{Brown}{\textbar}}
\newcommand{\Kleene}{\textcolor{Violet}{*}}
\newcommand{\OneOrMore}{\textcolor{Violet}{+}}
\newcommand{\ZeroOrOne}{\textcolor{Violet}{?}}
\newcommand{\Begin}{\textcolor{Brown}{\^{}}}
\newcommand{\End}{\textcolor{Brown}{\textdollar}}
\newcommand{\MLBegin}{\textcolor{Brown}{\Escape{A}}}
\newcommand{\MLEnd}{\textcolor{Brown}{\Escape{Z}}}
\newcommand{\Escape}[1]{\textbackslash{}#1}
\newcommand{\Wordchar}{\textcolor{Darkblue}{\Escape{w}}}
\newcommand{\Space}{\textcolor{Darkblue}{\Escape{s}}}
\newcommand{\Nonwordchar}{\textcolor{Darkblue}{\Escape{W}}}
\newcommand{\Digit}{\textcolor{Darkblue}{\Escape{d}}}
\newcommand{\Match}[1]{$($#1$)$}
\newcommand{\Matches}[1]{\textcolor{Darkblue}{\textbackslash{}#1}}
\newcommand{\Repeat}[1]{\textcolor{Violet}{${$#1$}$}}
\newcommand{\AnyOf}[1]{\textcolor{Green}{$[$#1$]$}}
\newcommand{\NoneOf}[1]{\textcolor{Red}{$[$\^{}#1$]$}}
\newcommand{\AnyNumberOf}[1]{#1\Kleene}
\newcommand{\OneOrMoreOf}[1]{#1\OneOrMore}
\newcommand{\ZeroOrOneOf}[1]{#1\ZeroOrOne}
\newcommand{\Modifier}[1]{\textcolor{Orange}{#1}}

Diese Anweisungen müssen for dem \begin{document}-Stanza stehen, damit man innerhalb des Dokuments dann z.B. folgende Regex inklusive Syntaxhighlighting darstellen kann:

/^[\w\d._%+-]+@[\w\d.-]+.\w{2,4}$/

wird in LaTeX zu:

\RegEx{\Begin\OneOrMoreOf{\AnyOf{\Wordchar\Digit.\_\%+-}}@\OneOrMoreOf{\AnyOf{\Wordchar\Digit.-}}\Escape{.}\Wordchar\Repeat{2,4}\End}

Gerendert wird das dann in etwa so:

regex1.png

Bei der Darstellung der Sonderzeichen in LaTeX hat mir dieses Dokument sehr geholfen: Everybody Loves Regular Expressions!

February 20, 2010

Javascript-Bibliothek für den Umgang mit Währungsangaben

Lange Zeit fehlte mir eine Javascript-Bibliothek, die mir den Clientseitigen Umgang mit Währungsangaben vereinfacht. Die typischen Use-Cases:

  • Parsen von Angaben wie "25.35 €", "100.5 $", "42,42" und Überführen in ein einheitliches internes Format
  • einfache formatierte Ausgabe solcher Werte, mit und ohne Währungssymbol
  • für die ganzen deutschsprachigen Komma-Fetischisten: Transparentes Handling von Dezimal-"," *graus*

Aus dieser Not ward' currency.js geboren, eine kleine auf prototype.js aufbauende (und also davon abhängende) Lib.

Anwendungsbeispiele:

Input-Parsing:

var value_1 = $pM('42.23');
var value_2 = $pM($F('my_input'));

Formatierte Ausgabe mit Währungssymbol:

alert(value_1.to_s());

Formatierte Ausgabe ohne Währungssymbol:

alert(value_1.to_val());

Für Berechnungen muss leider die interne cent-Repräsentation verwendet werden, da Javascript das Überladen arithmetischer Operatoren leider nicht erlaubt:

var sum = $M(value_1.cents + value_2.cents)

Für weitere Dokumentation siehe (currency.js auf Github)