Kurs:Neueste Internet- und WWW-Technologien/Entwurfsmuster für die Webprogrammierung (am Beispiel Ruby on Rails)

Aus Wikiversity
Zur Navigation springen Zur Suche springen

Einleitung[Bearbeiten]

Ruby 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[Bearbeiten]

Grundbeziehung in einem MVC-basiertem Webframework

Ruby 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[Bearbeiten]

Mö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[Bearbeiten]

Ein 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[Bearbeiten]

Beziehungen innerhalb der Beispielimplementierung

Im 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]

  1. 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.
  2. 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.
  3. 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.
    1. rails generate scaffold Team name:string shortcut:string
    2. rails generate scaffold Player name:string number:integer
    3. rails generate scaffold Trainer name:string
  4. 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
  5. 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[Bearbeiten]

In 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[Bearbeiten]

Falls ü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[Bearbeiten]

Beim 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[Bearbeiten]

Entwurfsmuster (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[Bearbeiten]

REST (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[Bearbeiten]

Im folgenden werden drei Refactorings für das Model eines Objekts vorgestellt. Diese sind nur ein Teil von Verbesserungsmöglichkeiten.

Law of Demeter[Bearbeiten]

Das 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[Bearbeiten]

Problem: 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[Bearbeiten]

Jeder 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[Bearbeiten]

Im 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[Bearbeiten]

In 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[Bearbeiten]

Im 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[Bearbeiten]

Im 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[Bearbeiten]

Fü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[Bearbeiten]

Logik 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]

  1. Quotes, Ruby on Rails Quotes
  2. VirtualRails, vorinstalliertes "Ruby on Rails"-System in einer Ubuntu VM.
  3. Railsinstaller, Installationspaket für Windows und MacOS.
  4. Installationsguide für einen Ubuntu VM mit RoR, ausführlicher Guide mit Ruby on Rails Tutorial.
  5. RubyGem, Gem Hosting Service zur Bereitstellung und Installation von Gems