Google: la frequenza di rimbalzo collassa. Parte 2

Avevamo visto nel post precedente che un problema di configurazione aveveva fatto precipitare improvvisamente la frequenza di rimbalzo.

Per accorgerci di dove stia il problema avevo due strade, una un po’ più complicata e una semplice.

Frequenza di rimabalzo quasi a 0: che è successo?

Come ho già detto prima, il problema è la doppia invocazione di Google Analyitcs che viene scambiata per due pagine visitate, per cui sparivano i rimbalzi come per incanto.

Come prima cosa ho cercato sul web e ho trovato questa risorsa web che dava come suggerimento l’installazione di un plugin che controlla le informazioni scambiate dalla pagina con Analytics.

Il plugin si chiama Google Tag Assistant. L’ho installato su Chrome e in effetti mi ha segnalato che c’era questa doppia invocazione.

Un modo più semplice di accorgersene, senza installare altri plugin, è semplicemente quello di leggersi il sorgente HTML della pagina web (Ctrl-U in Chrome):

<script type='text/javascript' id='google_gtagjs-js-after'>
window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-28350766-1');
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-28350766-1');
</script>

Scorrendo la pagina fino a dove si trovano i codici di Google (si cerca “UA-“) ho individuato il punto in cui c’era il codice di tracciamento ripetuto perché ogni plugin aggiungeva le stesse identiche istruzioni, che sono quelle che Google si aspetta.

Bastava toglierne uno, e quindi dovevo disinstallare uno dei due plugin di Google. Ho tenuto SiteKit e buttato l’altro.

A distanza di due giorni la situazione della frequenza di rimbalzo è tornata alla normalità come testimonia il grafico di Google Analytics:

La frequenza di rimbalzo risale
La frequenza di rimbalzo risale

Aul fatto che la frequenza di rimbalzo sia così alta be’… è un po’ quello che faccio anch’io con i siti web: mordo e fuggo. Ad esempio, il sito in cui ho trovato il suggerimento del Tag Assistant ha avuto da me esattamente un rimbalzo: ho trovato la risposta e me ne sono andato.

È un comportamento che un po’ tutti teniamo qualora si usi il web per trovare risposte alle domande.

È tutto.

Google: la frequenza di rimbalzo collassa. Che succede?

Google Analytics - la frequenza di rimbalzo collassa istantaneamente. Che sta succedendo?
Google Analytics

Mi è successa una cosa.

Un giorno mi accorgo che da un momento all’altro la frequenza di rimbalzo (bounce rate) del mio sito è passata da attorno al 90% allo 0.

Sì non è un sito che fa tutta sta audience… ma a me serve solo per studiare. E un po’ per farmi conoscere.

Frequenza di rimbalzo: il contesto

Di cosa stiamo parlando? La frequenza di rimbalzo è uno degli indici di bontà calcolati da Google Analytics.

Questo indice misura la percentuale di utenti che visitano una sola pagina del nostro sito, proveniendo da una ricerca di Google.

Come una palla da tennis, atterrano sul nostro sito e se ne vanno.

Non è molto confortante; magari siamo riusciti ad ottenere un buon rank sui motori di ricerca grazie ad una buona ottimizzazione SEO e la nostra pagina è cliccabile nella prima pagina di risultati, però il nostro sito non è in grado di catturare sufficiente attenzione dell’utente per convincerlo a rimanere e a visitare almeno un altra pagina o leggere un altro post.

Più sono le sessioni che generano una sola page view e più alta è la frequenza di rimbalzo. Quindi avere un’alta frequenza di rimbalzo è di per sé un indice indesiderabile.

Sarebbe bello che fosse bassa!

Che bello, improvvisamente la frequenza di rimbalzo è quasi a zero!

Eh… non è detto che sia sempre una buona notizia. Soprattutto quando il calo è improvviso. Una diminuzione graduale è più compatibile con il progressivo aumento dell’interesse che mano a mano cattura persone che passano nel nostro sito sempre più tempo e visitano più di una pagina (in media).

Frequenza di rimbalzo precipita
Drop della Frequenza di rimbalzo

Una diminuzione drastica nel giro di qualche giorno generalmente nasconde un problema tecnico abbastanza serio che mette a repentaglio l’attendibilità dei dati elaborati da Analytics.

Troppi plugin per gestire Google Analytics

Ho aggiunto un plugn molto figo per WordPress che si chiama Google Site Kit.

Fornisce 4 dashboard per 4 prodotti di Google come mostrato dalla figura seguente:

Site kit per studiare la frequenza di rimbalzo
Site kit per studiare la frequenza di rimbalzo

Ho inserito nelle impostazioni l’ID per collegare il mio sito a Google Anaytics (UA-XXXX…).

Sfortunatamente molto tempo fa avevo anche installato un plugin Google Analyticator per WordPress che in sostanza faceva lo stesso lavoro (senza però fornirmi i cruscotti consultabili in un solo posto)

Praticamente la compresenza di questi plugin duplicava le chiamate ad Analytics per ogni click, risultando in almento due pagine visitate per ogni click, quindi facendo praticamente sparire i rimbalzi.

Questo ha fatto collassare il bounce rate!

Ah va be’, era facile!

Be’, facile… come al solito il diavolo si nasconde nei dettagli. La parte difficile era capire che era proprio questo il problema 🙂

Come l’ho scovato?

Stay tuned per la seconda parte, coming soon!

Riconoscimento e sintesi vocale con Python

Riconoscimento e sintesi vocale con Python
Riconoscimento e sintesi vocale con Python

Riconoscimento e sintesi vocale: In questo articolo parlerò di due bellissime funzionalità di Python che non è nemmeno difficile testare: il riconoscimento vocale (speech recognizing) e la sintesi vocale.

Ho preso ispirazione dal sito copyassignment.com e mi sono un attimo studiato le librerie.

Analisi vocale (riconoscimento)

Il primo aspetto comprende i metodi con i quali viene analizzato il segnale audio.

Il parlato è un processo stocastico particolare nell’insieme dei suoni che ci circondano che ha delle caratteristiche peculiari. Vengono individuate all’interno del segnale audio cioè le caratteristiche come la frequenza, la presenza di determinati pattern:

  • segnali periodici, come le vocali o le mute “m”, “n”;
  • segnali aperiodici, come il suono “f” o “s” che assomigliano al rumore bianco
  • segnali “esplosivi” come le lettere “p”, “t” ecc.

e la suddivisione del segnale del parlato in fonemi. In sostanza analisi significa estrarre l’informazione dal parlato (analogico), ovvero riconoscere i fonemi e ricomporli per associarli a parole di una frase per poterla maneggiare come informazione di testo (digitale).

L’analisi vocale è basata sulla libreria Python speech_recognition che mette a disposizione una serie di API per l’analisi.

Sintesi vocale

La sintesi, viceversa, è la costruzione del segnale sonoro (analogico) del parlato a partire da una stringa di testo (digitale). Come assemblare gruppi di consonanti e di vocali per produrre i fonemi che compongono a loro volta le parole. La sintesi vocale è fornita dalla libreria pyttsx3.

pyttsx3 è una libreria di conversione “da testo a parlato” scritta in Python. A differenza di librerie alternative, può funzionare senza essere collegati a Internet ed è compatibile sia con Python 2 che con Python 3.

Prima di avventurarci su riconoscimento e sintesi vocale

Potreste anche direttamente andare nel sito di copyassignment.com se volete, ma qui posso risolvervi un paio di piccoli grattacapi dovuti alla mancanza di configurazione necessaria per eseguire questo programma.

Si tratta di farsi una minima idea del complesso di librerie che ci è nessario per passare al microfono al programma e dal programma agli altoparlanti.

Vi predigerisco già la pappa e vi illustro qual è lo stack su cui si basa il programma che in sostanza ci servirà per spegnere il computer:

sd.py  <----- il nostro programma
---------------------------------
speech_assignment / pyttsx3
---------------------------------
espeak                        (x)
---------------------------------
pyaudio
---------------------------------
PortAudio19                   (x)
---------------------------------
hardware (mike/speakers)
---------------------------------

L’ordine delle operazioni come ve lo presenta CopyAssignment.com è il seguente dove però non capiamo cosa stiamo facendo:

$ pip install pyaudio
$ pip install speechrecognition
$ pip install pyttsx3

Procedendo in quest’ordine però è molto probabile che vi troverete qualche gatta da pelare, come vediamo tra poco. Con (x) nello stack sopra ho contrassegnato le installazioni non citate da CopyAssignment.com

Installazione di pyaudio

marcob@jsbach:~$ pip install pyaudio

Se vi dovesse uscire questo errore

Building wheels for collected packages: pyaudio
   Building wheel for pyaudio (setup.py) 
   ERROR: Command errored out with exit status 1:
    ...

significa che non è installato l’header file portaudio.h, che è contenuto nel package portaudio19-dev. Dobbiamo pertanto installarlo preventivamente.

Installazione di PortAudio19 (x)

marcob@jsbach:~$ sudo apt-get install portaudio19-dev

PortAudio è una libreria per I/O audio portabile progettata per la gestione multipiattaforma dell’audio. Dell’audio in generale, intendiamo. non solo la voce umana.

È la libreria che permette di generare suoni e di leggere dalla periferica del microfono e convertire il segnale analogico in digitale. È la libreria a più basso livello di cui abbiamo bisogno.

Usa un meccanismo di callback per richiedere l’elaborazione audio. L’audio può essere generato in svariati formati, inclusa virgola mobile a 32 bit, e sarà convertito internamente nel formato nativo.

Riprovando a questo punto, a me è terminata con successo l’installazione di pyaudio; posso procedere con il package dell’analisi vocale.

Installazione di speech_recognition

arcob@jsbach:~$ pip install speechrecognition
Collecting speechrecognition
  Downloading SpeechRecognition-3.8.1-py2.py3-none-any.whl (32.8 MB)
     |████████████████████████████████| 32.8 MB 43 kB/s 
Installing collected packages: speechrecognition
Successfully installed speechrecognition-3.8.1

A questo punto si deve installare con pip il package Python che per la produzione del parlato

Installazione di pyttsx3

marcob@jsbach:~$ pip install pyttsx3
Collecting pyttsx3
  Downloading pyttsx3-2.90-py3-none-any.whl (39 kB)
Installing collected packages: pyttsx3
Successfully installed pyttsx3-2.90

Anche questo è andato bene. Questo è il sorgente del programma che potete trovare in CopyAssignment e che ho salvato in un file sd.py, modificando in italiano le stringhe di colloquio con il computer: lo posiamo avviare

# Importing required modules
# importing pyttsx3
import pyttsx3
# importing speech_recognition
import speech_recognition as sr
# importing os module
import os


# creating take_commands() function which
# can take some audio, Recognize and return
# if there are not any errors
def take_commands():
    # initializing speech_recognition
    r = sr.Recognizer()
    # opening physical microphone of computer
    with sr.Microphone() as source:
        print('Ascolto...')
        r.pause_threshold = 0.7
        # storing audio/sound to audio variable
        audio = r.listen(source)
        try:
            print("Riconoscimento")
            # Recognizing audio using google api
            Query = r.recognize_google(audio)
            print("Questo è ciò che ho capito='", Query, "'")
        except Exception as e:
            print(e)
            print("Prova a ripetere")
            # returning none if there are errors
            return "None"
    # returning audio as text
    import time
    time.sleep(2)
    return Query


# creating Speak() function to giving Speaking power
# to our voice assistant
def Speak(audio):
    # initializing pyttsx3 module
    engine = pyttsx3.init()
    voice_id = 'italian'
    engine.setProperty('voice', voice_id)
    # anything we pass inside engine.say(),
    # will be spoken by our voice assistant
    engine.say(audio)
    engine.runAndWait()

Speak("Vuoi spegnere il computer, Marco?")

while True:
    command = take_commands()
    if "no" in command:
        Speak("Ok Marco, lascio acceso")
        break
    if "yes" in command:
        # Shutting down
        Speak("Spengo")
        os.system("shutdown -h now")
        break
    Speak("Puoi ripetere? grazie")

Salvo il tutto in un file sd.py e provo ad eseguirlo

marcob@jsbach:~$ python3 sd.py
 OSError: libespeak.so.1: cannot open shared object file: No such file or directory

Questo è un problema che si situa ad un livello intermedio tra portaudio19 e Python (speech_assignment / pyttsx3): non risulta infatti installata nel sistema la libreria per il riconoscimento e la sintesi vocale che saranno invocate dalle librerie Python: espeak.

Installazione di espeak (x)

eSpeak è un sintetizzatore vocale software open source compatto per inglese e altre lingue, per Linux e Windows.
eSpeak utilizza un metodo di “sintesi formante”. Ossia costruisce i suoni in base ad un modello fisico dell’apparato fonatorio umano e utilizza la sovrapposizione di segnali sinusoidali, ovvero le serie di Fourier, per determinare il contenuto armonico del segnale.

Ciò consente di fornire molte lingue in dimensioni ridotte. Il parlato prodotto è chiaro e può essere utilizzato ad alta velocità, ma non è così naturale o fluido come sintetizzatori più grandi basati su registrazioni vocali umane. Ricorda un po’ i primi vocoder anni ’80. Se riprodotti su un impianto HiFi anche discreto, il suono risulta un po’ meno “cibernetico”.

marcob@jsbach:~$ sudo apt install espeak-ng-espeak
Lettura elenco dei pacchetti... Fatto
...
 Elaborazione dei trigger per doc-base (0.10.9)...
 Elaborazione 1 file doc-base aggiunto...
marcob@jsbach:~$

A questo punto manca solo il componente che collega python a espeak:

marcob@jsbach:~$ sudo apt install python3-espeak

A questo punto possiamo testare il programma.

Proprietà della libreria speech_recognition

Di questa libreria noi utilizzeremo in particolare la libreria Google di riconoscimento dei fonemi, il metodo recognize_google della classe Recognizer del package speech_recognition (sr).

L’unico parametro che viene impostato è

pause_threshold

ossia la soglia di silenzio che permette di separare le parole. Viene impostato a 0.7.

La variabile audio contiene un file mp3 che viene scritto dal metodo listen() della classe sr.Microhpone().

Alla fine il metodo magico che trasforma il suono (audio) ìn stringa (Query) è il seguente

Query = r.recognize_google(audio)

La classe è scritta bene, rende l’idea di cosa il programma stia facendo e con quali periferiche stia interagendo.

Proprietà della libreria pyttsx3

Questa componente trasforma la stringa in parlato ed è utilizzata per l’interfaccia human computer di interrogazione delle direttive dell’operatore. Ho selezionato il parlato in italiano (tra i molti disponibili):

voice_id = 'italian'
engine.setProperty('voice', voice_id)

e finalmente viene invocato il metodo che produce il suono:

engine.say(audio)
engine.runAndWait()

Se vi è piaciuto, mettete un like e/o ricondividetelo nel vostro social network preferito.

Laravel – modifiche ad un tracciato dati in una dashboard di dati Covid-19: come gestirle

Laravel vs SARS-Cov-2
Laravel vs SARS-Cov-2
Immagine del virus SARS-Cov-2 al

Nella gestione dei dati della pandemia da Covid-19, nel corso del tempo è successo che i gestori del flusso dati della Protezione Civile per ragioni di miglioramento della interpretazione edlla situazione, integrassero le colonne con nuove dimensioni. Ad esempio dal 18 gennaio 2021 sono stati introdotti dimensioni come il numero di tamponi rapidi, per distinguerli dai tamponi con test molecolare.

Questa modifica impatta nella base dati locale, che si maniene aggiornata quotidianamente, con l’aggiunta di una colonna ulteriore nel tracciato CSV reperibile nel repository pubblico ufficiale della Protezione Civile ospitato su Github, colonna che deve essere ospitata in una tabella relazionale che rispecchia la struttura del CSV.

Come adeguare il modello MVC al nuovo tracciato? Laravel consente di farlo in modo molto veloce.

Modifica al modello Laravel e alla tabella database

Si crea innanziatutto una nuova migrazione vuota:

$ php artisan make:migration tab_dati_nazionali_add_tampone_rapido

Il nome della migrazione è a piacere e deve ricordare l’operazione che svolgerà. Non serve che sia il nome esatto del campo (sono nomi lunghi e preferisco abbreviare).

Un nuovo file per la migrazione verrà quidi creato sotto la directory

app/database/migrations
-rw-rw-r-- 1 marcob marcob 822 gen 22 10:51 2021_01_22_095107_tab_dati_nazionali_add_tampone_rapido.php

Editiamo il file che si presenta così, con una nuova classe che è la versione camelCased del nome del file di migrazione:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class TabDatiNazionaliAddTamponeRapido extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        //
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
    }
}

La classe ha due metodi: uno up() per aggiungere il campo e uno che si chiama ovviamente down() per eliminarlo, nel caso in cui fosse necessario un rollback delle operazioni di modifica della struttura.

Dobbiamo quindi aggiungere l’istruzione PHP che verrà convertita in istruzione DDL dall’ORM. Il nome esatto del campo che verrà prelevato dal CSV è tamponi_test_antigenico_rapido ed è modellabile con un integer; lo sisteiamo appena dopo la colonna note_test:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class TabDatiNazionaliAddTamponeRapido extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        //
        Schema::table('dati_nazionali', function (Blueprint $table) {
            //
            $table->integer('tamponi_test_antigenico_rapido', '0')
                ->after('note_test')
                ->default(0)
                ->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
        Schema::table('dati_nazionali', function (Blueprint $table) {
            //
            $table->dropColumn('tamponi_test_antigenico_rapido');
        });
    }
}

Ho creato a suo tempo una tabella dati_nazionali nel database che ospita i dati provenienti dai file CSV. Ora devo solo aggiungere una nuova colonna.

A questo punto è arrivato il momento di lanciare la migrazione

$ php artisan migrate
Migrating: 2021_01_22_095107_tab_dati_nazionali_add_tampone_molecolare
Migrated:  2021_01_22_095107_tab_dati_nazionali_add_tampone_molecolare (0.43 seconds)
$

Si può controllare la presenza del nuovo campo nella tabella dati nazionali:

mysql> desc italia;
+----------------------------------------+-----------------+------+-----+---------+-----------+
| Field                                  | Type            | Null | Key | Default | Extra     |
+----------------------------------------+-----------------+------+-----+---------+-----------+
| id                                     | bigint unsigned | NO   | PRI | NULL    | auto_inc..|
| data                                   | datetime        | NO   | UNI | NULL    |           |
| stato                                  | varchar(255)    | NO   |     | NULL    |           |
| ricoverati_con_sintomi                 | int unsigned    | YES  |     | NULL    |           |
| terapia_intensiva                      | int unsigned    | YES  |     | NULL    |           |
| ingressi_terapia_intensiva             | int             | YES  |     | 0       |           |
| totale_ospedalizzati                   | int unsigned    | YES  |     | NULL    |           |
| isolamento_domiciliare                 | int unsigned    | YES  |     | NULL    |           |
| totale_positivi                        | int unsigned    | YES  |     | NULL    |           |
| variazione_totale_positivi             | int             | YES  |     | NULL    |           |
| nuovi_positivi                         | int unsigned    | YES  |     | NULL    |           |
| dimessi_guariti                        | int unsigned    | YES  |     | NULL    |           |
| deceduti                               | int unsigned    | YES  |     | NULL    |           |
| casi_da_sospetto_diagnostico           | int             | YES  |     | 0       |           |
| casi_da_screening                      | int             | YES  |     | 0       |           |
| totale_casi                            | int unsigned    | YES  |     | NULL    |           |
| tamponi                                | int unsigned    | YES  |     | NULL    |           |
| casi_testati                           | int unsigned    | YES  |     | NULL    |           |
| note                                   | varchar(255)    | YES  |     | NULL    |           |
| note_casi                              | longtext        | YES  |     | NULL    |           |
| note_test                              | longtext        | YES  |     | NULL    |           |
| tamponi_test_antigenico_rapido         | int             | YES  |     | 0       |           |
| tamponi_test_molecolare                | int             | YES  |     | 0       |           |
| totale_positivi_test_antigenico_rapido | int             | YES  |     | 0       |           |
| totale_positivi_test_molecolare        | int             | YES  |     | 0       |           |
| created_at                             | timestamp       | YES  |     | NULL    |           |
| updated_at                             | timestamp       | YES  |     | NULL    |           |
+----------------------------------------+-----------------+------+-----+---------+-----------+
27 rows in set (0.01 sec)

Come si può vedere, il campo tamponi_test_antigenico_rapido è un intero ed è satato inserito dopo il campo note_test come richiesto.

Finito!

E se qualcosa va storto?

Si può sempre tornare indietro facendo un rollback: artisan eseguirà il metodo down() della classe che viene tradotto con un alter table remove column.

$ php artisan migrate:rollback

Gestione della view e dell’importatore Laravel

In questo caso ho scelto di non mostrare nella tabella anche il nuovo dato, ma se si volesse è sufficiente modificare la view

app/resources/views/dati_nazionali/index.blade.php

aggiungendo una colonna nella riga di intestazione

    <table class="table table-hover text-centered">
            <thead>
                <th>data</th>
               ...
                <th>totale</th>
                <th>tamponi molecolari</th>
                <th>tamponi rapidi</th> <-------
               ...

e il segnaposto per il dato nel ciclo

@foreach($array['data'] as $item)
                <tr>
                    <td>{{$item->data}}</a></td>
                    ...
                    <td>{{$item->tamponi_test_molecolare}}</a></td>
                    <td>{{$item->tamponi_test_antigenico_rapido}}</a></td>
                    ...

Nle mio caso non l’ho integrato per pigrizia (mi interessa maggiormanete che il processo di importazione giri senza problemi ogni giorno), ma appena posso lo integro nella vista.

Ultimo step: l’estrattore da CSV

L’ultimo passaggio è l’esecuzione giornaliera dell’importatore: una riga di cron lancia lo script Laravel che si connette via Curl (vedi l’articolo sui problemi della libreria Ixudra che implememta il client cURL http) al repository Github e scrive le nuove righe nelle tabelle dei dati nazionali e regionali.

Questo script non ho dovuto modificarlo perché l’ho già scritto in modo dinamico: il parser estrae i dati leggendo le colonne del csv e cercando le colonne omonime nella tabella, per cui è già tutto ok.

Sito della situazione Covid-19 di betaingegneria.

Grails scaffolding

Questo articolo parla dello scaffolding di Grails, ossia della funzionalità di generare le pagine web standard in base alle tabelle del database. In particolare affrontiamo gli aspetti della generazione dello scaffold e dell’ordine di comparsa dei campi della tabella nella form HTML. La versione di Grails di cui parlo non è nuovissima, ma è quella su cui sto lavorando su comissione.

A TRUE scaffold
Le impalcature servono anche nel software 😉

Cos’è uno scaffold

Lo scaffold è l’impalcatura, cioè una rappresentazione grezza, anche se visavamente decorosa, delle tabelle DBMS come form HTML, che viene generata automaticamente dal framework, assieme alle implementazioni HTTP dei verbi CRUD che definiscono le operazioni su queste tabelle.

Per chi come me ha iniziato a scrivere applicazioni web nel 2000 si ricorderà che si doveva fare tutto a mano: mappare i campi della tabella in opportuni controlli (HTML), scrivere il controllo di omogeneità lato server e lato client (javascript/jQuery) . Ad esempio: range di valori, correttezza dell’input, ecc. E poi implementare le operazioni CRUD attraverso la compilazione di comandi SQL parametrici.

Dopo un po’ ci si è resi conto che questo lavoro era anche terribilmente noioso perché meccanico e ripetitivo, per cui presto si è messo a punto lo strumento dello scaffolding che automatizzava questa parte di lavoro, producendo automaticamente il codice nel linguaggio dell’application server e la parte grafica in HTML/CSS a partire dalla definizione in DDL della tabella DBMS per la realizzazione delle 4 operazioni.

Scaffolding con Grails 1: static vs dynamic

Lo scaffolding con Grails è possibile in due modi.

Dynamic scaffolding.

Il primo veloce ed economico è lo scaffolding dinamico che significa che gli HTML delle pagine che realizzano le operazioni vengono generati al volo dal framework come anche le funzionalità CRUD del controller e viene tenuto tutto in memoria RAM dell’Application server. È sufficente aggiungere la variabile static scaffolding inizializzata a true nella dichiarazione del controller:

package myapp

class CapitoloController {
    static scaffold = true

//    def index() {
//        render "Elenco dei capitoli"
//    }

}

Come alternativa si può anche inizializzare la variabile scaffold con il nome del dominio (tabella) che si vuole implementare.

Con questa modalità però non ho nessuna azione che venga cablata nel controller, né la produzione di alcuna vista. Se arresto il server perdo tutto.

È un buon punto di partenza ma non offre alcuna flessibilità.

Static scaffolding.

Lo scaffolding statico invece produce codice (controller / view) che poi può essere personalizzato.

In IDE del tipo Groovy/Grails toolsuite esisteva un comando disponibile a menu che produceva lo scaffold per il controller (quindi le azioni standard) e per le viste (le pagine html standard per ogni verbo). Venegoo generati anche i codici Groovy per tutti questi elementi.

Io però uso IntelliJ IDEA di JetBrains che non ha questo comando. Tuttavia è solo un lack della GUI perché da linea di comando si può fare tranquillamente:

$ grails generate-controller rewards.OrderItem

ad esempio genererà il codice per il Controller con i metodi standard index(), show(), creste(), save(), update(), delete(), notFound() e inoltre produrrà una cartella sotto views con il nome del dominio e le viste standard: _form.gsp, create.gsp, edit.gsp, index.gsp e show.gsp.

Grails Scaffolding #2: decidere l’ordine di apparizione dei campi della form HTML

Parte dell’utilizzo dei database nelle applicazioni web è rivolto alla posssibilità di fornire una rappresentazione grafica della tabelle db (assieme alle relazioni) come moduli (form) html. Infatti ciò che si fa è mappare ogni record in una maschera (form) con controlli (input) che ne riportano i valori.

Se si usa la funzionalità di scaffolding di Grails per costtuire le views (le schermate), è possibile decidere in quale ordine debbano essere visualizzati i campi di una form CRUD per la gestione di una tabella DBMS (ovvero di un domain Grails).

Ovviamente se non si usa lo scaffolidng è tutto a carico del progettista, ma è anche possibile dire a Grails come generare gli scaffold.

Nella dichiarazione del domain, nella dichiarazione dei constraints, scriveremo i campi nell’ordine in cui vogliamo che compaiano:

package rewards

class Customer {
    String firstName
    String lastName
    Long phone
    String email
    Integer totalPoints
    static hasMany = [awards: Award, orders: OnlineOrder]

    static constraints = {
        firstName()
        lastName()
        email()
        phone()
        totalPoints()
    }

Nelle dichiarazioni delle variabili, l’ordine non è importante (a questo riguardo). Per quanto riguarda l’ordine nel quale comparranno nella form, è la varibile static costraints a guidare.

Il risultato è rappresentato in figura 1

Come ordinare i campi della form usando lo scaffolding Grails
Oridine dei campi della form

Groovy on Grails, regolazione fine dell’ambiente di sviluppo integrato con Oracle

Grails, driver Oracle, Groovy

In questo articolo riassumo un po’ di informazioni sulla corretta impostazione di un progetto Groovy on Grails che integra una base dati Oracle, utilizzando la IDE IntelliJ IDEA di JetBrains. Con poco sforzo si può adattare ad altri tipi di DBMS.

Determinare la versione da linea di comando

Mi sono accorto oggi che mentre inizializzavo i miei progetti Groovy on Grails con la versione 2.3.11 (che è quella che mi ha richiesto il Cliente), in realtà quando eseguivo il comando grails veniva impiegata la versione 4.0.5. L’output era il seguente:

$ grails -version
 Grails version: 4.0.5

Ho in realtà a disposizione diverse versioni:

$ ll ~/.sdkman/candidates/grails
 drwxr-xr-x  8 marcob marcob 4096 gen  8 11:24 ./
 drwxr-xr-x  6 marcob marcob 4096 ago 13  2019 ../
 drwxr-xr-x 13 marcob marcob 4096 apr 29  2013 2.1.5/
 drwxr-xr-x 13 marcob marcob 4096 giu 21  2013 2.2.3/
 drwxr-xr-x 12 marcob marcob 4096 gen  8 11:23 2.3.11/
 drwxr-xr-x  7 marcob marcob 4096 apr 17  2018 3.2.13/
 drwxr-xr-x  7 marcob marcob 4096 lug 11  2019 4.0.0/
 drwxr-xr-x  7 marcob marcob 4096 ott 25 20:07 4.0.5/
 lrwxrwxrwx  1 marcob marcob    7 gen  8 11:24 current -> 4.0.5/

È sufficiente pertanto modificare il puntamento:

$ unlink ~/.sdkman/candidates/grails/current
$ ln -s ~/.sdkman/candidates/grails/2.3.11 current

A questo punto la versione era quella desiderata:

$ grails -version
 Grails version: 2.3.11

Oracle driver non trovato

La compilazione si arrestava dando questo errore

ClassNotFoundException: oracle.jdbc.driver.OracleDriver

Il driver Oracle dev’essere infatti disponibile nella directory del progetto.

$ echo $ORACLE_HOME 
 /usr/lib/oracle/12.2/client64
$ cd <PROJECT_HOME>/lib
$ cp $ORACLE_HOME/lib/ojdbc8.jar .

È forse più economico linkare il file invece di copiarlo:

$ ln -s $ORACLE_HOME/lib/ojdbc8.jar

Poi occorre aggiungere il path del client anche alle variabili di ambiente; il modo più robusto per farlo è modificare il file ~/.bashrc

$ nano ~/.bashrc
...
export ORACLE_HOME=/usr/lib/oracle/12.2/client64
export PATH=$PATH:$ORACLE_HOME/lib
...

Occorre poi rileggere il file per poter caricare le variabili

$ source ~/.bashrc

A questo punto la mia IDE (Intellij IDEA) compila che è una meraviglia.

Fonti

Altri articoli del blog sull’argomento

Incontri spaziali: la congiunzione tra Giove e Saturno (21/12/2020)

Incontri spaziali. Sembra che Giove e Saturno siano prossimi a scontrarsi ma naturalmente è solo una questione di prospettiva: è la loro proiezione sulla volta celeste a farlo.

Quello che accade realmente è che siamo all’incirca tutti e tre, noi, Giove e Saturno, lungo un’unica linea retta di congiunzione.

Incontri spaziali: Giove e Saturno si incontrano sulla sfera celeste sotto un angolo di 1/5 del disco lunare
Giove e Saturno si incontrano sulla sfera celeste sotto un angolo di 1/5 del disco lunare

Non si tratta in realtà di un allineamento perfetto; più precisamente vediamo entrambi i pianeti sotto un angolo solido pari a circa di 1/5 del disco lunare.

Per esempio questa elaborazione della Nasa mostra cosa si intenda per allineamento tra pianeti. Quello qui rappresentato è la sovrapposizione tra Venere e Giove avvenuta il 30 giugno 2015

Allineamento tra Terra, Venere e Giove del 30 giugno 2015
Allineamento tra Terra, Venere e Giove del 30 giugno 2015

Questo delle congiunzioni o allineamenti è un evento molto raro sulla scala di una vita umana. L’ultima volta che Giove e Saturno si trovarono così vicini sulla volta celeste successe 4 marzo 1226. In quell’anno morirono S. Francesco d’Assisi e Federico II di Hohenstaufen.

Non per la congiunzione astrale, beninteso, è solo una coincidenza!

Anche un’altra volta avvenne, il 16 luglio 1623, però da terra non si vide nulla perché la congiunzione avvenne vicino al disco solare, verso il tramonto.

La congiunzione tra Giove e Saturno in realtà avviene molto più spesso, circa ogni 20 anni, ma non sempre entro un angolo così piccolo e in condizione di oscurità favorevole. Una lista delle congiunzioni tra i due pianeti, nel passato e nel futuro si trova in questa risorsa. Trovate tutte le congiunzioni, più o meno notevoli, dei due pianti dal 1206 al 2398.

Appunto, l’ultima volta i cui entrambe queste condizioni si verificarono, fu nel 1226.

C’è da dire che questa è la prima volta in assoluto che noi umani possiamo goderci lo spettacolo così da vicino, grazie alla Scienza e alla Tecnologia.

Qui trovate l’evento raccontato su The Virtual Telescope Project da Gianluca Masi.

Fonti

PKIX: errore nel creare un’applicazione Grails.

PKIX: La non disponibiità di un certificato SSL è all'origine del problema
La non disponibiità di un certificato SSL è all’origine del “problema PXIX”

PKIX: una precisazione

Voglio avvisarvi subito che non esiste in sé un “errore PKIX”. È soltanto un modo breve di descrivere una situazione in cui c’è un problema del client Grails (o a qualunque altro client) ad accedere ad un URL protetto da SSL.

Se vi capita di vedere questa sigla PKIX in un messaggio errore di Grails, si fa riferimento alla Public Key Infrastructure X.509, uno standard usato per definire il formato dei certificati a chiave pubblica (PKC) e delle autorità di certificazione (CA).

Significa che è sorto qualche problema nello stabilire la connessione cifrata SSL con lo storage da cui il client scarica il template dell’applicazione.

Mi è capitato in più di un contesto questo problema (ad esempio nella configurazione di un reverse proxy Apache), però il significato di un simile tipo di sintomatologia risulta abbastanza chiaro nello specifico esempio che vi propongo.

Qual è il problema per cui compare questa misteriosa sigla PKIX?

Questo problema sorge appena si avvia il client Grails:

$ grails

compare infatti il seguente messaggio di errore:

“PKIX path building failed” and “unable to find valid certification path to requested target”

Ciò che succede è che insorge un problema di sicurezza perché non ho tra i certificati SSL installati nella JVM quello di grails.org da cui il client cerca di scaricare il boilerplate dell’applicazione. Da qui l’errore PKIX.

Una soluzione

La soluzione consiste nello scaricare e installare manualmente il certificato dal sito con Chrome o Firefox, e importarlo nel keystore della JVM.

I passaggi sono un po’ diversi a seconda del browser che state usando per accedere all’applicazione Laravel, per cui per vostra comodità vi illustro i passaggi con entrambi i browser. Per i possessori di Mac: mi dispiace. Per gli appassionati di Edge / Microsoft Internet Explorer: mi dispiace.

Passaggio 1

Scaricare il certificato: puntare il browser all’URL

https://repo.grails.org/grails/core

Una volta stabilita la connessione e visualizzato il sito, cliccare sul lucchetto a sinistra nella barra degli indirizzi; cliccare quindi su “Certificato”, su “Dettagli”, su “Esporta” e fare il download in una cartella che conosciamo, ad esempio Downloads. Questo per Chrome.

Nota bene: questa è un’operazione che potete fare con qualunque sito in https, potete così informarvi sull’autorità emittente, sul proprietario del sito, sull’attendibilità e la validità del certificato di chiave pubblica.

Per Firefox: cliccare sul lucchetto, quindi su “Connessione Sicura >”, su “Ulteriori informazioni”, su “Visualizza certificato”; quindi sulla tab repo.grails.org cliccare sul link “PEM (certficato) e selezionare l’opzione “Salva con nome”. Un po’ più laborioso.

Passaggio 2

Importare il certificato appena scaricato nella keystore della JVM: individuare dunque la directory della JVM, nel mio caso:

/usr/lib/java/jdk1.8.0_25/jre/lib/security

Il file dei certificati è cacerts

Lanciare l’importazione

$ sudo keytool -import -alias example 
   -keystore /usr/lib/java/jdk1.8.0_25/jre/lib/security/cacerts 
   -file /home/marcob/Downloads/repo.grails.org 

Keytools richiede la password del keystore che al primo utilizzo è changeit

[sudo] password di marcob: 
Immettere la password del keystore:  

Proprietario: CN=repo.grails.org
Emittente: CN=Sectigo RSA Domain Validation Secure Server CA, O=Sectigo Limited, L=Salford, ST=Greater Manchester, C=GB
Numero di serie: 2560fb52bfdc8f6bb2b3366805e3c911
Valido da: Fri Nov 13 01:00:00 CET 2020 a: Wed Dec 15 00:59:59 CET 2021
Impronte digitali certificato:
	 SHA1: D6:24:89:09:18:F1:39:E8:D6:6F:DF:EA:88:68:5C:BD:DB:2C:DF:F3
	 SHA256: 65:39:9F:7D:C3:09:D5:58:ED:E3:C3:82:59:98:BA:22:E2:8F:2D:65:6D:1E:F6:C3:51:55:9B:4F:14:9C:ED:A4
Nome algoritmo firma: SHA256withRSA
Algoritmo di chiave pubblica oggetto: Chiave RSA a 2048 bit
Versione: 3
...
Considerare sicuro questo certificato? [no]:  si
Il certificato è stato aggiunto al keystore

Una volta completato l’nserimento del certificato nel portachiavi si può avviare Grails dalla cartella del progetto:

$ grails create-app com.djamware.gadgethouse
| Application created at /home/marcob/IdeaProjects/java/gadgethouse
| Resolving Dependencies. Please wait…
| Starting interactive mode…
| Enter a command name to run. Use TAB for completion:
grails> run-app
| Running application…
...
Grails application running at http://localhost:8080 in environment: development

Fine.

PS: ho preso l’esempio da questo sito qui

Grails in pillole – come superare i problemi che sorgono con database già fatti

Grails Logo
Grails

In questo articolo parlerò dei problemi che si incontrano nel refactoring di una applicazione. Più precisamente di quanto espresso dal titolo quindi:

Come gestire l’ORM (Object Relationship Mapping) su tabelle già definite.

Devo riscrivere un’applicazione PHP in Groovy on Grails ma il database non deve essere toccato come specifica di progetto.

Le pratiche di convention over configuration di molti framweork MVC (come Groovy on Grails, Laravel o Django) permettono di creare il database semplicemente dalla definizione delle classi, senza scrivere alcuna istruzione DDL.

Tuttavia se il Cliente non vuole che si modifichi il database, abbiamo una serie di strumenti messi a disposizone dal layer di gestione della mappatura tra oggetti e tabelle adottato da Grails (Hibernate) che ci permettono di uscire dallo standard per adeguarci all’esistente.

Importante: se non si vuole sovrascrivere il database, nel file grails-app/conf/dataSource.groovy va impostato il parametro dbCreate come “update“, non come “create-drop” (mi raccomando).

Se c’è già un campo chiave primaria

Ogni tabella ha come standard la colonna ID che di solito è un number(10) (sto usando Oracle). Se nella definizione della classe aggiungiamo la proprietà id, Hibernate mapperà il campo id nella colonna ID senza protestare.

Il campo version

Hibernate aggiunge per ogni tabella anche il campo version per implementare la strategia di “optimistic locking”. Cioè si vuole gestire una storia del record per cui viene di fatto creata una chiave composita che si riferisce ad un particolare record attraverso la fornitura sia dell’id che del numero di versione. In realtà in molte applicazioni a cui ho lavorato questo tipo di meccanismo è delegato al DBMS, nel quale (soprattutto nei database Oracle che ho gestito) viene scritto un trigger che crea un “giornale” della tabella con dentro tutte le oprazioni fatte sul record: dal primo inserimento, alle succesive modifiche fino all’eventuale cancellazione fisica. Per cui, in questo contesto ho ritenuto sovrabbondante l’introduzione dell’oprimistic locking e ho optato per la sua soppressione.

Per evitare di gestire una proprietà version si deve dichiare che non la si vuole nel dictionary mapping dentro alla dichiarazione del domain (il corrispondente Grails del model nel paradiga MVC):

package myapp
class Utente
    ...
    static mapping = {
        version false
    }

Il campo version non verrà aggiunto alla tabella.

Il nome della tabella non segue uno standard

I nomi delle classi domain e nelle tabelle prassi abbastanza diffusa sceglierli in inglese, al singolare; però io ho una tabella Utenti per cui faccio un compromesso:

package myapp
class Utente
    ...
    static mapping = {
        table "utenti"
        version false
    }

Specificando nel dictionary statico mapping il campo table posso scegliere di associare il nome della tabella del database “intoccabile”.

Relazioni uno a molti

Come esempio ho un tabella articoli che è figlia di una tabella capitoli: ad un capitolo possono essere associati più articoli. Ho di fatto una serie di articoli che appartengono ad un capitolo per cui si usa la variabile belongsTo per specifcare questa relazione:

package myapp

class Articolo {
    Integer id
    Float prezzo
    String descrizione
    Integer attivo

    static belongsTo = [capitolo: Capitolo]

In questa dichiarazione, il nome a sinistra del : è il nome della relazione (capitolo in minuscolo), e il nome a destra è il nome dell’oggetto padre (Capitolo).

Il nome delle chiavi esterne non segue uno standard

O, meglio, ne segue uno di suo che non è quello di Grails. Nella tabella Articoli per esempio ho una chiave esterna per realizzare la relazione con i Capitoli: idcapitolo che si riferisce all’id della tabella Capitoli.

Seguendo il suo standard, Hibernate crea il nome <tabella>Id per dichiarare le variabili “chiave”. Sfortunamtamente all’epoca nel database hanno seguito la convenzione inversa IDCAPITOLO (con l’id prima del nome tabella).

Mai paura: si dichiara questa deviazione dallo standard nella creazione del domain:

package myapp

class Articolo {
    Integer id
    Float prezzo
    String descrizione
    Integer attivo

    static belongsTo = [capitolo: Capitolo]
    static mapping = {
        table "articoli"
        capitolo column: "idcapitolo" // <-----
        version false
    }

Si faccia attenzione che per mappare il nome della colonna della tabella database ho utilizzato il nome della relazione definita in belongsTo: non capitoloId (che non è definito da nessuna parte) ma capitolo.

Una tabella ha una chiave primaria composita

Come ultimo caso c’è quello di una tabella con una doppia chiave composta dall’unione di due campi idutente e iddipartimento (dovendo gestire il fatto che un utente può lavorare anche in dipartimenti diversi). È una strategia per gestire una relazione molti-a-molti. Di suo Hibernate vorrebbe che io aggiungessi una chiave artificiale ID ma la consegna data me lo impedisce, posso dichiarare la chiave composita e sopprimere la direttiva di creazione dell’ID:

package myapp

class UtenteDipartimento implements Serializable {
    Integer attivo
    Date dataInizio
    Date dataFine

    static belongsTo = [utente: Utente, dipartimento: Dipartimento]
    static mapping = {
        table "utenti_dipartimenti"
        dataInizio column: "data_inizio"
        dataFine column: "data_fine"
        // relazioni
        utente column: "idutente"
        dipartimento column: "iddipartimento"
        id composite: ['utente', 'dipartimento']

        version false
    }

nel quale sorgente riassumo un po’ tutte le cose dette nell’articolo.

Attenzione: se voglio creare una chiave composita, la classe deve implementare l’interfaccia Serializable.

Risorse

Grails Framework home page

Oracle: alcuni tips utili

Oracle Logo

Oracle tips: in questo post troverete alcuni consigli utili per gestire situazioni anomale che, quindi, normamente non si dovrebbero verificare: la password di system è scaduta oppure Sql*Plus non si avvia.
Aggiungo anche un suggerimento su come creare un nuovo schema e degli utenti di supporto per limitare le operazioni sul database da applicazione.

Problem 1: password di system scaduta

Il problema si presenta tipicamente quando non si accede da molto ad un database. È un’eventualità abbastanza remota se si lavora regolarmente sui sistemi. Nel mio caso avevo un vecchio DBMS Oracle di sviluppo in una macchina Windows 10 che non usavo da un po’ ed era scaduta anche la password di system.

Una semplice procedura consente di risolvere il problema. Da linea di comando si digiti

$ sqlplus system
SQL*Plus: Release 11.2.0.2.0 Production on Mer Nov 25 11:57:12 2020
...
Enter password:
ERROR:
ORA-28001: the password has expired
Changing password for system
New password:
Retype new password:
Password changed

Stessa procedura per qualsiasi altro utente. In sostanza Oracle avvia automaticamente la procedura di cambio password nel caso sia scaduta. È chiaro che il ststema è bene non venga lasciato inattivo per troppo tempo, perché chiunque potrebbe impossessarsi del DBMS con questa procedura!

Problem 2: indisponibilità di SQL*Plus

Nel tentativo di risolvere il problema sopra, ho cozzato contro un altro problema che veniva prima in ordine di priorità, ossia non riuscivo nemmeno ad avviare SQL*Plus, il tool di amministrazione/sviluppo di Oracle da linea di comando.

SQL*Plus è uno strumento molto grezzo ma è potentissimo, consente di lanciare ogni tio di comando di amministrazione (DDL) ed elaborazione dati (SQL).

Generalmente si preferisce utilizzare tool con una GUI (Graphic User Interface) come TOAD o Oracle SQL Developer ma questo tool è davvero equivalente, se non più potente.

Nel mio caso l’errore che si verificava all’avvio del comando era il seguente:

C:\> sqlplus
PSP2-0667: Message file sp1<lang>.msb not found

Il problema all’origine di questo messaggio è la non corretta valorizzazione della variabile di ambiente ORACLE_HOME, ho impostato (nel mio caso) al valore:

ORACLE_HOME=C:\oraclexe\app\oracle\product\11.2.0\server\

Problema rientrato.

Tip 1: come creare uno schema

Ecco invece una breve lista di comandi per creare uno schema (uno schema è un utente Oracle proprietario di oggetti) e utilizzarlo per un’applicazione web (da sviluppare con qualsiasi tecnologia).

  1. CREATE USER rewards IDENTIFIED BY smartPassw0rd;
  2. GRANT CONNECT, RESOURCE TO rewards;
  3. GRANT CREATE SESSION TO rewards;
  4. CREATE USER rewards_rw IDENTIFIED BY P4ssw0rd;

Una volta creati gli oggetti, ad esempio creata una una tabella books, devo stabilire cosa l’utente può fare in questa tabella; devo concedere (GRANT) dei privilegi all’utente sulla tabella, andando dalla sua modifica, alla distruzione, alla gestione dei dati. Per questo utente mi limito alle sole operazioni CRUD:

  1. GRANT SELECT, INSERT, UPDATE, DELETE ON rewards.books TO rewards_rw;

È buona prassi non dare i massimi privilegi sul database all’utente utilizzato dall’applicativo per connettersi a Oracle mediante il client. In genere è sufficiente un profilo abilitato alle sole operazioni CRUD, come realizzato dal comando sopra.

Si evita così la possibilità che in modo malevolo (sempre che l’applicazione lo consenta – e lo consente se non è progettata abbastanza bene da inibire comandi SQL – la cosiddetta SQL injection) un attaccante, per mezzo dell’applicazione, lanci istruzione SQL verso il server Oracle.

CRUD

Un’ultima considerazione: CRUD è l’elenco delle operazioni su database definite però in un ambito più genarale e astratto di storage. In pratica queste quattro operazioni (4 verbi) sono richieste e possibili anche in un file system, per esempio: creazione, lettura, scrittura, cancellazione di file, e non è inteso specificatamente per un DBMS. Tuttavia nel linguaggio SQL le quattro operazioni prendono dei nomi specifici:

  • Create → Insert
  • Read → Select
  • Update → Update
  • Delete → Delete

Links utili

Oracle Database Administration Guide