Websiteentwicklung: Ruby on Rails: Assoziationen
Dazu gibt es einen Rails Guide [1] Active Record Associations |
Zwei Modelle sollen eine Beziehung haben, zum Beispiel: zu jeder unserer Hello-Meldungen soll es mehrere Comments geben können, jedes Comment gehört zu genau einem Hallo. Dies nennt man eine 1:n Assoziationen.
Modell und Datenbank
[Bearbeiten]Diese Beziehung passiert auf zwei Ebenene: im Modell und in der Datenbank.
In den beiden Modellen wird die Beziehung so aussehen:
class Comment < ActiveRecord::Base belongs_to :hallo attr_accessible :text end class Hallo < ActiveRecord::Base has_many :comments, :dependent => :destroy attr_accessible :farbe, :meldung, :von end
In der Datenbank gibt es einen Fremdschlüssel hallo_id in der comment-Tabelle.
Wenn man das gleich von Anfang an bedenkt kann man diesen Schlüssel schon beim scaffold mit erzeugen lassen:
rails generate scaffold Comment text:text hallo_id:integer rake db:migrate
Unter der URL http://localhost.3000/comments/ kann man jetzt Comments anlegen
Die Objekte sind auf foglende Weise miteinander verbunden: man kann von einem comment immer zu seinem hallo gelangen und umgekehrt:
@hallo.comments # ---> Array aller Comments @comment.hallo # ---> ein Hallo
Routes
[Bearbeiten]Bei einer belongs_to/has_many Beziehung macht es Sinn, die URLs zu verschachteln: Ein Comment ist immer einem Hallo zugeordnet.
hallo_comments GET /hallos/:hallo_id/comments(.:format) comments#index POST /hallos/:hallo_id/comments(.:format) comments#create new_hallo_comment GET /hallos/:hallo_id/comments/new(.:format) comments#new edit_hallo_comment GET /hallos/:hallo_id/comments/:id/edit(.:format) comments#edit hallo_comment GET /hallos/:hallo_id/comments/:id(.:format) comments#show PUT /hallos/:hallo_id/comments/:id(.:format) comments#update DELETE /hallos/:hallo_id/comments/:id(.:format) comments#destroy hallos GET /hallos(.:format) hallos#index POST /hallos(.:format) hallos#create new_hallo GET /hallos/new(.:format) hallos#new edit_hallo GET /hallos/:id/edit(.:format) hallos#edit hallo GET /hallos/:id(.:format) hallos#show PUT /hallos/:id(.:format) hallos#update DELETE /hallos/:id(.:format) hallos#destroy root / hallos#index
Dies erreicht man mit folgender Änderung in config/routes.rb
resources :hallos do resources :comments end
Nun können wir die Controller und Views ausprobieren: die URL zum Einstieg lautet http://localhost:3000/hallo/1/comments
Controller
[Bearbeiten]Im comments_controller erhalten wir immer den Parameter hallo_id. Wir können also bei jeder einzelnen Action folgende erste Zeile eintragen:
@hallo = Hallo.find(params[:hallo_id])
Statt das 7mal einzutragen gibt es eine einfachere Möglichkeit: einen before_filter. Die im before_filter angegebene Funktion wird vor jeder Action ausgeführt. Da die Funktion in eine Instanz-Variable schreibt (erkennbar am @ vor dem Variablennamen) ist die Information dann auch in der Action verfügbar.
class CommentsController < ApplicationController before_filter :get_hallo ... # am Ende der Klasse kommen die privaten Funktionen: private def get_hallo @hallo = Hallo.find(params[:hallo_id]) if @hallo.nil? then raise "Welches Hallo?" end end end
Die Methoden create muss auch angepasst werden: das Kommentar wird nicht einfach als Objekt der Klasse Comment erzeugt, sondern wir verwenden die build Methode des Objekts @hello:
@comment = @hallo.comments.build(params[:comment])
Im index wollen wir nicht alle comments der Datenbank (Comments.all) anzeigen, sondern
nur diejenigen, die zum akutellen Hallo gehören:
def index @comments = @hallo.comments ...
Neue URLs in Views und Controller
[Bearbeiten]In den Views des comment-Controllers kann man jetzt auch @hallo lesen.
In den Views muss man viele Pfade ändern. Ein Beispiel:
aus comment_path() wird hallo_comment_path(@hallo)
Am einfachsten ist es sich die Routes anzeigen zu lassen, um heraus zu finden wie die neue Version der einzelnen Methoden lautet.
Vorher:
<td><%= link_to 'Show', comment %></td> <td><%= link_to 'Edit', edit_comment_path(comment) %></td> <td><%= link_to 'Destroy', comment, method: :delete, data: { confirm: 'Are you sure?' } %></td>
Nachher:
<td><%= link_to 'Show', hallo_comment_path(@hallo, comment)' %></td> <td><%= link_to 'Edit', edit_hallo_comment_path(@hallo,comment) %></td> <td><%= link_to 'Destroy', hallo_comment_path(@hallo,comment), method: :delete, data: { confirm: 'Are you sure?' } %></td>
Analog für form_for: auch das Formular muss einen längern Pfad benutzen:
<%= form_for([@hallo,@comment]) do |f| %>
Das Eingabefeld für hallo_id kann man dafür ersatzlos streichen: der Parameter hallo_id wird aus der URL übernommen.
Wenn man nun ein neues Comment speichern will gibt es ein Problem mit dem Controller:
# POST /comments # POST /comments.json def create @comment = Comment.new(params[:comment])
respond_to do |format| if @comment.save format.html { redirect_to @comment, notice: 'Comment was successfully created.' } format.json { render json: @comment, status: :created, location: @comment } else format.html { render action: "new" } format.json { render json: @comment.errors, status: :unprocessable_entity } end end end
Das Erzeugen und Abspeichern des Comments funktioniert schon richtig, das Problem tritt danach auf:
format.html { redirect_to @comment, notice: 'Comment was successfully created.' }
Hier soll zur show-action des neu angelegten Comments umgeleitet werden. Da Comments nun eine verschachtelte Ressource ist, müssen wir die entsprechende URL verwenden:
format.html { redirect_to hallo_comment_path(@hallo,@comment), notice: 'Comment was successfully created.' }
Quellen
- ↑ Rails Guide Active Record Associations