Ruby on Rails: CMS
Aus Wikibooks
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.
Inhaltsverzeichnis |
[Bearbeiten] Texte administrieren für CMS
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 eine Namenskonflikt zwischen unserer Text-Klasse und dem vorhandenen Module 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 klapps. 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>
[Bearbeiten] CMS-Seiten administrieren
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 ..
[Bearbeiten] Darstellung
Jetzt wirds ein bischen 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:
[Bearbeiten] Erste Variante: Individulelle ContentRenderer mit Content
Zu jedem 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 mehre Renderer enthalten. Also
- Page has_many Renderers (Rendrer sind contentspezifisch)
- XyRenderer has XyContentStore
[Bearbeiten] Zweite Variante: Jeder Content wird mit RenderModul renderfähig
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 mehre 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.
[Bearbeiten] Exkurs: Modelierung von Polymorphie unter Rails
TODO: Theorie Kapitel
- http://railsforum.com/viewtopic.php?id=1422
- http://stackoverflow.com/questions/1054892/inheritance-and-polymorphism-conflicting-in-ruby-on-rails
- http://basic70tech.wordpress.com/2007/05/10/rails-belongs_to-polymorphic-and-inheritance/
- http://blog.evanweaver.com/articles/2006/07/28/has_many_polymorphs-gets-official/
- http://benjamin.francisoud.googlepages.com/googlecalendar
Besser?
- http://m.onkey.org/2007/8/14/excuse-me-wtf-is-polymorphs
- http://blog.evanweaver.com/files/doc/fauna/has_many_polymorphs/files/README.html
Ausserdem benötigen wir eine Kalenderdarstelung
[Bearbeiten] Kalenderdarstellung
Auch für die Kalenderdarstelung benutzen wir ein Pugin. Es gibt viele Alternativen
- http://github.com/clemens/later_dude/tree/master
- http://github.com/topfunky/calendar_helper/tree/master
- http://code.google.com/p/calendardateselect/
- http://github.com/dmix/weekly_builder/tree/master
- http://www.railslodge.com/tags/plugin/Calendar
- ..
Das Standardplugin ist
Eine Musteranwendung:
Wir eintscheiden uns aber nicht für ds Standartplugin, sondern für
- LaterDude (http://github.com/clemens/later_dude/tree/master)
Es ist vom Standardplugin abgeleitet, aktuell und - für uns wichtig - für Internatonalisierung vorbereitet.
Buch über Plugins
[Bearbeiten] Ok legen wir los ...
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" <source> script/generate model EventsPerMonth script/generate controller EventsPerMonth show script/generate model EventsPerYear script/generate controller EventsPerYear show ==== SeitenModell ==== 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, <source lang="rails"> 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
TODO / Anmerkungen: - Plolymorphiedeklaraion ist schlecht. Deshalb EventsPerMonth, .. Name ändern? EventsPerMonthBlock wäre klarer - Wenn neue Renderbare Klassen dazukommen muss immer die Ploymorphiedeklaration erweitert werde. Das ist ein Problem. Das bei der alternativen Modelierung nicht auftritt. Zumindest dann nicht, wenn uns das renderable Interface genügt.