homeASCIIcasts

193: Modelli non persistenti 

(view original Railscast)

Other translations: En Es Cn

Other formats:

Written by Andrea Salicetti

Una delle domande più frequenti che mi vengono poste è come creare una form in un’applicazione Rails senza che ci sia necessariamente una tabella sul database relativa al modello. Daremo risposta a questa domanda proprio in questo episodio e per farlo, useremo l’applicazione di blogging che abbiamo usato in un paio di episodi precedenti.

La nostra applicazione blog.

Ciò che vogliamo fare è aggiungere un collegamento “Share this article” su ogni pagina di articolo. Questo link porterà l’utente in una form su cui si potranno inserire le informazioni per poter inviare l’articolo via email. Non vogliamo mantenere queste informazioni, ma usarle solo per inviare in quel momento l’email. Quindi, come creiamo una form e un modello privi di corrispondente tabella sul database?

L’approccio che seguiremo sarà quello di creare un normale modello con una tabella di database associata e poi eseguire le modifiche all’applicazione per rimuovere la tabella. Cominciamo generando uno scaffold, usando il nifty scaffold generator di Ryan Bates.

Dobbiamo creare un nuovo modello dal momento che vogliamo fare il submit di una form e creare una nuova risorsa. Chiamiamo il nuovo modello Recommendation, dal momento che inviando dei dettagli di un articolo via email a qualcuno, stiamo fornendo una sorta di suggerimento a quel qualcuno. Il modello Recommendation avrà i campi necessari a contenere l’email del mittente e quella del destinatario, l’id dell’articolo che si sta suggerendo e un campo di testo per un messaggio. Il controller relativo dovrà avere le action new e create. Possiamo generare lo scaffold con:

script/generate nifty_scaffold recommendation from_email:string to_email:string article_id:integer message:text new create

Poi dobbiamo migrare il database per creare la tabella, anche se in realtà alla fine non vorremo più avere questa tabella (faremo il rollback di questa migrazione più tardi):

rake db:migrate

Ora che abbiamo il modello e un controller, creiamo un link alla action new nel RecommendationController, passandogli l’id dell’articolo che verrà suggerito:

<p>
  <%= link_to "Share this article", new_recommendation_path(:article_id => @article.id) %>
  <%= link_to "Back to Articles", articles_path %>
</p>

L’aggiunta del link di suggerimento a /app/views/articles/show.html.erb

Si noti che c’è bisogno di passare l’id dell’articolo dal link al nuovo Recommendation in modo che non si perda il riferimento.

Adesso spostiamoci sulla vista generata dallo scaffold e cambiamola per rimuovere il campo article_id e la sua etichetta e sostituiamola con un campo nascosto che mantenga l’article_id:

<% title "New Recommendation" %>
<% form_for @recommendation do |f| %>
  <%= f.error_messages %>
  <%= f.hidden_field :article_id %>
  <p>
    <%= f.label :from_email %><br />
    <%= f.text_field :from_email %>
  </p>
  <p>
    <%= f.label :to_email %><br />
    <%= f.text_field :to_email %>
  </p>
  <p>
    <%= f.label :message %><br />
    <%= f.text_area :message %>
  </p>
  <p><%= f.submit "Submit" %></p>
<% end %>
<p><%= link_to "Back to List", recommendations_path %></p>

Modifiche alla /app/views/recommendations/new.html.erb

Quando ricarichiamo la pagina dell’articolo ora, sarà visibile il link “Share this article” e quando gli clicchiamo sopra, vedremo la nuova form:

La nuova form di invio consiglio.

Se riempissimo la form e la inviassimo creeremmo un nuovo record nella tabella recommendations sul database, ma noi non vogliamo tutto ciò. Vorremmo solamente inviare l’email e basta. Nelle applicazioni Rails l’action create è tipicamente usata per salvare un nuovo modello sul database, ma non c’è nulla che ci costringa a farlo. Potremmo piuttosto verificare solamente che i dati sul nuovo modello Recommendation siano validi:

def create
  @recommendation = Recommendation.new(params[:recommendation])
  if @recommendation.valid?
    flash[:notice] = "Successfully created recommendation."
    redirect_to @recommendation
  else
    render :action => 'new'
  end
end

Ora possiamo fare il roll-back dell’ultima migrazione per eliminare la tabella recommendations e vedere se la nostra form funziona ugualmente senza di essa. Rimuoviamo anche il file di migrazione:

rm db/migrate/*_recommendations.rb

Senza la tabella sul database otteniamo un errore al ricaricamento della form. L’ applicazione si lamenta, guardacaso, perchè non trova la tabella recommendations. Questo dipende dal fatto che ActiveRecord assume che ogni modello abbia una tabella associata.

L’eccezione sollevata dopo che abbiamo rimosso la tabella.

Dunque, come possiamo fare per creare un modello che non abbia una tabella associata sul database?Ci sono diverse soluzioni potenziali, incluso l’utilizzo di vari plugin, ma noi seguiremo il metodo descritto in un post del blog Code Tunes. Quest’ultimo mostra una tecnica che prevede la ridefinizione di un paio di metodi in un modello ActiveRecord e la definizione manuale delle colonne nel file del modello, piuttosto che nella tabella del database. Nel nostro modello Recommendation ridefiniamo dunque i due metodi e poi usiamo il metodo di classe column per definire le colonne in un modo simile a come sono definite in un file di migrazione:

class Recommendation < ActiveRecord::Base
  def self.columns() @columns ||= []; end
 
  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end
  
  column :from_email, :string
  column :to_email, :string
  column :article_id, :string
  column :message, :text  
end

Definizione delle colonne nel /app/models/recommendation.rb

Al ricaricamento della pagina di nuovo suggerimento, ora vediamo nuovamente la form anzichè l’errore, ma le colonne ora sono definite direttamente nella classe del modello anzichè essere recuperate dalla corrispettiva tabella sul database.

Potreste domandarvi perchè la classe Recommendation erediti ancora da ActiveRecord::Base, dal momento che non stiamo usando più il database per questo modello. Potremmo facilmente definire una classe di modello che non si basi su ActiveRecord e che funzioni, dato che Rails è abbastanza disaccoppiato da ActiveRecord, ma ci sono una serie di vantaggi nel mantenere la nostra classe di modello specializzazione di ActiveRecord. Da un lato significa che possiamo ereditare gratis funzionalità tipo la validazione. Possiamo dunque validare il formato dell’email e la lunghezza del messaggio, appoggiandoci ai metodi definiti sull’ancestor ActiveRecord:

validates_format_of :from_email, :to_email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :message, :maximum => 500

Aggiunta delle validazioni a /app/models/recommendation.rb

Con l’aggiunta di queste validazioni vedremo gli stessi messaggi di errore che vedremmo se la classe fosse una normalissima classe di modello con tabella associata sul database, nel caso in cui i dati immessi siano scorretti.

Gli errori di validazione mostrati nel caso di indirizzo email errato.

Un altro motivo per cui conviene mantenere la discendenza da ActiveRecord per il nostro modello privo di tabella è che possiamo in tal modo continuare ad usare le associazioni. Recommendation ha un article_id come uno dei suoi campi, per cui possiamo continuare a dichiarare che:

belongs_to :article

nel modello, affinchè si possano continuare a portare dietro gli oggetti di tipo Article associati quando ce n’è bisogno.

Ora abbiamo un modello ActiveRecord che si comporta praticamente come tutti gli altri modelli, ma che non presuppone alcuna tabella sul backend, dal momento che definisce da sè le proprie colonne. Se accidentalmente chiamassimo un metodo che coinvolga il database e richieda una tabella sul database, otterremmo la stessa eccezione vista poco fa, che dice che la tabella non esiste, e potremmo rimediare puntualmente al problema.

L’unica parte dell’applicazione che rimane da sviluppare è il codice responsabile dell’invio email, una volta che la form di suggerimento è stata riempita e inviata correttamente al server. Non vogliamo scrivere quel codice in questo episodio, dal momento che andrebbe oltre i nostri propositi, ma se volete sapere come si fa, date un’occhiata all’episodio 61 che tratta proprio quell’argomento.

Anche se poter creare modelli non persistenti è una tecnica utile, dovreste chiedervi se davvero non vogliate salvare queste informazioni sul database. E’ così semplice salvare dati immessi dall’utente sul database, che ci deve essere una buona ragione per non farlo, anche se non vedete alcun uso immediato di tali dati. Se non altro, può servire come backup nel caso in cui il nostro sistema di email si rompa.