homeASCIIcasts

222: Rack in Rails 3 

(view original Railscast)

Other translations: En Es

Other formats:

Written by Andrea Salicetti

Il routing in Rails 3 è stato trattato piuttosto esaustivamente nel presedente episodio 203 [guardalo, leggilo], ma ci sono alcune funzionalità avanzate che mostrano in modo davvero palese la flessibilità del router di Rails 3. Alcune di queste vi verranno mostrate proprio in questo episodio.

Routing verso applicazioni Rack

Cominciamo con un esempio semplice. La nostra applicazione di esempio ha un instradamento che punta il proprio URL radice alla action index del HomeController:

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => "home#index"
end

La cosa importante da sapere in merito alla stringa "home#index" è che è uno shortcut fornitoci per comodità. Dietro le quinte la stringa viene tradotta in questo:

HomeController.action(:index)

Questo metodo action restituisce un’applicazione Rack e l’avere il router capace di gestire applicazioni Rack gli da tutta la flessibilità, citata, poichè diventa semplice ottenere un’applicazione Rack da qualsiasi controller Rails. Per dimostrarlo, creiamo rapidamente una semplice applicazione Rack passando l’hash dell’environment ad un nuovo oggetto proc e restituiamo un array con un codice di stato, un hash (vuoto) di header e un po’ di semplice contenuto per il body:

/config.routes.rb

Store::Application.routes.draw do |map|
  root :to => proc { |env| [200, {}, ["Welcome"]] }
end

Visitando l’URL radice della nostra applicazione vedremo che ha restituito una risposta.

L’output proveniente dalla nostra applicazione Rack.

Questa flessibilità si traduce nel fatto che è semplice integrare qualsiasi applicazione basata su Rack in una applicazione Rails, ed ora dimostreremo ciò aggiungendo alla nostra applicazione Sinatra.

Dobbiamo aggiungere un riferimento al gem di Sinatra nel Gemfile della nostra applicazione Rails:

/Gemfile

gem 'sinatra'

Poi, per essere sicuri che il gem sia installato, lanciamo:

bundle install

Ora possiamo fare la nostra applicazione Sinatra. La creiamo nella cartella /lib e la chiamiamo home_app.rb. Dobbiamo creare una classe che erediti da Sinatra::Base e darle un metodo get per l’URL radice che restituisca una stringa.

/lib/home_app.rb

class HomeApp < Sinatra::Base
  get "/" do
    "Hello from Sinatra"
  end
end

Ora possiamo aggiornare il file degli instradamenti in modo tale che l’instadamento radice punti alla nostra applicazione Rack HomeApp:

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => HomeApp
end

Ora, ricaricando la home page della nostra applicazione, vedremo la risposta proveniente dall’applicazione Sinatra:

L’output della nostra applicazione Sinatra.

Ridirezioni

Come potete vedere, Rails 3 aderisce perfettamente a Rack. Uno dei benefici di ciò è il nuovo metodo di ridirezione che ora vi mostraremo nel suo funzionamento. Poniamo che la nostra applicazione abbia un route per una pagina di about con l’URL /about e che sia servita dalla action about dell’ InfoController:

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => HomeApp
  match "/about" => "info#about" 
end

Se volessimo cambiare l’URL da /about a /aboutus, potremmo semplicemente modificare il route, ma ci rimarrebbe un route legacy che non punterebbe più a nulla. Per fortuna è semplice impostare dei redirect in Rails 3, usando il metodo redirect:

/config/routes.rb

Store::Application.routes.draw do |map|
  root :to => HomeApp
  match "/about" => redirect("/aboutus")
  match "/aboutus" => "info#about" 
end

Col nuovo route ed il redirect a posto, il nuovo route funzionerà ed il vecchio ridirigerà al nuovo. Visitando l’URL /about, verremo ridiretti a /aboutus:

Viene mostrata al pagina di ridirezione.

Ora mostreremo uno scenario di uso leggermente più complesso per il metodo redirect. Poniamo di avere un ProductsController e di voler avere un’URL di shortcut della forma /p/:id per ciascun prodotto. Per fare ciò, possiamo cambiare il file di route in questo modo:

/config/routes.rb

Store::Application.routes.draw do |map|
  get "info/about"

  root :to => HomeApp
  match "/about" => redirect("/aboutus")
  match "/aboutus" => "info#about" 
  
  resources :products
  match "/p/:id" => redirect("/products/%{id}")
end

Il route di shortcut per un prodotto è l’ultimo presente nel file. Ridirige dall’URL di shortcut all’URL classico che punta a un prodotto. Lo fa, usando nuovamente il metodo redirect, ma questa volta la ridirezione avviene verso un URL dinamico. Per aggiungere l’id di un prodotto id nell’URL di ridirezione, dobbiamo usare il simbolo percentuale seguito da id racchiuso fra parentesi graffe. Ciò causerà l’inserzione del parametro id nell’URL di ridirezione.

Provando a visitare http://localhost:3000/p/1 ora, verremo ridiretti alla pagina di quel prodotto:

La pagina dei prodotti a cui siamo stati ridiretti.

Se avessimo bisogno di un maggior controllo su come avvengono i redirect, potremmo passare un blocco al metodo redirect, ma non tratteremo questo aspetto in questo episodio. Ci sono comunque i riferimenti su come farlo sul sito rails.info.

Rails Metal

Nell’episodio 150 abbiamo parlato di Rails Metal [guardalo, leggilo] e abbiamo creato una pagina che elencasse i processi in esecuzione su una certa macchina. In Rails 3 la tecnica in questione è lievemente differente, ma molto più semplice da usare grazie all’integrazione con Rack. Replicare quanto fatto il tale episodio è piuttosto semplice. Per prima cosa creiamo un nuovo route e lo facciamo puntare ad una nuova applicazione Rack chiamata ProcessesApp:

/config/routes.rb

match "/processes" => ProcessesApp

Creiamo questa nuova applicazione nella cartella /lib e la chiamiamo processes_app.rb. La classe avrà un metodo di classe chiamato call che accetta un hash di ambiente. In questo metodo, lanceremo lo stesso comando usato nell’episodio 150 e ne restituiremo il risultato:

/lib/processes_app.rb

class ProcessesApp
  def self.call(env)
    [200, {}, [`ps -axcr -o "pid,pcpu, pmem, time, comm"`]]
  end
end

Andando alla pagina dei processi da browser, vedremo l’elenco dei processi e, ricaricando la pagina, noteremo come il ricaricamento sia decisamente rapido dal momento che si sta utilizzando una semplice applicazione Rack:

La nostra applicazione dei processi.

Non avviene nulla di speciale nella classe ProcessApp. Tutto ciò che stiamo facendo è banalmente creare una semplice applicazione Rack: siamo solo riusciti a replicare in poche linee di codice il comportamento dell’episodio 150, in modo ancor più semplice.

Non abbiamo alcun controllo sullo stile della pagina dei processi, dal momento che la response non ha template. Possiamo, in ogni modo, estendere la nostra applicazione Rack in modo tale che richieda alcuni pezzi ad un controller Rails, permettendoci in tal modo di sfruttare i template rendendo la pagina meno arida.

Per dare la possibilità alla pagina dei processi di renderizzare un template erb anzichè restituire semplicemente l’output grezzo del comando ps, dobbiamo solo fare in modo che la classe ProcessesApp erediti da ActionController::Metal. Questa modifica di tassonomia inietterà un po’ di comportamento stile "controller" dalla nostra applicazione Rails, ma ad un livello più basso, per cui dovremo includere il module ActionController::Rendering per ottenere tutte le funzionalità che vogliamo.

Dal momento che il controller che stiamo usando agisce a un livello più basso rispetto ad un normale controller Rails, dobbiamo inidicare il percorso della vista e lo facciamo chiamando il metodo append_view_path e passandogli il route alla cartella views della nostra applicazione. Ciò farà sì che il controller ricerchi i propri template nella stessa cartella in cui andrebbe a guardare un controller normale.

Vogliamo che il controller abbia una action index, per cui abbiamo sostituito il metodo self.call con un metodo index che fa le stesse chiamate al ps che avevamo prima, ma che assegna l’output a una variabile di istanza. Il metodo poi chiama il metodo render affinchè renderizzato il template. Rispetto a un controller Rails normale, il template non verrebbe altrimenti renderizzato in modo automatico:

/lib/processes_app.rb

class ProcessesApp < ActionController::Metal
  include ActionController::Rendering
  
  append_view_path "#{Rails.root}/app/views"
  
  def index
    @processes = `ps -axcr -o "pid,pcpu, pmem, time, comm"`
    render
  end
end

Ora dobbiamo creare il template. Nella cartella delle viste della nostra applicazione dobbiamo creare una cartella process_app in cui possiamo mettere il nostro nuovo template. Possiamo mettere qualsiasi template vogliamo in questa cartella, esattamente come faremmo per un normale template erb:

/app/views/processes_app/index.html.erb

<h1>Processes</h1>
<pre><%= @processes %></pre>

Ci siamo quasi. Tutto quello che dobbiamo fare è di modificare lievemente il route in modo tale che punti a quella specifica action:

/config/routes.rb

match "/processes" => ProcessesApp.action(:index)

Al ricaricamento del server, visitando la pagina dei processi, vedremo che la pagina ora utilizza il nostro template:

La pagina dei processi renderizzata col proprio template.

Come vi abbiamo fatto vedere, Rails 3 è alquanto modulare e ci permette di estrarre ed usare esattamente ciò di cui abbiamo bisogno da un controller in una applicazione Rack custom. Tenete a mente che scrivendo un controller in questo modo risparmierete se va bene alpiù pochi millisecondi per richiesta, per cui è il caso di creare un controller Rails normale per prima cosa e lanciare un po’ di test di performance su di esso prima di scrivere una applicazione Rack come abbiamo fatto in questo episodio.