#222 Rack in Rails 3
- Download:
- source codeProject Files in Zip (99.9 KB)
- mp4Full Size H.264 Video (12.9 MB)
- m4vSmaller H.264 Video (8.73 MB)
- webmFull Size VP8 Video (21.4 MB)
- ogvFull Size Theora Video (16.2 MB)
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
:
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:
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.
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:
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.
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
:
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:
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:
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
:
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
:
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:
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:
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
:
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:
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:
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:
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:
<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:
match "/processes" => ProcessesApp.action(:index)
Al ricaricamento del server, visitando la pagina dei processi, vedremo che la pagina ora utilizza il nostro 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.