About

This page contains a single entry from the blog posted on February 17, 2009 8:38 PM.

The previous post in this blog was @shop.products.published.for_category(@category).since(3.years.ago).bought_by(@me).

The next post in this blog is wvk goes hardware again.

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

Powered by
Movable Type 3.31

« @shop.products.published.for_category(@category).since(3.years.ago).bought_by(@me) | Main | wvk goes hardware again »

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!

TrackBack

TrackBack URL for this entry:
http://www.innoq.com/movabletype/mt-tb.cgi/3072

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)