Bevor wir zur Darstellung kommen, wollen wir noch Texte und Seiten anlegen und verwalten können. Die Seiten können dann Texte oder Terminübersichten enthalten.

Texte administrieren für CMS Bearbeiten

Für Texte reicht uns zunächst ein einfacher Textcontainer ohne Struktur.

script/generate scaffold text content:text
rake db:migrate

Fertig. Oder auch nicht. Die Textübersichtsseite meldet: "undefined method `all' for Text:Module". Offensichtlich gibt es einen Namenskonflikt zwischen unserer Text-Klasse und dem vorhandenen Modul Text. Müssen wir nicht klären, wir können auf die Bezeichnung Textblock ausweichen.

Migration rückwärts

 rake db:migrate:down VERSION=20090714210547

Alle erzeugten Dateien löschen. Dabei auch die Tests nicht vergessen.

Und dann neu:

script/generate scaffold textblock content:text
rake db:migrate

Jetzt klappts. Wir legen ein paar Textblöcke mit Blindtexten wie "Lorem ipsum .." an. Dann vielleicht noch die Darstellung auf der Übersichtsseite kürzen ... Kennen wir ja.

# app/views/textblocks/index.html.erb

  <tr style="background-color: <%= cycle 'silver', 'white' %>;">
    <td><%=h truncate textblock.content, :length => 64 %></td>

CMS-Seiten administrieren Bearbeiten

Die Seiten sind etwas komplexer. Wir wollen eine Menuebaum bauen. Dazu müssen die Seiten eine parent_id bekommen wie vorhin die events.

  • parent_id:integer

Außerdem sollen die Seiten folgende Felder haben

  • menutext:string
  • title:string
  • keywords:text
  • description:text

also

script/generate scaffold page parent_id:integer menutext:string title:string keywords:text description:text

Wegen der Verschachtelung könnten wir den Code von den events übernehmen. Aber ist das dry? und ist er überhaupt gut?

Ersetzen durch plugin "acts_as_tree"

TODO: Gems, Plugins und acts_as_tree war Bestandteil von Rails. Ausgelagert in Bibliothek um Rails schlank zu halten.

Installation als Plugin:

script/plugin install git://github.com/rails/acts_as_tree.git

restart server

Änderungen im Model:

class Page < ActiveRecord::Base
  acts_as_tree
end

View (Übersichts-, New- und Edit-Seite)

# ../index.html.erb
    <td><%=h page.parent ? page.parent.menutext : ' - ' %></td>
# ../new.html.erb
  <p>
    <%= f.label "gehört_zu" %><br />
	<%= f.collection_select :parent_id, Page.all, :id, :menutext, :include_blank => ' - '  %>
  </p>
# ../edit.html.erb
  <p>
    <%= f.label "gehört_zu" %><br />
	<%= f.collection_select :parent_id, Page.all.reject { |p| p == @page }, :id, :menutext, :include_blank => ' - ' %>
  </p>

oder

# ../edit.html.erb
  <p>
    <%= f.label "gehört_zu" %><br />
	<%= f.collection_select :parent_id, Page.all, :id, :menutext, :include_blank => ' - ', :disabled => @page.id %>
  </p>

Warum Collection-Select? ..

disabled oder reject ..

Darstellung Bearbeiten

Jetzt wirds ein bisschen komplizierter. Anders ausgedrückt, es gibt wieder was zu lernen. Auf den Seiten sollen nämlich Events, Texte oder andere Elemente wie z.B. eine Bildergalerie dargestelt werden. Die Events sollen Monats- oder Jahresweise zusammengefasst dargestellt werden. Auch die Textblöcke wollen wir zu Gruppen zusammnenfassen und dann eine ganze Gruppe auf einer Seite und darstellen können. Für das Datenmodell dazu bieten sich zwei Möglichkeiten an:

Erste Variante: Individuelle ContentRenderer mit Content Bearbeiten

Zu jeder Klasse die wir auf einer Seite darstellen wollen schreiben wir einen Renderer. Also z.B.: TextBlockRenderer, EventsPerMonthRenderer, EventsPerYearRenderer, ImageGalerieRenderer ..

Alle diese Renderer sind von eine Klasse (Renderer) mit SingleTableInheritace abgeleitet. Und jede Seite kann mehrere Renderer enthalten. Also

  • Page has_many Renderers (Rendrer sind contentspezifisch)
  • XyRenderer has XyContentStore

Zweite Variante: Jeder Content wird mit RenderModul renderfähig Bearbeiten

Jede Klasse, die wir auf einer Seite darstellen wollen wird mit einem Modul "Renderable" erweitert. Damit können sich alle Content-Objekte selbst darstellen. Und jede Seite kann mehrere ContentElemente direkt enthalten. Also

  • Page has_many ContentStores (individual an independent Classes for Texts, Events, Images ...)
  • every ContentStores

Besondere Herausforderung: Die ContentStores sind so unterschiedlich, dass STI keinen Sinn macht. D.h. 'Rails unterstützt diese Form der polymorphen Beziehung nicht. Wir müssen das aber nicht selbst lösen. "has_many_polymorphs" ist ein Plugin, das das für uns erledigt.

Exkurs: Modelierung von Polymorphie unter Rails Bearbeiten

TODO: Theorie Kapitel

Besser?

Ausserdem benötigen wir eine Kalenderdarstelung

Kalenderdarstellung Bearbeiten

Auch für die Kalenderdarstelung benutzen wir ein Pugin. Es gibt viele Alternativen

Das Standardplugin ist

Eine Musteranwendung:

Wir eintscheiden uns aber nicht für ds Standartplugin, sondern für

Es ist vom Standardplugin abgeleitet, aktuell und - für uns wichtig - für Internationalisierung vorbereitet.


Buch über Plugins

Ok legen wir los ... Bearbeiten

Zunächst die Kalenderdarstellung. Wir installieren das Plugin und erzeugen die ContentSpeicher, die wir haben wollen. Zu jedem Contentspeicher erzeugen wir noch einen controller, damit wir die Darstelung testen können.

script/plugin install git://github.com/clemens/later_dude.git
# environment.rb
  ..
  config.gem "has_many_polymorphs"
script/generate model EventsPerMonth
script/generate controller EventsPerMonth show 
script/generate model EventsPerYear 
script/generate controller EventsPerYear show

SeitenModell Bearbeiten

kein Wildwuchs, wir benutzen has_many_polymorphs. Wenn wir has_many_polymorphs als gem installieren steht es uns in auch in anderen Projekten zur Verfügung.

sudo gem install has_many_polymorphs

Wir müssen das Seitenmodel erweitern,

class Page < ActiveRecord::Base
  acts_as_tree
  has_many_polymorphs :renderables, :from => [:textblocks, :events_per_months]
end

ein Join-Modell anlegen

script/generate model PagesRenderable
  class PagesRenderable < ActiveRecord::Base
    belongs_to :page
    belongs_to :renderable, :polymorphic => true
  end

und die Migration dazu:

  class CreatePagesRenderables < ActiveRecord::Migration
    def self.up
      create_table :pages_renderables do |t|
        t.references :renderable, :polymorphic => true
        t.references :page
      end
    end

    def self.down
      drop_table :pages_renderables
    end
  end

In den Rederable-Klassen (TextBlock, EventsPerMonth) müssen wir nicht ändern. NICHTS!!!

Prüfen wir ob alles klappt:

  test "page can have textblocks" do
    tb1 = Textblock.create :content => "asdf gh"
    tb2 = Textblock.create :content => "jklö qw"
    testpage = Page.create :menutext => "testpage"
    
    testpage.renderables << tb1 <<tb2
    
    assert_equal 2, testpage.renderables.size
    assert_equal "asdf gh", testpage.renderables.first.content
    assert_equal "asdf gh", testpage.textblocks.first.content  
  end
    
    assert_equal 2, testpage.renderables.size
    assert_equal "asdf gh", testpage.renderables.first.content
    assert_equal "asdf gh", testpage.textblocks.first.content  
  end


TODO / Anmerkungen: - Polymorphiedeklaration ist schlecht. Deshalb EventsPerMonth, .. Name ändern? EventsPerMonthBlock wäre klarer - Wenn neue renderbare Klassen dazukommen muss immer die Polymorphiedeklaration erweitert werde. Das ist ein Problem. Das bei der alternativen Modelierung nicht auftritt. Zumindest dann nicht, wenn uns das renderable Interface genügt.