Redvex3
Benvenuto visitatore
Login Registrati
Spinner
REST, risorse annidate

Piccolo tutorial per gestire le risorse annidate in Rails 2.x

E' comune, in un'applicazione web, avere a che fare con risorse annidate, pensiamo per esempio alle attività di un progetto, oppure ai libri di una libreria e così via. A livello di progetto e di database possiamo mappare le relazioni tramite direttive di ActiveRecord in modo da poterle poi richiamare nei nostri oggetti, ma possiamo fare la medesima cosa anche con le nostre risorse. Vediamo come.

Supponiamo di voler aggiungere l'oggetto "iterations" ai nostri "projects" creati durante il tutorial scorso.

Tramite terminale:

ruby script/generate scaffold iteration name:string start:date end:date project_id:integer
rake db:migrate

 Andiamo ad aggiungere le relazioni ai nostri modelli

class Project < ActiveRecord::Base 
   has_many :iterations 
end

class Iteration < ActiveRecord::Base 
   belongs_to :project 
end

Nested URL

Come nel precedente esempio, lo scaffold ha modificato il file routes.rb per mappare la risorsa, ma a noi la mappatura standard non va bene.

Per rendere le risorse gerarchiche anche nell'url cambiamo entrambe le direttive alle risorse generate nel file di routing come segue

map.resources :projects, :has_many :iterations

 Ovvero stiamo dicendo a Rails che per ogni progetto deve mappare molte iterazioni.
Se la relazione fosse stata 1 ad uno avremmo potuto usare :has_one.
Gli url risultanti saranno simili a:

/project/:project_id/iterations 
/project/:project_id/iterations/:id 

 E se volessimo dare un'occhiata a tutte le routes che la nostra applicazione accetterà? Nulla di più semplice, Rake routes ci da la risposta in tempo reale. Questa direttiva di rake è utile soprattutto per controllare che abbiam fatto tutto bene e non abbiam combinato guai.

Controller

Avendo modificato il comportamento standard dello scaffold ci tocca anche modificare controller e view. La modifica è abbastanza semplice, dobbiamo infatti semplicemente dire al nostro controller che l'iterazione la deve prendere dal progetto indicato.

Modifichiamo come segue:

def index 
   #@iterations = Iteration.find(:all) 
   @project = Project.find(params[:project_id]) 
   @iteration = @project.iterations.find(params[:id])
   respond_to do |format| 
      format.html # index.rhtml 
      format.xml { render :xml => @iterations.to_xml } 
   end 
end 

Nelle azioni show, edit e new invece dobbiamo solo aggiungere il riferimento a @project.

@project = Project.find(params[:project_id])

(create ed update e destroy le vedremo nel dettaglio)

View

Anche le view vanno modificate per riflettere i nuovi cambiamenti. In particolare nel progetto dovremo dare accesso alla funzione che mostri le iterazioni collegate. Diamo uno sguardo nuovamente al prodotto di rake routes e troviamo:

             project_iterations GET    /projects/:project_id/iterations                  {:action=>"index", :controller=>"iterations"}

Il primo elemento è proprio il nome dell'helper, il secondo il metodo http, il terzo la route e il quarto l'azione chiamata. Ora che abbiam trovato il nostro helper aggiungiamo il link alla view della lista progetti.

<h1>Listing projects</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Desc</th>
  </tr>

<% for project in @projects %>
  <tr>
    <td><%=h project.name %></td>
    <td><%=h project.desc %></td>
    <!-- Aggiunta per iterazioni in progetto -->
    <td><%= link_to 'Iterations', project_iterations(project) %></td>
    <!-- Fine aggiunta -->
    <td><%= link_to 'Show', project %></td>
    <td><%= link_to 'Edit', edit_project_path(project) %></td>
    <td><%= link_to 'Destroy', project, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New project', new_project_path %>

Abbiamo così detto a Rails come lanciare la visualizzazione dei progetti, passiamo ora alle view delle iterazioni.

Iniziamo con la lista delle iterazioni:

...
<% for iteration in @iterations %>
  <tr>
    <td><%=h iteration.name %></td>
    <td><%=h iteration.start %></td>
    <td><%=h iteration.end %></td>
    <td><%=h iteration.project_id %></td>
    <!-- <td><%= link_to 'Show', iteration %></td> -->
    <!-- <td><%= link_to 'Edit', edit_iteration_path(iteration) %></td> -->
    <!-- <td><%= link_to 'Destroy', iteration, :confirm => 'Are you sure?', :method => :delete %></td> -->
    <td><%= link_to 'Show', project_iteration_path(@project,iteration) %></td>
    <td><%= link_to 'Edit', edit_project_iteration_path(@project, iteration) %></td>
    <td><%= link_to 'Destroy', project_iteration_path(@project, iteration), :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>
...
<!-- <%= link_to 'New iteration', new_iteration_path %> -->
<% link_to "New Iteration", new_project_iteration_path %>

Nuova iterazione

Per inserire una nuova iterazione dobbiamo il controller.

Nell vista dobbiamo modificare due elementi. Il form_for e il link per tornare alle iterazioni, come segue:

 <h1>New iteration</h1>

<% form_for([@project, @iteration]) do |f| %> <!-- Passiamo il riferimento dell'iterazione all'interno del progetto -->
  <%= f.error_messages %>
  #... FORM ...#
<% end %>

<%= link_to 'Back', project_iterations_path(@project) %> <!-- Ritorna alle iterazioni all'interno del progetto -->

Ora modifichiamo il controller per indicare a rails che la nuova iterazione fa parte di del progetto preso in esame.

def create
    @iteration = @project.iterations.new(params[:iteration]) #Creiamo un'iterazione all'interno di un progetto

    respond_to do |format|
      if @iteration.save
        flash[:notice] = 'Iteration was successfully created.'
        #Redirect verso l'url corretto per il formato html e xml
        format.html { redirect_to(project_iterations_path(@project)) }
        format.xml  { render :xml => @iteration, :status => :created, :location => (project_iterations_path(@project)) }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @iteration.errors, :status => :unprocessable_entity }
      end
    end
  end

 Modifica iterazione

Analizziamo ora l'update

Iniziamo al solito con la vista, anche qui dobbiamo modificare il form_for e gli url per tornare indietro e mostrare l'iterazione

 <h1>Editing iteration</h1>

<% form_for([@project,@iteration]) do |f| %>
  <%= f.error_messages %>
  #...FORM...#
<% end %>

<%= link_to 'Show', project_iteration_path(@project,@iteration) %> |
<%= link_to 'Back', project_iterations_path(@project) %>

Mentre nel controller dobbiamo modificare il metodo di update e i redirect_to come segue

  def update
    @iteration = @project.iterations.find(params[:id])

    respond_to do |format|
      if @iteration.update_attributes(params[:iteration])
        flash[:notice] = 'Iteration was successfully updated.'
        format.html { iteration_url(@iteration.project, @iteration) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @iteration.errors, :status => :unprocessable_entity }
      end
    end
  end

Non dimentichiamo che prima di ogni azione recuperiamo il riferimento a @project dalla funzione privata find_project.

Cancellazione iterazione

L'ultimo metodo CRUD da analizzare è il destroy.

Qui abbiamo solo il controller e la modifica è simile a quella fatta per gli altri metodi, ovvero modifica :

 def destroy
    @iteration = @project.iterations.find(params[:id])
    @iteration.destroy

    respond_to do |format|
      format.html { redirect_to(project_iterations_path(@project)) }
      format.xml  { head :ok }
    end
  end

 Conclusioni

Con rails 2.x la nostra applicazione RESTful raggiunge lo stato dell'arte, alcune soluzioni, come l'has_many nelle routes sono puro zucchero sintattico come direbbe qualche mio collega.

Potete scaricare l'intero progetto da qui

Back
se non sei registrato, registrati per inserire commenti, oppure effettua il login

 
Notizie Mac
 
 
1
PdC Calculator
1 2 3
 
View Gianni Mazza's profile on LinkedIn