Ruby on Rails

Introduzione

Installazione di Ruby e Rails
Collegati a singularity con Visual Studio Code - Remote SSH

Uso di Screen

Utilizza l'utility screen per continuare a lavorare in caso di disconnessione:
Il comando si ricollega a una sessione (se esiste già), altrimenti ne crea una nuova.

Per uscire da una sessione, lasciando i comandi in esecuzione:
CTRL + A, e poi D (detach)

Per vedere una lista delle sessioni:


Per eliminare una sessione:

Installazione di NodeJS


curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
source ~/.bashrc
nvm -v
nvm install 21.5.0

Installazione di Ruby e Rails

Se non hai ancora installato ruby e rbenv:
cd /home/$USER
git clone https://github.com/rbenv/rbenv.git /home/$USER/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> /home/$USER/.bashrc
echo 'eval "$(rbenv init -)"' >> /home/$USER/.bashrc
exec $SHELL
git clone https://github.com/rbenv/ruby-build.git /home/$USER/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> /home/$USER/.bashrc
exec $SHELL

Nei passaggi successivi, se serve aggiornare ruby-build (la versione cercata non è disponibile) rbenv proporrà di farlo con questo comando:
git -C /home/NOMEUTENTE/.rbenv/plugins/ruby-build pull

rbenv install 3.3.0
rbenv global 3.3.0
rbenv local 3.3.0
rbenv rehash
git config --global color.ui true
gem install rails -v 7.0.8
rbenv rehash
ruby -v
rails -v
echo --- Installazione terminata ---

todo: rails 7.1.2 dava un problema con la configurazione del database

Copia tutti i comandi qui sopra e incollali in modo da eseguirli e installare nel tuo spazio personale sul server:
  1. rbenv: un'utility che consente di installare più versioni diverse di Ruby
  2. Ruby: il linguaggio di programmazione che useremo
  3. Rails: un framework basato su ruby che consente di creare applicazioni web

Ci vorrà un po' di tempo.
Controlla l'output dei singoli comandi e vedi se compare qualche errore.

Se tutto è andato a buon fine, dovresti vedere queste righe che mostrano la versione di Ruby e Rails installate:


Creare una nuova applicazione
1. Crea un repository privato (nell'esempio aprile5), senza file readme
2. Collegati con VS Code a singularity
3. Clona il repository e aprilo, anche nel terminale
4. Esci dalla cartella del progetto e vai nella tua home

Aggiorna bundler:
gem update bundler

5. crea la nuova applicazione:
rails _7.0.8_ new nome_della_tua_applicazione --css=bootstrap --javascript=esbuild --database=postgresql --force

Ricordati di cambiare il nome facendolo corrispondere a quello della tua cartella.
Nota che rails si occuperà di installare per te anche Bootstrap.

6. Ci vorrà un po' di tempo... al termine torna nella cartella dell'applicazione:

Avvertenza

Per semplificare la clonazione dell'applicazione non fare commit prima di aver eseguito le istruzioni .gitignore che trovi in fondo alla pagina

Preparazione del database

Dato che ogni utente ha un unico database, per ogni applicazione creeremo uno schema diverso, all'interno del tuo database:

Schema

Ricordi? Gli schema sono come cartelle all'interno del tuo database.

Ci servirà uno schema anche per i test:

Esci dalla console di psql con il comando
\q

Modifica il file di configurazione del database, in modo che l'applicazione si colleghi agli schema appena creati:



Setup dell'applicazione

Foreman si occupa di avviare il web server di sviluppo, e di compilare i file javascript e css necessari tenendo d'occhio (watch) le modifiche fatte al sorgente dal programmatore. Andiamo nel suo script di configurazione e cambiamo la porta:


Verifica che il file package.json contenga un oggetto scripts simile al seguente... in caso contrario aggiungi le righe mancanti:
{
  "name": "app",
  "private": "true",
  "dependencies": {
    "@hotwired/stimulus": "^3.2.2",
    "@hotwired/turbo-rails": "^8.0.0-beta.2",
    "@popperjs/core": "^2.11.8",
    "autoprefixer": "^10.4.16",
    "bootstrap": "^5.3.2",
    "bootstrap-icons": "^1.11.2",
    "esbuild": "^0.19.10",
    "nodemon": "^3.0.2",
    "postcss": "^8.4.32",
    "postcss-cli": "^11.0.0",
    "sass": "^1.69.5"
  },
  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets",
    "build:css:compile": "sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules",
    "build:css:prefix": "postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css",
    "build:css": "yarn build:css:compile && yarn build:css:prefix",
    "watch:css": "nodemon --watch ./app/assets/stylesheets/ --ext scss --exec \"yarn build:css\""
  },
  "browserslist": [
    "defaults"
  ]
}


Avviamo il server di sviluppo:

Apriamo l'applicazione nel browser:

.gitignore

Per semplificare la clonazione di un'applicazione da parte di un tuo compagno, prima ancora di fare dei commit aggiungi questi due file alla lista di quelli che git dovrà ignorare:

/config/database.yml
/Procfile.dev

In questo modo ognuno potrà mantenere la sua propria copia di questi file di configurazione.

Per praticità togli da .gitignore la master.key, anche se questa non è assolutamente una buona prassi.

Crea una copia dei due file per la distribuzione, denominandoli:
/config/database.yml.dist
/Procfile.dev.dist

Ricordati di cancellare eventuali password dalla copia che sarà distribuita.

Clonare un'applicazione esistente

Clona il repository da GitHub come di consueto.
Personalizza i due file
/config/database.yml
/Procfile.yml
partendo dalle copie .dist

Prepara i nuovi schema nel database (se non ricordi come fare guarda qui).

Avvertenza

Non usare il segno - nel nome degli schema: non sarà accettato.
Per lo stesso motivo usa il segno _ solo per aggiungere il suffisso _test al secondo schema.

Esegui il setup dell'applicazione con
bin/setup
bundle install
yarn install


Creare una pagina

L'architettura MVC

Rails usa un'architettura di tipo Model - View - Controller:

Vogliamo creare una semplice pagina HTML di benvenuto.
Per fare questo avremo bisogno di:
  • una rotta: prende la richiesta dell'utente e la indirizza al controller
  • un controller: contiene una serie di azioni (nel nostro caso solo l'azione index)
  • un'azione: svolge le operazioni logiche necessarie (nel nostro caso si occupa solo di mostrare la vista)
  • una vista (prende i dati ricevuti dal controller e assembla l'HTML da inviare al browser dell'utente)

Il controller

Creiamo un controller Home:
Vengono creati:
  • un file per il controller (app/controllers/home_controller.rb)
  • una cartella per le sue viste (app/views/home)

 Definiamo l'azione index nel controller:

La vista

creiamo la vista corrispondente:


La rotta

Configuriamo una rotta che permetta all'utente di raggiungere l'azione che abbiamo creato qui sopra:

Come funziona la rotta

Quando l'utente richiede la pagina /home
la sua richiesta viene inoltrata a home#index (controller home, azione index)

Possiamo ora visitarla nel browser:
 

La rotta di default

Possiamo fare in modo che quando l'utente non specifica una particolare pagina, venga aperta la nostra pagina di benvenuto: stiamo di fatto creando la home page dell'applicazione:

Adesso la pagina che abbiamo creato è raggiungibile sia quando l'utente scrive l'indirizzo
http://127.0.0.1:3058/home/

 che quando scrive semplicemente
http://127.0.0.1:3058/


Denominare le rotte

Può essere utile dare un nome alle rotte più importanti:


Creare un link
Dentro a una vista possiamo creare un link a una delle pagine dell'applicazione in uno di questi modi:

L'helper (aiutante) link_to vuole come parametri una stringa e una rotta, e prepara per te un tag di questo tipo:

Nota che possiamo scrivere la rotta per esteso oppure - se è una rotta per la quale abbiamo definito un nome - usare il suo nome come nell'esempio: home_path

Puoi usare
home_path: genera un link relativo del tipo "/home"
home_url: genera un link assoluto del tipo "https://localhost:3058/home"

Naturalmente è possibile creare anche link esterni all'applicazione come in questo esempio:

Bootstrap

Avendo creato l'applicazione con l'opzione --css=bootstrap avrai a disposizione tutte le classi a cui sei abituato, ad esempio:

Visualizzare un'immagine

File pubblici

Rails è progettato per creare applicazioni sicure, quindi normalmente tutti i file con il codice sorgente non sono visibili all'utente.
Esiste però una cartella /public che sarà completamente visibile agli utenti, e può essere utile per contenere immagini o documenti da far scaricare all'utente.

Crea la cartella /public/images
e salva al suo interno un'immagine profilo.png

Per trasferire dei file nel server remoto via SSH è sufficiente trascinarli dal tuo desktop alla cartella di destinazione su Visual Studio Code.

Ora puoi visualizzare l'immagine all'interno di una vista usando l'helper image_tag:

Puoi applicare all'immagine gli stili CSS che vuoi usando l'attributo style:



Meglio ancora, se hai definito una classe CSS...

puoi applicarla così:

Bootstrap

Avendo creato l'applicazione con l'opzione --css=bootstrap potrai usare senza fatica tutte le classi definite da Bootstrap!


I partial
È possibile rendere modulare la creazione delle viste suddividendole in file parziali (partial).

Creiamo ad esempio un partial che si occupi di visualizzare una card di Bootstrap:
Nota: il nome dei partial inizia sempre col simbolo _

Possiamo chiedere a Rails di visualizzare un partial usando il metodo render:
Nota: quando il partial viene richiamato non serve mettere il simbolo _ e neanche l'estensione del file.
Inoltre se si fa uso di un partial che è nella stessa cartella della vista che lo richiama, non serve neanche specificare un percorso.

Possiamo riutilizzare il partial per creare diverse card passando a ognuna i dati necessari:

All'interno del partial possiamo poi utilizzare le singole variabili locali per personalizzare la card:

Il risultato è questo:

Definire un layout
È possibile fare in modo che tutte le pagine dell'applicazione condividano lo stesso aspetto andando a modificare il file:

Il CSS e il JavaScript di Bootstrap sono già caricati dalle righe 9 e 10.

La riga 14 si occupa di inserire in quel punto i contenuti della singola pagina.

Potremmo allora andare a inserire in tutte le pagine una navbar e un footer importando i relativi partial:
Dobbiamo specificare che i partial sono nella cartella layouts perché dovendo apparire in tutte le pagine saranno richiamati da diversi controller, come ad esempio Home. Se non specificassimo la cartella, rails andrebbe a cercare il partial in views/home/navbar.

Crea allora questi partial inserendo il codice di una navbar e un footer a tuo piacere:

Probabilmente anche tu noterai un disallineamento di questo tipo:

...è perché dobbiamo mettere il contenuto di ogni pagina in un container.
Potremmo farlo nel layout dell'intera applicazione in questo modo, ed evitare di farlo in ogni pagina...

...ma se in qualche pagina volessimo inserire un container fluido che si adatti all'intera larghezza dello schermo, avremmo qualche problema.
Meglio allora inserire il tipo di container scelto dentro a ogni singola vista, e al suo interno mettere i contenuti della pagina, come in questo esempio:
Nota: una pagina può contenere più container anche di tipi diversi... ad esempio uno fluido per il carousel, uno per le card con i prodotti, e così via.
Inoltre puoi usare tutto quello che conosci della griglia di Bootstrap per disegnare layout con più colonne:

Le tre colonne di questo esempio sono ottenute inserendo tre partial "card" dentro a tre col, a loro volta contenute in una row:

Creare un Model
Finora abbiamo visto i Controller e le View dell'architettura MVC:
Se un'applicazione deve salvare dei dati in un database abbiamo bisogno anche del Model.

Il Model è un oggetto ruby che rappresenta un certo tipo di dati memorizzati nel database... potremmo ad esempio avere un model per le schede dei clienti, uno per i prodotti, uno per gli acquisti...
In questo modo potremo lavorare con i dati memorizzati nel database scrivendo in linguaggio Ruby, e non direttamente in SQL. Il vantaggio è che se un giorno vorremo cambiare tipo di database (salvando ad esempio i dati nel cloud AWS di Amazon) non avremo bisogno di modificare la nostra applicazione.

Cosa vogliamo fare

Per capire come funziona il Model, creiamone uno molto semplice: lo chiameremo Like e dovrà memorizzare il numero di "mi piace" che sono stati fatti in una certa pagina.
Avremo bisogno quindi di due dati:
  • il nome della pagina: una stringa di testo
  • il numero di click: un numero intero, inizialmente uguale a zero

Creazione del model

Per creare il model Like scriviamo questa riga nella console:

Rails genera per noi alcuni file... andiamo a modificare quello della migrazione, aggiungendo un valore iniziale (di default) per il numero di click:

Le migrazioni

Le migrazioni sono modifiche alla struttura del database: il loro nome contiene la data e l'ora in cui sono state create. Esse vengono poi applicate in ordine ai vari server contenenti i database... in questo modo se un'applicazione è distribuita sarà sempre possibile fare in modo ordinato gli aggiornamenti.

Per applicare la migrazione scriviamo:
A questo punto il database è stato aggiornato.

Rollback

È anche possibile fare delle operazioni di rollback, e cioè di annullamento di una certa migrazione, con il comando:

rails db:rollback

La console di Rails

Usando il comando:
rails c

è possibile entrare nella console di Rails:

Da qui possiamo creare un primo record per il nostro modello Like, andando a preparare lo spazio dove memorizzare i click della home page:

Come vedi il model converte il tuo comando molto semplice in una query SQL più complessa, che va a inserire il dato nel database.

Per uscire della console usa il comando:

quit

Leggere i dati dal Model

Andiamo a modificare l'azione index del controller Home:

La riga inserita:
  • prende il modello Like
  • lo usa per trovare (find) il record relativo alla pagina 'home'
  • estrae dal record il numero di click
  • memorizza questo valore nella variabile @like

Variabili d'istanza

In Ruby le variabili precedute da una @ sono dette variabili d'istanza, e Rails le usa per rendere disponibili alle viste le variabili definite nel controller.

Visualizzare il dato nella vista

Inseriamo un bottone con il numero di like nella vista index:
Nota come il numero di like viene scritto da un frammento di codice Ruby che legge la variabile @like definita nel controller.

Modificare il dato

Iniziamo ora a contare i click. Per prima cosa aggiungiamo un'azione click al controller Home:

Nota: mettere questa nuova azione nel controller Home non sarebbe la scelta migliore se volessimo contare i like di tutta l'applicazione, ma per ora non complichiamo le cose.

Come nell'azione index, l'azione click per prima cosa legge il Like relativo alla pagina home...
...incrementa il numero di click...
...salva l'oggetto Like nel database...
...e infine fa un redirect alla rotta home (in questo modo non avremo bisogno di definire una vista per questa azione, perché dopo aver fatto il suo lavoro redirigerà il browser dell'utente alla home page.

Definiamo una rotta per questa azione:

Potremo quindi usare il nome della rotta click per far funzionare il nostro link:

Adesso potrai cliccare il tuo bottone andando ad incrementare il numero di like a ogni click:

Passaggio di parametri alle rotte
La nostra action "click" per ora è in grado di contare solo i click fatti sulla pagina home.
Ci serve un modo per indicarle da quale pagina è venuta la richiesta.
Andiamo per prima cosa a modificare la rotta, informando Rails che l'azione click si aspetta un parametro denominato pagina:

L'action riuscirà a leggere il parametro prendendolo dall'array params:

...e conteggerà così il numero di click per la pagina corrispondente.

Dovremo però modificare anche il link presente nel bottone, in modo che passi come parametro il nome della pagina:
Oltre ai parametri espliciti definiti nelle rotte, è possibile passare dei parametri aggiuntivi... ad esempio:

genera un link di questo tipo:
http://127.0.0.1:3058/click/home?tema=scuro

e il valore del tema può essere letto dal controller scrivendo semplicemente params[:tema]


Connessione remota

Vogliamo adesso usare DBeaver per collegarci al nostro database PostgreSQL: scarica l'applicazione dal sito ufficiale
https://dbeaver.io/

Scegliere una password per PostgreSQL

Normalmente ci colleghiamo alla console di PostgreSQL con l'utente Linux che usiamo su Singularity.
Per collegarci in remoto però dobbiamo scegliere un'ulteriore password.
Collegati a Singularity con SSH e accedi alla console con
psql

Imposta la password per la connessione remota del tuo utente personalizzando questa query con il tuo nome utente e la password scelta:

ALTER USER "a.ros" ENCRYPTED PASSWORD '12345';

Tunnel SSH con DBeaver

Ora passa a DBeaver e crea una nuova connessione di tipo PostgreSQL (probabilmente ti sarà chiesto di scaricare il driver corrispondente).
Per prima cosa configura un tunnel SSH dalla relativa scheda:

 NAT della porta

La traslazione della porta da 5432 a 5433 normalmente non è necessaria, a meno che tu non abbia un server PostgreSQL anche sulla tua macchina di sviluppo.
Non serve quindi che tu modifichi le impostazioni avanzate.

Al termine premi "Test tunnel configuration" e verifica se il tunnel funziona:

Passa ora alla scheda principale e configura la porta (se non usi quella di default), il nome del database (che corrisponde al tuo username su Singularity), il nome utente e la password che hai scelto nel passaggio precedente.

Verifica che la connessione funzioni:

Esplorazione del database

Divertiti ora a esplorare il tuo database, vedendo quali schema hai creato e quali tabelle sono presenti al loro interno:
Ecco ad esempio il contenuto della tabella che Rails ha creato per il model Like:
Con DBeaver oltre a visualizzare i dati presenti nel tuo database, puoi anche modificarli a tuo piacere!
Avendo un lungo elenco di prodotti ad esempio, potresti trovarti meglio a fare delle modifiche sulla tabella di DBaver piuttosto che aprendo le singole schede dei prodotti dall'applicazione.

Scaffold

Vogliamo gestire le schede dei libri di una biblioteca memorizzando:
  • titolo: string
  • autore: string
  • anno: integer
  • introduzione: text
  • disponibile: boolean

Chiediamo a Rails di creare l'impalcatura (scaffold) per un model Book (per i model scegliamo sempre un nome in inglese, con l'iniziale maiuscola):

Avviso salvaesame

 Dedica tutto il tempo che serve a ricontrollare questo comando e a ripensare se hai inserito tutti i campi che ti servono, perché una volta avviato Rails procederà a creare tutta una serie di file che sarà difficile poi modificare manualmente.

Nel caso volessi eliminare lo scaffold appena creato, puoi farlo con il comando
rails destroy scaffold Book

Viene creata una migrazione, cioè una modifica alla struttura del database dell'applicazione:

Per applicare questa modifica al database diamo il comando:

Viene creato un model che ci permetterà di interagire col database da Rails, senza bisogno di scrivere query SQL:

Viene creato un controller con una serie di azioni:

  • index: lista dei libri
  • show: mostra un libro
  • new: mostra il form per creare un nuovo libro
  • create: crea un nuovo libro prendendo i dati dal form
  • edit: mostra il form per modificare un libro esistente
  • update: modifica un libro esistente prendendo i dati dal form
  • destroy: elimina un libro


Viene aggiunta una rotta, di tipo "resources", che in realtà è un insieme di rotte, una per ognuna delle azioni viste sopra:

Viene creata una view per ogni azione:
 _form è un "partial", usato sia dalla vista new che dalla vista edit:
Potrai raggiungere le pagine appena create dal percorso:
http://localhost:porta/books
Proviamo l'applicazione inserendo qualche libro:
Possiamo personalizzare le viste aggiungendo dei container, traducendole in italiano e inserendo le classi CSS di Bootstrap:
https://getbootstrap.com/docs/5.3/forms/overview/

Upload di immagini
Attiviamo active_storage, che permette di salvare dei file nell'applicazione:

rails active_storage:install

Viene preparata una migrazione che possiamo applicare al database con:

rails db:migrate
 

Aggiungiamo un allegato "copertina" al model del libro:

Nel controller permettiamo il passaggio del parametro :cover
Aggiungiamo un campo di tipo file_field al form:
...e visualizziamo l'immagine nel partial _book, verificando con un if se essa è presente:
Dato che il partial _book viene usato sia da index che da show, assegnamo all'immagine la classe w-100 di Bootstrap che estenderà l'immagine all'intera larghezza del suo contenitore (ad esempio una colonna o una card nell'indice), limitandola però a un massimo di 300 pixel con l'attributo style, in modo che quando essa viene visualizzata nella vista show non si allarghi troppo.

Autenticazione con Devise
L'autenticazione consiste nella verifica dell'identità dell'utente.
La aggiungeremo alla nostra applicazione sfruttando la gem "devise"... inseriscila in fondo al Gemfile:

Installala quindi con
bundle install

Se l'installazione è andata buon fine prepara la configurazione con
rails g devise:install

Segui quindi le indicazioni che saranno visualizzate. In particolare...

Configurare i link delle email inviate da devise

Ricordati di cambiare nell'esempio qui sopra il numero di porta.

 

Definire una rotta "root"



Fare in modo che le notifiche siano visibili in tutte le pagine

Generare il model per gli utenti

rails g devise User

Con questo comando chiediamo a devise di generare per noi una migrazione che creerà un nuovo model, denominato User, che conterrà nome utente e password di tutti gli utenti, oltre a eventuali dati aggiuntivi.

Possiamo aggiungere alla migrazione un campo booleano che specifica se un utente è amministratore (false di default!):


Applichiamo la migrazione:
rails db:migrate

Preparare l'uso con Turbo

Decommenta e modifica queste righe dell'initializer di devise, che ci permetteranno di integrare devise con le funzionalità avanzate di Rails 7:

Predisporre uno scaffold per gestire gli utenti

rails g scaffold_controller User email:string admin:boolean


Le rotte generate da devise_for :users devono apparire prima di quelle generate dallo scaffold (resources :users), o si verificherà un problema richiedendo l'autenticazione per il controller Users:

Definire un'area riservata

Richiedi che l'utente sia autenticato prima di accedere al controller della tua area riservata:
Nota che l'autenticazione sarà richiesta per tutte le azioni di questo controller, eccetto show: in questo modo anche gli utenti non loggati avranno accesso alle schede dei libri.

Tradurre i messaggi di devise

Basta creare un file config/locales/devise.it.yml
e inserire le traduzioni, che si trovano qui:
https://gist.github.com/iwan/91c724774594c8b484c95ff1db5d1a15

Personalizzare le viste di devise

Copia le viste di devise in modo da poterle personalizzare in un secondo momento:
rails g devise:views

Troverai in questa la cartella le varie viste da personalizzare:

Esse si riferiscono ai vari casi:
  • conferma di un utente
  • invio di email all'utente
  • modifica della password
  • registrazione di un nuovo utente
  • login dell'utente
  • sblocco dell'utente

La cartella shared contiene un partial con i link che compaiono in tutte queste pagine.

Riavviare Foreman

Dato che abbiamo aggiunto un initializer, che è un componente che viene usato all'avvio dell'applicazione, riavvia Foreman in modo che possa utilizzarlo.

Prova ora ad accedere all'area riservata da
http://127.0.0.1:3058/books

Ti sarà richiesto di loggarti... se non hai ancora un utente dovrai crearlo.

Ottenere l'utente corrente

Puoi ottenere i dati dell'utente corrente usando l'oggetto current_user in una qualunque delle tue viste:
Nota che visualizziamo il benvenuto solo se l'utente è loggato (user_signed_in).
 

Creare un link per il logout

Dopo aver verificato se l'utente è loggato (user_signed_in?) puoi creare un link per il logout, magari nella navbar:
 

Approfondimento

Solo per curiosità personale: se volessi far inviare le email di conferma di devise attraverso la tua casella Gmail...
 Dalla configurazione della tua casella poi dovresti abilitare l'accesso IMAP: