Rails Tutorial DHBW-Heidenheim

Erzeugung von HTML

Die Interaktion von Controller und View ist einer der komplexeren Aspekte von Ruby on Rails. Es gibt zahlreiche Möglichkeiten und Variationen, eine HTTP-Response umzusetzen und Daten in Views zu implementieren. In dieser Lesson werden die gebräuchlichsten Verfahren beschrieben.

Wenn der eigentliche Quellcode des Controllers durchlaufen ist, wird eine HTTP-Response erzeugt. Das Response-Objekt wird immer erzeugt, selbst wenn es bei der Verarbeitung im Controller zu Fehlern gekommen ist - in diesem Fall wäre der Status-Code ungleich 2XX. Die Response enthält ggf. Daten, die vom Controller an eine View gereicht werden.

Es gibt drei Methoden, eine HTTP-Response zu erzeugen, render, redirect_to und head. Render gibt eine komplette HTTP-Response an den Browser zurück. Redirect_to gibt einen HTTP-Statuscode 3xx (Redirect) an den Browser zurück und head erzeugt einen HTTP-Header und gibt diesen an den Browser zurück.

Render

Die render-Methode wird automatisch aufgerufen, und zwar wird standardmässig der View mit dem gleichen Namen wie die aufgerufene Methode des Controller gerendert. Die Daten der HTTP-Response werden automatisch in dieser View ausgegeben.

Ist beispielsweise die index-Methode des Gruppen-Controller


	def GroupsController < ApplicationController
	   def index

	   end
	end	
	

Mit einem entsprechenden Mapping


			Ressources :groups	
		

Und einer View namens index.html.erb im Verzeichnis app/views/groups/


	<h1>Hello from groups index</h1>

Wird der Text "Hello from groups index" ausgegeben, wenn die Index-Methode des Gruppen-Controllers aufgerufen wird (http://localhost:3000/groups).

In der View wird festgelegt, wie die Daten angezeigt werden. Typischerweise übergibt die Controller-Methode einen Hash mit allen Daten an die entsprechende View. Dort werden die einzelnen Datensätze in Form einer Tabelle oder einer Liste ausgegeben:

Also im Controller


	def GroupsController < ApplicationController
	   def index
		@groups = Group.all
	   end
	end	

Die zugehörige View


	<h1>Listing groups</h1>

	<p><%= link_to 'New Group', new_group_path %></p>
	<table>
	  <tr>
	    <th>Kurz</th>
	    <th></th>
	</tr>

	<% @groups.each do |group| %>
	  <tr>
	    <td><%= group.kurz %></td>
	    <td><%= link_to 'Show', group %></td>
	</tr>
	<% end %>
	</table>

	<br />	

Das Ergebnis ist die gerenderte Index-View mit allen Gruppen:

Explizite Verwendung von render

Es gibt mehrere Möglichkeiten, render in einer Controller-Methode zu verwenden. Die Einfachste ist render :nothing => true. Dadurch wird eine leeres HTTP-Response an den Browser zurückgegeben. In der Praxis wird render_nothing verwendet, wenn etwa die erfolgreiche Ausführung einer Controller-Methode durch einen AJAX-Request über den Statuscode erfragt wird.

Render wird am Häufigsten verwendet, um eine andere View des selben Controllers anzusprechen.

Wenn beispielsweise eine Gruppen-Ressource geändert wird, kann diese eventuell nicht abgespeichert werden. Für diesen Fall wird die View app/views/groups/edit.html.erb aufgerufen:


	Def GroupsController < ApplicationController
	...
	   @group = Group.find(params[:id])

	   if @group.update_attributes(params[:group])
	      redirect_to(@groups)
	   else
	      render :action=>"edit"
	   end
	...
	end	

ACHTUNG: Wenn die render-Methode für eine andere View aufgerufen wird, wird die zugehörige Controller-Action nicht durchlaufen. Variablen oder Datensätze, die in der aufgerufenen View verarbeitet werden, müssen übergeben werden! Im Beispiel übergibt die Update-Methode des Gruppen-Controllers die Ressource @group an app/views/groups/edit.html.erb.

Es gibt mehrere Möglichkeiten, die entsprechende View des selben Controllers aufzurufen. render action =>"edit" ist die ausführliche, render => "edit" ist die kurze Version.

Wenn Daten einer Controller-Methode in einer View eines anderen Controllers gerendert werden sollen, muss der vollständige Pfad angegeben werden, also :render => "controller/action" oder render :template => "controller/action".

Durch den slash im Pfad wird bekannt gegeben, dass eine View eines anderen Controllers angesprochen wird. Die ausführliche Variante verwendet den Zusatz template. In einer Web-Applikation wird render_template verwendet, wenn beispielsweise Daten in einer View des Adminbereiches dargestellt werden sollen.

Render kann Daten auch direkt ausgeben, eine View ist nicht erforderlich. Achtung: Dies verstösst gegen das MVC-Prinzip und die strikte Trennung der Schichten. Es wird deshalb davon abgeraten render_inline zu verwenden.

Beispielsweise könnten die Gruppen folgendermassen ausgegeben werden:


	def GroupsController < ApplicationController
	   def index
		@goups = Group.all
		render :inline =>
		  "<% @groups.each do |g| %> g.name <br /><% end %>"
	   end
	end

Render inline kann auch mit Javascript verwendet werden. Hierbei gelten die selben Bedenken wie bei render_inline mit Html.

Ein Element der Seite kann so direkt vom Controller geändert werden:


	...
	Render :update do |page|
	   Page.replace_html 'content', 'Neuer Inhalt'
	end
	...

render kann aber nicht nur HTML erzeugen. Soll etwa Javascript erzeugt werden, kann dies entsprechend angegeben werden.


	Render :js => "alert('Hello World!')"

Es können aber auch andere Formate als HTML, beispielsweise auch Text zurückgegeben werden.


	Render :text => "Hello world"

Diese Methode wird meist bei AJAX-Requests oder Webservices verwendet. Es kann aber auch json oder xml zurückgegeben werden:


	Render :json => @groups
	Render: xml => @groups
	

Layouts

Die Inhalte einer Webapplikation werden meist in einem Layout gerendert, siehe etwa www.apple.de . Das Layout enthält den Seitenaufbau, Farben oder die Art der Darstellung der Texte. Einige Bereiche in einem Layout sind dynamisch, etwa der Bereich in dem Produkte oder Texte dargestellt werden. Andere Bereiche des Layouts sind statisch, etwa der Hintergrund oder der Seitenkopf.

Eine Konvention von Rails ist DRY (Don't repeat yourself). Die statischen Teile des Layouts werden deshalb in das Verzeichnis app/views/layouts ausgelagert.

Während eine Methode gerendert wird, sucht Rails im Verzeichnis app/views/layouts eine Datei die den Namen des Controllers hat.

Wird keine gleichnamige Datei gefunden, wird die Datei app/views/layouts/application.html.erb verwendet.

Wenn eine Webapplikation hauptsächlich nur ein Layout hat, kann das Layout in app/views/layouts/application.html.erb abgelegt werden. Alle Controller-Methoden der Anwendung werden in diesem Layout dargestellt.

Werden in einer Webapplikation allerdings mehrere Layouts verwendet, können diese in Dateien mit anderen Namen abgelegt werden, etwa admin.html.erb oder visitor.html.erb. Wenn es einen Controller für Produkte gibt, könnten alle Methoden etwa im Admin-Layout gerendert werden.


		def ProductsController < ApplicationController
		layout "admin"
		...
		end
	

Methoden die für Besucher bzw. Kunden zugänglich sind, etwa product_user_show könnten im Visitor-Layout gerendert werden:


		def ProductsController < ApplicationController
		layout "admin", :except=>[:product_user_show]
		...
		def product_user_show
		@product = Product.find(params[:id])
		render :layout => "visitor"
		...
		end	
	

Der Zusatz except gibt an, welche Methoden des Controllers nicht im zugewiesenen Layout gerendert werden. Wenn die Methode aufgerufen wird, rendert Rails diese im Layout des zugehörigen Controllers oder in app/views/layouts/application.html.erb. Es kann aber auch ein spezielles Layout als Option zu Render angegeben werden, in diesem Fall das Visitor-Layout.

Im Update-Beispiel von oben


	def GroupsController < ApplicationController
	...
	   @group = Group.find(params[:id])

	   if @group.update_attributes(params[:group])
	      redirect_to(@gourps)
	   else
	      render action=>"edit"
	   end
	...
	end

ist render beschrieben, nicht aber redirect_to. Die beiden Methoden machen auf den ersten Blick vielleicht das Gleiche, haben tatsächlich aber eine komplett verschiedene Wirkung. Während Render die View angibt, in der Daten ausgegeben werden, erzeugt redirect_to einen komplett neuen HTTP-Request. Das ist gleichbedeutend mit der Anforderung einer Ressource, etwa http://localhost:3000/groups.

Wenn das Update im Beispiel erfolgreich ist, wird die Index-Methode des Gruppen-Controllers aufgerufen. Es wird nicht nur die View gerendert, sonder auch die Controller-Methode durchlaufen.

In der Navigation von Web-Applikationen ist es manchmal notwendig dem Benutzer die Möglichkeit zu geben, einen Schritt zurückzugehen - das geschieht mit redirect_to :back, dabei wird die Ressource ausgeführt, von der der Benutzer gekommen ist.

Aufbau und Bestandteile eines Layouts

Wenn eine View gerendert wird, werden verschiedene Bestandteile zusammengeführt, etwa das Layout mit den Style-Informationen (CSS), JavaScript und den Daten der Response des Controllers. Hierfür werden verschiedene Methoden bereitgestellt, etwa asset_tags, yield, content_for und partials.

Asset-Tags sind Helper-Methoden, die HTML erzeugen. Standardmässig gibt es stylesheet_link_tag, javascript_include_tag, image_tag, video_tag, audio_tag und auto_discovery_link_tag.

Mit dem stylesheet_link_tag werden Style-Informationen eingebunden, etwa wo diese zu finden sind. Typischerweise liegen die Stylesheet-Dateien im Verzeichnis public/stylesheets. Eine Style-Datei mit Namen style.css wird folgendermassen eingebunden:


		<%= stylesheet_link_tag "style"%>
	

Das erzeugte Html:


		<link rel="stylesheet" type="text/css" href="/stylesheets/style.css" media="screen" />
	

Der Helper Javascirpt_include_tag kann ebenfalls wie stylesheet_include_tag verewndet werden. Eine Besonderheit ist jedoch


		<%= javascript_include_tag :defaults %>
	

womit die Javascript-Bibliotheken Prototype und script.aculo.us eingebunden werden.

Mit Hilfe des image_tags können Bilder aus public/images in die Webapplikation eingebunden werden:


	<%= image_tag "bild.jpg" %>

Ebenso können mit video_tag Video- und mit audio_tag Audio-Files in die Applikation eingebunden werden.

View und Layout zusammenführen

An der Stelle im Layout, an der Views gerendert werden, steht <%= yield %>, also etwa:


		...
		<div id="content">
		   <div class="right">
		      <%= yield %>
		   </div>
		</div>
		...
	

Es können auch an mehreren Stellen eines Layouts Daten gerendert werden:


		...
		<body>
		<%= yield :my_message_title %>
		   <div id="content">
		      <div class="right">
		         <%= yield %>
		      </div>
		   </div>
		</body>
		...
	

Der Inhalt für das benannte yield wird in der entsprechenden View mit content_for definiert: /app/views/some_view.html.erb


		...
		<% content_for :my_messge do %>
		   <p>Hello from content_for</p>
		<% end %>
		<p>Der Inhalt für das unbenannte yield</p>
		...
	

Wenn die Methode some_view aufgerufen wird, wird folgender Html-Code erzeugt:


	<command>
		<html>
		   <head>
		...
		   </head>
		   <body>
		      <p>Hello from content_for</p>
		      <div id="content">
		         <div class="right">
		             <p>Der Inhalt für das unbenannte yield</p>
		         </div>
		      </div>
		   </body>
		</html>
	

Effiziente Views durch Partials

Ein Layout hat nicht nur dynamische Bestandteile wie etwa den Inhaltsbereich, sondern auch statische, wie etwa Hintergrund, Fusszeile oder die Login-Anzeige. Diese Informationen können in Partials ausgelagert und an der entsprechenden Stelle eingebunden werden. Durch den Einsatz von Partials können komplexe Layout-Dateien entlastet, oder Informationen in verschiedenen Views dargestellt werden.

Partials können an zwei Stellen abgelegt werden. Wenn sie für die gesamte Anwendung von Bedeutung sind, wie etwa die Fusszeile oder das Menü, werden sie in app/views/shared abgelegt, ansonsten in den View-Ordner des Controllers.

Quellcode der etwa in das Partial app/views/shared/_company_data.html.erb ausgelagert ist, wird mit dem Befehl:


	<%= render :partial=>"shared/company_data" %>

eingebunden. Der Quellcode wird an der Stelle vom render_partial-Aufruf ausgegeben. Wenn Partials von einem View-Ordner aufgerufen werden, wird shared entsprechend ersetzt, also etwa


	<%= render :partial => "groups/das_partial" %>

Einfügen des Layouts in die Beispiel-Anwendung

Die Index-Methode der Beispiel-Anwendung wird im Moment mit den Html-Standardeinstellungen ausgegeben. Es sind noch keine Layout- oder Style-Informationen implementiert:

Für die Beispielanwendung wird ein freies Template verwendet ( http://www.free-css-templates.com/downloads/239/WhiteClean.zip ):

Zuerst werden die Beispiel-Inhalte entfernt:


		<DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
		"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
		<html xmlns="http://www.w3.org/1999/xhtml">
		<head>
		<title>White and Clean</title>
		<meta http-equiv="Content-Language" content="English" />
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<link rel="stylesheet" type="text/css" href="style.css" media="screen" />
		</head>
		<body>

		<div id="wrap"> 

		<div id="header">
		<h1><a href="#">Rails tutorial</a></h1>
		<h2>only demonstration ;-(</h2>
		</div>

		<div id="menu">
		<ul>
		<li><a href=“/“>HOME</a></li>
		</ul>
		</div>

		<div id="content">
		<div class="right">
		<%= yield %>
		</div>

		<div class="left">
		</div>
		<div style="clear: both;"> </div>
		</div>
		<div id="footer">
		<a href="http://www.templatesold.com/" target="_blank">Website Templates</a> by 
		<a href="http://www.free-css-templates.com/" target="_blank">Free CSS Templates</a>
		</div>
		</div>

		</body>
		</html>
	

Der Html-Code wird in app/views/layouts/application.html.erb eingefügt. Es ist darauf zu achten, dass <%= yield %> an der entsprechenden Stelle eingefügt ist! Die Anwendung sieht jetzt so aus - http://localhost:3000:

Das Layout wird gerendert, allerdings fehlen die Style-Informationen. Hierzu werden die Stylesheets in /public/stylesheets/style.css eingefügt. Abschliessend wird in app/views/application.html.erb der korrekte Pfad zu den Style-Informationen angegeben in dem die Zeile:


	<link rel="stylesheet" type="text/css" href="style.css" media="screen" />

durch:


	<%= stylesheet_link_tag "style" %>

ersetzt wird. Das Ergebnis:

Die Style-Informationen sind eingebunden und das Layout wird ordentlich dargestellt. Falls Bilder fehlen, sind diese in das Verzeichnis public/images zu kopieren und in den entsprechenden CSS-Dateien anzupassen, das ist hier im Beispiel allerdings nicht der Fall.

Im nächsten Schritt wird das Menü eingebunden. Die Menüeinträge werden in app/views/shared/_menu.html.erb abgelegt:


	<ul>
	<li><%= link_to "HOME", :root%> </li>
	<li><%= link_to "Mein Profil", users_my_profile_path %> </li>
	<li><%= link_to "Meine Nachrichten", messages_path %>
	<li><%= link_to "Mitglieder suchen", users_search_user_path %> </li>
	<li><%= link_to "Gruppen", groups_path %> </li>
	</ul>

und an der entsprechenden Stelle im Layout ausgegeben:


	...
	<div id="menu">
	   <%= render :partial=>"shared/menu" %>
	</div>
	...

Die Login-Links werden in app/views/shared/_login.html.erb abgelegt:


	<% if user_signed_in? %> 
	    Eingeloggt als <%= current_user.email %>. Nicht Ihr Mitgliedsname?
	    <%= link_to "ausloggen", destroy_user_session_path %>
	  <% if current_user.messages.all(:conditions=>["read=false"]).count > 0 %>
	   <p>Sei haben neue Nachrichten: <%= current_user.messages.all(
	:conditions=>["read=false"]).count %></p>
	  <% end %>
	  <% else %>
	    <%= link_to "Registrieren", new_user_registration_path %> oder 
	<%= link_to "einloggen", new_user_session_path %>
	  <% end %>	

und an der entsprechenden Stelle im Layout ausgegeben:


	...
	<div id="benutzer_navigation"<
	<%= render :partial=>"shared/login" %<
	</div>
	...	

Als letztes wird die Liste mit neuen Benutzern in einem Partial abgelegt - app/views/shared/_new_members.html.erb:


	<h2>Neueste Mitglieder</h2>
	<% @users = User.find(:all, :order=>"id ASC", :limit=>5) %>
	<ul>
	<% @users.each do |user| %>
	<li><%= link_to user.nickname, users_profile_path(user.id) %></li>
	<% end %>
	</ul>

und an der entsprechenden Stelle im Layout ausgegeben:


	...
	<div class="left">
	   <%= render :partial=>"/shared/new_members" %>
	</div>
	...

das komplette Layout: