About February 2009

This page contains all entries posted to /blog/wvk in February 2009. They are listed from oldest to newest.

January 2009 is the previous archive.

March 2009 is the next archive.

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

Powered by
Movable Type 3.31

« January 2009 | Main | March 2009 »

February 2009 Archives

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!