Kurs:Neueste Internet- und WWW-Technologien/Entwurfsmuster für die Webprogrammierung (am Beispiel Ruby on Rails)
Einleitung
BearbeitenRuby on Rails, kurz RoR, ist ein durch David Heinemeier Hannson entwickeltes quelloffenes Web Application Framework. Es basiert auf den Designgrundlagen:
- CoC - Convention over Configuration
- DRY - Don't repeat yourself
- CRUD - Create, Read, Update und Delete
- MVC - Model, View und Controller
Des Weiteren werden alle Objekte mittels Scaffolding angelegt, d.h. RoR stellt Baupläne für die Erstellung von u.a. Models, Controllern, Tests, usw. bereit, welche durch den Nutzer verwendet werden können und somit einfach und schnell neue Elemente im System implementiert werden können.
Zitate[1]
Bearbeiten- "Rails is the killer app for Ruby." Yukihiro Matsumoto, Creator of Ruby
- "After researching the market, Ruby on Rails stood out as the best choice. We have been very happy with that decision. We will continue building on Rails and consider it a key business advantage." Evan Williams, Creator of Blogger, ODEO, and Twitter
Architektur
BearbeitenRuby on Rails besteht, wie der Name schon sagt, aus zwei Grundkomponenten. Zum einen der Programmiersprache Ruby und zum anderen dem Webframework Rails. Ruby ist eine objektorientierte Programmiersprache, wobei alles ein Objekt ist. Es gibt keine primitiven Datentypen wie int oder boolean. Sie ist mächtiger als Perl und objektorientierter als Python. Rails ist ein Fullstack MVC Webframework und ist mit der Open Source MIT Lizenz veröffentlicht wurden.
Ruby on Rails entspricht der Model-View-Controller-Struktur. Anfragen, welche an eine Seite gestellt werden, werden vom Controller verarbeitet und entsprechend der gewünschten Aktion die entsprechenden Veränderungen am Model anstößt. Der Controller entscheidet darauf hin, welche View dem Nutzer als nächstes angezeigt wird. Das Model wiederrum ist nur ein Repräsentant eines Datenbankeintrags. Controller und View könnten zwar direkt mit der Datenbank kommunizieren, dies sollte aber vermieden werden. Warum das so ist und wie man solchen Code verbessert, wird in den folgenden Abschnitten genauer erläutert. Durch die MVC-Struktur gibt es eine klare Aufgabentrennung zwischen den einzelnen Schichten.
In der Abbildung ist eine Datenbank die Grundlage aller Informationen, welche im Frontend (View) dargestellt werden können. Die Datenbanken SQLite, DB2, Informix, MySQL, Oracle und einige andere werden derzeit unterstützt. Das Model stellt eine Abstraktion dieser Datenbank dar. Mittels ActiveRecords werden Attribute auf eine Datenbankspalte abgebildet. Ein Datensatz (Tupel) in einer solchen Tabelle stellt hierbei eine Objektinstanz dar. Über dem Model kommt die Controller-Schicht. Sie wird mit Hilfe der ActionController Klasse dargestellt. Ein Controller enthält die Ablaufslogik und bietet Schnittstellen für eine Kommunikation zwischen View und Model an. Für die Nutzeroberfläche (View) ist in Ruby on Rails die Klasse ActionView zuständig. Hierbei werden folgende Ausgabeformate bereits unterstützt:
- HTML
- XML (für z.B. XHTML)
- JavaScript - RJS-Templates
Zusätzlich werden folgende Templatemechanismen bereitgestellt:
- ERB (für XHTML und JS)
- Builder für XML
- Haml
- Sass
- CoffeeScript
Außerdem ist es möglich den HTML-Header zu manipulieren, um weitere Formate zum Clienten zu übertragen.
Installation
BearbeitenMöglichkeiten zum Aufsetzten einer Entwicklungsumgebung:
- Virtualrails [2]
- Schneller Einstieg, da nur die virtuelle Maschine in einem Player gestartet werden muss
- Aufgesetztes System mit Rails 1.8.X, dies ist veraltet und man findet kaum Hilfestellungen (neuste Version 1.9.3 [Stand: 06.07.2012])
- Railsinstaller [3]
- Unterstützt die Betriebssysteme MacOS & Windows
- Einfache Installation, System voll aufgesetzt
- Ubuntu VM
- ausführlicher Installationsguide (inkl. kleinem Tutorial) [4]
Gem
BearbeitenEin gem ist eine Ruby Anwendung oder Bibliothek. Sie hat einen Namen (z.B. rake) und eine Versionsnummer (z.B. 0.4.16). Gem können mittels des Befehls gem verwaltet werden, d.h. es können z.B. neue installiert und alte entfernt werden. RubyGem [5] ist ein bekanntes und häufig verwendetes Projekt, um Gems im eigenen Projekt verwalten zu können.
Beispielimplementierung
BearbeitenIm Weiteren wird folgendes Beispielszenario in den ersten Schritten implementiert und alle Designpattern beispielshaft an dem Szenario erläutert. Bei diesem Szenario handelt es sich um ein Mannschaftsverwaltungssystem. Hierbei gelten die folgenden einfachen Abhängigkeiten im zu entwickelnden System:
- jede Mannschaft soll dabei genau einen Trainer haben und jeder Trainer gehört nur zu genau einer Mannschaft
- jede Mannschaft hat n Spieler, aber ein Spieler gehört nur zu genau einer Mannschaft
Die ersten Schritte
Bearbeiten- Erstellung eines neuen Projekts mit Kommandozeilenbefehl: rails new <appname>
Es wird die Grundstruktur von jedem Railsprojekt angelegt und diverse Ordner und Dateien im Verzeichnis erstellt. Nachdem alle vollständig angelegt wurde, wird automatisch bundle install ausgeführt. Es installiert alle bereits vorgegebenen Gems. - Zur Ausführung des Programms benötigt Ruby on Rails Javascript, deshalb muss in der Datei Gemfile im Hauptverzeichnis um folgende Zeilen ergänzt werden:
gem ´execjs´
gem ´therubyracer´
Danach ist eine erneute Ausführung von bundle install notwendig, um die beiden Gems im System zu integrieren. - Anlegen der benötigten Modelle, durch Nutzung von Scaffolds. Innerhalb jedes Befehls werden für jede "Klasse" diverse Dateien angelegt, u.a. das Model, ein Kontroller, mehrere Dateien für die Ansicht sowie Tests und eine Migration für die Datenbank.
- rails generate scaffold Team name:string shortcut:string
- rails generate scaffold Player name:string number:integer
- rails generate scaffold Trainer name:string
- Die Modelle und das Schema für die Datenbank sind bereits vorhanden, aber die Datenbank wurde noch nicht auf das Schema geupdatet. Um das Updaten der Datenbank anzupassen muss folgender Kommandozeilenbefehl ausgeführt werden: rake db:migrate
- Nun ist alles soweit vorbereitet, dass ein erster Testlauf möglich ist. Zum Starten des Servers kann nun folgender Befehl ausgeführt werden: rails s
Wenn der Server erfolgreich gestartet ist, dann kann z.B. mittels der URL http://localhost:3000/teams auf die Übersicht der Teams zugegriffen werden.
Beziehungen herstellen
BearbeitenIn der einleitenden Erläuterung sind zwei Beziehungsarten identifizierbar. Zum einen eine 1:n und zum anderen eine 1:1 Beziehung. In RoR sind Beziehungsangaben in Modellen zu spezifizieren. Im Code muss dieses wie folgt umgesetzt werden:
class Team < ActiveRecord::Base
has_many :players
has_one :trainer
end
|
Pfad: app/models/Team.rb |
Die anderen beiden Modelle brauchen jeweils die Zeile:
belongs_to :team
|
Nun kann Ruby on Rails beispielsweise den Zugriff auswerten: player.team.name. Dadurch würde der Name der Mannschaft des Spielers ausgegeben werden. Ein Problem besteht aber immer noch, die Referenzen zwischen Objekten müssen in der Datenbank gespeichert werden, doch dafür sind noch keine Spalten definiert. Da sich RoR an Convention over Configuration orientiert, gibt es auch hierzu eine Konvention. Die Spalten gehört zu dem Model, welches belongs_to beinhaltet. In RoR werden Datenbankveränderungen über Migrations vorgenommen, d.h. um die zwei Spalten hinzuzufügen wird eine neue Migrationsdatei benötigt. Der Kommandozeilenbefehl rails generate migration addTeamID erzeugt eine neue Datei. Diese muss nach der Bearbeitung folgenden Inhalt aufweisen:
class add_team_id < ActiveRecord::Migration
def up
add_column :players, :team_id, :integer
add_column :trainers, :team_id, :integer
end
def down
remove_column :players, :team_id
remove_column :trainers, :team_id
end
end
|
Pfad: db/migrate/<rnd-number>_add_team_id.rb |
In dem Code ist sowohl eine up, als auch eine down Methode zu erkennen. Der Befehl rake db:migrate führt alle up Methoden von Migrationen aus, die im aktuellen System noch nicht ausgeführt wurden. Die down Methode dient dazu um Migrationen rückgängig zu machen.
Validatoren
BearbeitenFalls über das Interface neue Objekte hinzugefügt werden, dann ist es nicht zwangsläufig notwendig, dass Attribute Werte erhalten. Nun kann es aber passieren, dass Attribute gesetzt werden sollen. Dazu gibt es in Rails Validatoren, welche bei der Erzeugung eines neuen Objekts überprüfen, ob das neue Objekt valide ist. Beispielshaft sind im folgenden Validatoren für den Spieler angelegt wurden.
class Player < ActiveRecord::Base
validates_presence_of :name, :number
validates :number, :numericality => true
validates :team_id, :numericality => true
belongs_to :team
end
|
Veränderung der Ansicht
BearbeitenBeim Anlegen eines neuen Spielers tritt mit den aktuellen Validatoren ein Problem auf. Es geht nicht mehr! Der einfache Grund: Es wird eine TeamID als Input erwartet, doch das Interface stellt eine Möglichkeit dazu bereit. Eine Lösung wäre eine neue Textbox, worin der Nutzer die ID des Teams eintragen kann. Für ein Produktivsystem ist das keine akzeptable Lösung. Ein Auswahlmenu ist eine elegantere Lösung. Die Ansicht, welche gerendert wird, ist in der Datei app/views/players/_form.html.erb definiert. Durch die Zeile <%= f.select(:team_id, @teams.collect{ |t| [t.name, t.id] }) %> wird eine Auswahlbox gerendert, welche alle angelegten Teams im System enthält und bei der Erzeugung eines Spielers die ausgewählte TeamID als Inputparameter mitsendet.
<%= form_for(@player) do |f| %>
<% if @player.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@player.errors.count, "error") %> prohibited this player from being saved:</h2>
<ul>
<% @player.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :number %><br />
<%= f.number_field :number %>
</div>
<div class="field">
<%= f.label :team_id %><br />
<%= f.select(:team_id, @teams.collect{ |t| [t.name, t.id] }) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
|
Die Variable @teams, welche alle Teams im System enthalten soll, muss noch gesetzt werden. Dazu muss in den entsprechenden Kontroller die folgende Zeile in die new-Methode eingefügt werden @teams = Team.all. Auszugsweise sollte der Kontroller dann folgendermaßen aussehen:
class PlayersController < ApplicationController
...
# GET /players/new
# GET /players/new.json
def new
@teams = Team.all
@player = Player.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: @player }
end
end
...
end
|
Nun können Spieler angelegt werden, aber die Informationen werden in der Spielerübersicht und der Detailansicht nicht angezeigt, dazu müssen zwei Dateien erweitert werden.
<h1>Listing players</h1>
<table>
<tr>
<th>Name</th>
<th>Number</th>
<th>Team</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @players.each do |player| %>
<tr>
<td><%= player.name %></td>
<td><%= player.number %></td>
<td><%= player.team.shortcut %></td>
<td><%= link_to 'Show', player %></td>
<td><%= link_to 'Edit', edit_player_path(player) %></td>
<td><%= link_to 'Destroy', player, confirm: 'Are you sure?', method: :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Player', new_player_path %>
|
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @player.name %>
</p>
<p>
<b>Number:</b>
<%= @player.number %>
</p>
<p>
<b>Team:</b>
<%= @player.team.name %>
</p>
<%= link_to 'Edit', edit_player_path(@player) %> |
<%= link_to 'Back', players_path %>
|
Pfad: app/views/teams/index.html.erb | Pfad: app/views/teams/show.html.erb |
Neue Spieler können nun angelegt und die Teams über eine Selectbox ausgewählt werden. Die Daten zu einem Spieler werden sowohl in der Übersicht, in Form der Abkürzung des Teams, als auch in der Detailansicht, in Form des ganzen Namens, angezeigt. Das Letzte was in diesem kleinen Tutorial behandelt wird, ist eine Ansicht innerhalb des Teams, sodass auf der Detailansichtsseite alle Mitglieder einer Mannschaft angezeigt werden. Dazu wird folgende Anpassung vorgenommen:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @team.name %>
</p>
<p>
<b>City:</b>
<%= @team.city %>
</p>
<p>
<b>Players:</b>
<% @team.players.all.each do |p| %>
<%=link_to p.name, p %> </br>
<% end %>
</p>
<%= link_to 'Edit', edit_team_path(@team) %> |
<%= link_to 'Back', teams_path %>
|
Pfad : app/views/teams/show.html.erb |
Zu erkennen ist, dass nicht nur alle Spieler aufgelistet werden, sondern auch Links zu den Spielern gerendert werden.
Designpattern
BearbeitenEntwurfsmuster (engl. design patterns) sind Lösungen für bekannte und häufig auftretende Entwurfsprobleme. Softwareentwickler können die Pattern auf ihre Probleme anwenden und die Codequalität damit verbessern.
Probleme bei Software ohne Pattern | Ziel der Nutzung von Pattern |
---|---|
Instabil Komplex Undurchsichtig Wartungsressistent Codewiederholungen |
Verständlich Flexibel Effektiv Wartbar Testbar |
RESTful
BearbeitenREST (Representational State Transfer) ist eine Programmierempfehlung für Webanwendungen. Das Prinzip ist nicht explizit definiert, folglich divergieren die Vorstellungen über das Verfahren auseinander. Die zugrundeliegende Idee ist, dass es genau eine URL für genau eine serverseitige Aktion gibt. REST abstrahiert und reduziert die zwischen Systemen ausgeführten Aktionen auf eine standardisierte Menge. Diese wären:
- GET (Resourcenanforderung)
- POST (Erzeugung einer neuen Resource)
- PUT (Resource wird angelegt oder verändert)
- PATCH (Teil der Resource wird verändert / Seiteneffekte erlaubt)
- DELETE (Resource wird gelöscht)
- HEAD (Metainformationen zu einer Resource)
- OPTIONS (Überprüfung zur Verfügung stehender Methoden)
Die Architektur eines Controllers ist RESTful, eine Teilmenge der Standardmenge umsetzt. In der folgenden Implementierung ist diese Architektur nicht umgesetzt. Des Weiteren ist das Prinzip von CoC (Convention over Configuration) nicht eingehalten, da Namespaces nicht genutzt werden, die Aufrufe vereinfachen würden.
class GameEventController < ApplicationController
def add_goal_to_stats def exchange_player def start_game
end end end
def add_comment def add_injury def end_game
end end end
def destroy_comment def edit_comment def add_goal_to_player
end end end
end
|
Zur (Auf-)Lösung des Problems/Controllers sollten mehrere Controller erstellt werden, sodass die Methoden in die entsprechenden Controller ausgelagert werden können und die spezialisierten Controller dem REST-Prinzip entsprechen. Vorschlagsweise könnten die folgenden Controller angelegt werden.
class GameController < ApplicationController
class PlayerController < ApplicationController
class CommentController < ApplicationController
class GameStatsController < ApplicationController
class TeamController < ApplicationController
|
Model
BearbeitenIm folgenden werden drei Refactorings für das Model eines Objekts vorgestellt. Diese sind nur ein Teil von Verbesserungsmöglichkeiten.
Law of Demeter
BearbeitenDas Law of Demeter besagt, dass die Kommunikation zwischen Objekten in einer objektorientierten Sprache auf ein "gesundes" Minimum reduziert werden soll. Laut LoD soll eine Methode eines Objekts nur folgende andere Methoden verwenden dürfen:
- Methoden der eigenen Klasse
- Methoden der Parameter
- Methoden assoziierter Klassen
- Methoden von selbsterzeugten Objekten
Es gibt immer Ausnahmen zu Regeln für das Law of Demeter wäre eine Ausnahme sogenannte Datenklassen, welche u.a. zu Konfigurationszwecken genutzt werden. Bei diesen Klassen kann eine hierarchische Struktur umgesetzt werden, sodass es zu folgenden Zugriffen kommen kann:
@length = config.table.top.length
|
Durch das LoD wird die Kopplung zwischen Objekten verringert und die Wartbarkeit des kompletten Systems erhöht. Im folgenden Beispiel wird in der View gegen das Gesetz verstoßen, indem in der View auf die Position eines Spielers innerhalb eines Teams zugegriffen wird, wobei die Position eines Spielers im Team gespeichert ist und nicht im Spieler selbst.
class Person < ActiveRecord::Base
belongs_to :team
end
|
<%= @person.team.position %>
<%= @person.team.name %>
|
Zur Vermeidung des Verstoßes gegen das Law of Demeter gibt es in Ruby on Rails folgende Lösungsmöglichkeit:
class Person < ActiveRecord::Base
belongs_to :team
delegate :name, :position,
:to => :team,
:prefix => true
end
|
<%= @person.team_position %>
<%= @person.team_name %>
|
Durch den delegate Befehl können Variablen des Teams so behandelt werden, als ob sie innerhalb der Person gespeichert sind. RoR übernimmt die Verknüpfung der Objekte über die ID und leitete Anfragen, egal ob lesend oder schreibend, an das richtige Objekt weiter, in dem Fall das Team.
Finder-Methoden
BearbeitenProblem: Methoden, welche zum Auffinden von Objekten genutzt werden, gehören in das Model des Objekts, welches gefiltert wird.
class Team < ActiveRecord::Base
has_many :players
def find_players_without_card
self.players.find(:all, :conditions => {:cards => 0})
end
end
class Player < ActiveRecord::Base
belongs_to :team
end
|
class Controller < ApplicationController
def index
@players = @team.find_players_without_card
end
end
|
Im Beispielcode gibt es eine Methode der Klasse Team, welche alle Spieler eines Teams heraussucht, die noch keine Karte erhalten haben (find_players_without_card). Im Controller des Teams wird diese Methode aufgerufen, wenn in dem entsprechenden View diese mit Karten belasteten Spieler herausgefiltert werden sollen. Wie bereits in der Problemstellung angesprochen, gehören Filtermethoden jedoch in das zu filternde Objekt. Da hier Spieler gefiltert werden sollen, gehört die Methode also eigentlich in die Player-Klasse und nicht in die Team-Klasse. Im Folgenden wird eine elegantere Möglichkeit aufgezeigt, dieses Problem in Ruby zu lösen:
class Team < ActiveRecord::Base
has_many :players
end
class Player < ActiveRecord::Base
belongs_to :team
named_scope :without_card, :conditions => {:cards => 0}
end
|
class Controller < ApplicationController
def index
@players = @team.players.without_card
end
end
|
Durch den named_scope im Playermodel ist es möglich im Controller des Teams eine "Methode" auf allen Spielern aufzurufen und so nur die Spieler zu erhalten, welche der Bedingung genügen, dass sie keine Karte besitzen.
Code-Duplikation
BearbeitenJeder Softwareentwickler möchte es möglichst vermeiden, Code mehrmals zu schreiben, da Veränderungen an mehreren Stellen vorgenommen werden müssen und der Wartungsaufwand enorm steigt. Eine nicht ganz offensichtliche Code-Duplikation ist im folgenden Beispiel dargestellt.
class Player < ActiveRecord::Base
validate_inclusion_of :status, :in => ['ready', 'injured', 'lazy']
def self.all_ready def ready?
find(:all, :conditions => {:status => 'ready'}) self.status == 'ready'
end end
def self.all_injured def injured?
find(:all, :conditions => {:status => 'injured'}) self.status == 'injured'
end end
def self.all_lazy def lazy?
find(:all, :conditions => {:status => 'lazy'}) self.status == 'lazy'
end end
end
|
Betrachtet man die Methoden, stellt man fest, dass der Code in den Methoden ähnlich ist. So gibt es finder- und check-Methoden für den Status "bereit", "verletzt" und "faul" eines Spielers. Möchte der Verwender des Systems nun einen weiteren Status für einen Spieler haben, dann muss sowohl eine Methode zum Auffinden aller Spieler mit diesem Status geschrieben werden, als auch eine Methode, welche überprüft, ob ein Spieler den Status hat. Im Folgenden wird durch Meta-Programmierung die Erstellung dieser Methoden automatisiert und es ist nicht mehr notwendig neue Methoden zu definieren, wenn ein neuer Status hinzugefügt wird.
class Player < ActiveRecord::Base
STATUS = ['ready', 'injured', 'lazy']
validate_inclusion_of :status, :in => STATUS
class << self STATUS.each do |s|
STATUS.each do |s| define_method "#{s}?" do
define_method "all_#{s}" do self.status == s
find(:all, :conditions => { :status => s}) end
end end
end
end
end
|
Durch diese Form der Meta-Programmierung ist es nun ohne weiteres möglich mehr Status einzutragen. Die entsprechenden Methoden sind automatisch im System verfügbar. Code-Duplikationen wurden weiterhin aus dem Model entfernt.
Controller
BearbeitenIm Folgenden werden zwei Möglichkeiten vorgestellt, wie Controller schlank gehalten werden (Skinny - Controller). Das ist ein "good-practise" in Ruby on Rails.
Don't repeat yourself
BearbeitenIn dem nachfolgenden Controller-Code ist das Problem an Codewiederholungen in einem Controller beispielhaft dargestellt.
class PlayerController < ApplicationController
def show
@player = current_user.find_favorite
end
def edit
@player = current_user.find_favorite
end
def update
@player = current_user.find_favorite
@player.update_like
end
def destroy
@player = current_user.find_favorite
@player.destroy
end
end
|
Auffällig ist, dass innerhalb der definierten Methoden immer die gleiche Zeile Code steht, welche den Spieler des aktuellen Nutzers heraussucht und diesen in der Variable @player ablegt. Diese Zeile Code kann in Ruby on Rails mittels des Befehls before_filter vor jede Methode geschaltet werden. Hierbei kann definiert werden, vor welchen Methoden der Filter ausgeführt wird.
class PlayerController < ApplicationController
before_filter :find_post, :only => [:show, :edit, :update, :destroy]
def destroy def update
@player.destroy @player.update_like
end end
def find_post
@player = current_user.find_favorite
end
end
|
Durch dieses Refactoring fallen weiterhin zwei Methoden aus dem definierten Controller. Die Methoden show und edit sind leer und sind somit ist es nicht mehr notwendig sie zu definieren. Der Controller ist kleiner geworden und man entspricht dem Skinny Controller-Prinzip.
Inherited resources
BearbeitenIm folgenden Beispiel gäbe es zwei Möglichkeiten, Verbesserungen vorzunehmen. Die Anwendung der before_filter-Methode ist denkbar. Aber schaut man sich den Code etwas genauer an, dann stellt man noch was anderes fest.
class TeamController < ApplicationController
def index
@team = Team.all
end
def new
@team = Team.new
end
def show
@team = Team.find(params[:id])
end
def create
@team.create(params[:id])
end
def edit
@team = Team.find(params[:id])
end
def update
@team = Team.find(params[:id])
@team.update_attributes(params[:team])
end
def destroy
@team = Team.find(params[:id])
@team.destroy
end
end
|
Bei näherer Betrachtung fällt auf, dass die Methoden die zu erwartenden Funktionen implementieren und zwar ohne Extras. In der ersten Zeile ist zu erkennen, dass der TeamController ein ApplicationController ist. Da die Methoden der Standartimplementierung entsprechen, ist es nicht notwendig sie zu implementieren, daraus folgt der folgende abgespeckte Controller.
class TeamController < ApplicationController
end
|
Nun kann es sein, dass nach einer erfolgreichen Erstellung eines Objekts die Umleitung nicht wie vorgesehen von statten gehen soll. Hierzu nutzt man die vererbte Methode und verändert nur die Umleitung, so wird Code-Duplikation vermieden. Ein Beispiel für eine veränderte Umleitung nach der Erstellung eines Objekts ist in folgender Methode dargestellt. Hierbei wird nach der Erstellung eines Team-Objekts im Falle des Erfolgs auf die Detailansicht des Erstellten Teams und im Fehlerfall zur Hauptseite, der Übersicht über alle Teams, weitergeleitet.
def create
create! do |success, failure|
success.html { redirect_to post_url(@team) }
failure.html { redirect_to root_url }
end
|
View
BearbeitenIm folgenden Abschnitt werden Verbesserungsmöglichkeiten für die View dargestellt. Als Grundregel für alle Ansichten gilt immer NO LOGIC IN VIEW. Folgend sind zwei Arten dargestellt Logik aus der View in andere Teile des Codes zu verschieben.
Helper
BearbeitenFür jede View gibt es in Ruby on Rails ein Helperfile. Methoden, welche innerhalb dieser Datei definiert werden, können aus der View heraus aufgerufen werden. Beispiel für Logik in der View ist im Folgenden dargestellt, hierbei wird eine Anzeige zur Statusauswahl für einen Spieler gerendert.
<%= select_tag :state, options_for_select ( [[(:lazy), "draft"],
[(:injured), "injured"]],
params[:default_state] ) %>
|
In der Selectbox gibt es nun drei Status die man für einen Spieler auswählen kann. Dies wären zum einen "faul" und "verletzt" und zum anderen ein default-Wert, welcher in den Parametern steht. Diese Methode sollte wie folgt in den Helper ausgelagert werden.
def options_for_team_state (default_state)
options_for_select ( [[(:lazy), "draft"],
[(:injured), "injured"]],
default_state )
end
|
Pfad: app/helpers/<modelname>_helper.rb |
Dadurch kann die View angepasst und die Logik ausgelagert werden, sodass die View keine Logik mehr enthält. Ein weiterer Vorteil ist, dass auch in anderen Ansichtstypen die Funktion verwendet werden kann. Denkbar sind z.B. Methoden mit mehr Parametern, welche entsprechend der Parameter ähnliche Selectboxen mit unterschiedlichen Einstellungen erzeugt. Der ausreichende View-Code sieht dann wie folgt aus.
<%= select_tag :state, options_for_team_state ( params[:default_state] ) %>
|
Controller
BearbeitenLogik kann aus der View auch in den entsprechenden Controller ausgelagert werden. Beispielshaft ist im folgenden Viewcode dargestellt, welcher für jedes Team den Namen und die Abkürzung anzeigt.
<% @team = Team.find(:all) %>
<% @team.each do |team| %>
<%= team.name %>
<%= team.shortcut %>
<% end %>
|
Innerhalb dieses Codeabschnitts hat die erste Zeile, welche alle Teams in die @team Variable schreibt, nichts in der View zu suchen. Natürlich muss über die Variable iteriert werden, damit die Informationen für jedes Team angezeigt werden kann, doch hinter jeder Ansicht steckt eine Controller-Methode. Innerhalb dieser sollte die Variable @team gesetzt werden. Die Übersicht jedes Teamnamen und dessen Abkürzung ist in der Übersicht aller Teams dargestellt. Diese wird angezeigt, wenn man sich die Hauptseite für Teams anzeigen lässt. Die entsprechende Controllermethode ist index, deshalb muss in dem Controller in der Methode die Variable gesetzt werden und dann ist sie in der View gesetzt und kann verwendet werden.
class TeamController < ApplicationController
def index
@team = Team.find(:all)
end
end
|
Durch diese Zeile Code innerhalb der index-Methode ist die Variable @team initialisiert und kann in der View verwendet werden, ohne nochmals nach allen Teams suchen zu müssen. Auf die erste Zeile im View-Code kann also verzichtet werden und die Logik ist aus der View verschwunden.
Quellen
Bearbeiten- ↑ Quotes, Ruby on Rails Quotes
- ↑ VirtualRails, vorinstalliertes "Ruby on Rails"-System in einer Ubuntu VM.
- ↑ Railsinstaller, Installationspaket für Windows und MacOS.
- ↑ Installationsguide für einen Ubuntu VM mit RoR, ausführlicher Guide mit Ruby on Rails Tutorial.
- ↑ RubyGem, Gem Hosting Service zur Bereitstellung und Installation von Gems