Shadow Classes

Bei Fiona 7 gibt es zwei Betriebsarten, den ausschließlich an Scrivito orientierten standalone-Modus sowie den legacy-Modus, in dem Fiona 7 auch kompatibel zum Rails Connector ist.

Im Kompatibilitätsmodus (legacy) werden die Model-Klassen für Vorlagen von RailsConnector::BasicObj abgeleitet (meistens indirekt via Obj). Eine Vorlage muss genau so heißen, wie die Klasse, auf der sie basiert. Dadurch ergibt sich ein Problem, wenn man die APIs des Scrivito SDK benutzen möchte, weil dieses auch von dieser Namensgleichheit ausgeht.

Da es in Ruby keine mehrfache Vererbung gibt, lässt sich dieser Konflikt nicht mit den Mitteln der Programmiersprache lösen. Fiona 7 löst dieses Problem mit dem Konzept der Shadow Classes: Wenn man in einer Klasse, die von RailsConnector::BasicObj abgeleitet ist, den in_place-Block nutzt, dann wird eine zusätzliche, versteckte Klasse angelegt.

Fiona 7 verwendet diese versteckte Klasse als Ersatz für die fehlende globale Klasse für die Abwicklung aller internen Funktionen. Werden beispielsweise die in Homepage definierten Attribute abgefragt, dann wird zunächst die Klasse Homepage geladen und verwendet, wenn sie von Scrivito::BasicObj erbt. Sollte dies nicht der Fall sein, wird es noch einmal probiert, diesmal mit ShadowClasses::Homepage.

Ein Beispiel: die Vorlage Homepage hat das Attribut headline, das in-place bearbeitbar werden soll. In der Applikation gibt es bereits eine Definition für die Klasse Homepage:

class Homepage < Obj
end

Die Klasse Obj wiederum erbt ganz normal von RailsConnector::BasicObj

class Obj < RailsConnector::BasicObj
end

Nun wird in der Klasse Homepage das headline-Attribut im in_place-Block hinzugefügt:

class Homepage < Obj
  in_place do
    attribute :headline, :string
  end
end

Nach dieser Änderung kann man an geeigneter Stelle fiona7_tag @obj, :headline einsetzen, um das Attribut headline der Homepage dort in-place bearbeitbar zu machen.

Intern wird durch den in_place Block die Klasse ShadowClasses::Homepage angelegt, die von ShadowClasses::Obj erbt. Wenn man den in_place Block in der Obj-Klasse nutzt, dann findet sich die Definition in ShadowClasses::Obj wieder. Es werden also automatisch zwei Klassen definiert, die etwa wie folgt aussehen:

class ShadowClasses::Obj < Scrivito::BasicObj
end

class ShadowClasses::Homepage < ShadowClasses::Obj
  attribute :headline, :string
end

Es ist somit möglich, ein Attribut global, also für alle definierten Vorlagen, festzulegen, indem man den in_place-Block in der Obj-Klasse einträgt:

class Obj < RailsConnector::BasicObj
  in_place do
    attribute :show_in_navigation, :enum, values: ['yes', 'no']
  end
end

Im in_place Block können alle Funktionen genutzt werden, die sonst in den von Scrivito::BasicObj abgeleiteten Klassen verfügbar sind, z.B. valid_widget_classes_for zur Einschränkung der verfügbaren Widgets, oder description_for_editor zur Festlegung eines Beschreibungstextes von Seiten- und Widgettypen.

Die Klassenhierarchie der Applikation wird bei den Shadow Classes derzeit (1.5.x) nicht abgebildet. Das heißt, es greifen nur die Attribute und Methoden, die man entweder in Obj oder in der Vorlage selbst definiert hat.

Das folgende Beispiel funktioniert nicht wie beabsichtigt:

class ArticlePage < Obj
  in_place do
    attribute :headline, :string
  end
end

class NewsPage < ArticlePage
  in_place do
    attribute :summary, :html
  end
end

Die Vorlage NewsPage erhält in diesem Fall lediglich das Attribut summary (sowie gegebenenfalls auch die Attribute aus Obj) und nicht headline.

Sollte Vererbung bei Shadow Classes nicht anders (zum Beispiel mit Mixins) abgebildet werden können, dann kann – als die absolut letzte Lösung – das folgende Muster angewandt werden:

class ArticlePage < Obj
  def self.inherited(subclass)
    super
    subclass.class_eval do
      in_place do
        attribute :headline, :string
      end
    end
  end
end

class NewsPage < ArticlePage
  in_place do
    attribute :summary, :html
  end
end