Websiteentwicklung: Ruby on Rails: Assoziationen

Rails Guide 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

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

  1. Rails Guide Active Record Associations