De l'utilisation de Scenic pour gérer nos vues SQL

Publié le 19 septembre 2017 par François Vantomme | rails

Cet article est publié sous licence CC BY-NC-SA

Scenic, vues SQL : de quoi parlons-nous ?

Aujourd’hui, je souhaiterais aborder avec vous le sujet des vues SQL. Une vue, au sens SQL, peut être considérée comme un agrégat, partiel ou non, de tables accessibles en lecture uniquement. Toute altération de donnée au sein d’une table sera instantanément reflétée dans les vues qui lui sont associées. Prenons un exemple pour illustrer mon propos.

CREATE TABLE authors (
  id integer NOT NULL,
  name character varying
);

CREATE TYPE book_status AS ENUM ('to-read', 'read');

CREATE TABLE books (
  id integer NOT NULL,
  author_id integer,
  title character varying,
  isbn character varying,
  pages integer,
  status book_status
);

Nous avons à présent deux tables authors et books qui représentent, sans surprise, des auteur·e·s et des livres écrits par ces dernier·ère·s.

Insérons-y quelques données…

INSERT INTO authors (id, name) VALUES (7, 'Pratchett, Terry');
INSERT INTO authors (id, name) VALUES (8, 'Damasio, Alain');
INSERT INTO authors (id, name) VALUES (9, 'Faye, Estelle');

INSERT INTO books (id, author_id, title, isbn, pages, status)
  VALUES (9, 7, 'The Color of Magic', '9780062225672', 288, 'to-read');
INSERT INTO books (id, author_id, title, isbn, pages, status)
  VALUES (10, 7, 'Strata', '9780552133258', 288, 'to-read');
INSERT INTO books (id, author_id, title, isbn, pages, status)
  VALUES (11, 8, 'La Zone du Dehors', '9782917157114', 492, 'read');
INSERT INTO books (id, author_id, title, isbn, pages, status)
  VALUES (12, 8, 'La Horde du Contrevent', '9782070449064', 1392, 'to-read');
INSERT INTO books (id, author_id, title, isbn, pages, status)
  VALUES (13, 9, 'Porcelaine', '9782361830977', 274, 'read');

Parfait. Si à présent nous souhaitons lister le code ISBN, le titre et l’auteur·e des livres que nous avons lu, il nous faut requêter notre base ainsi :

SELECT "books"."isbn", "books"."title", "authors"."name"
FROM "books"
INNER JOIN "authors" ON "authors"."id" = "books"."author_id"
WHERE ("books"."status" = 'read')

-- +---------------|-------------------|----------------+
-- | isbn          | title             | name           |
-- |---------------|-------------------|----------------|
-- | 9782917157114 | La Zone du Dehors | Damasio, Alain |
-- | 9782361830977 | Porcelaine        | Faye, Estelle  |
-- +---------------|-------------------|----------------+
-- SELECT 2

Seulement, il est possible que nous ayons à requêter ces données ainsi agrégées très fréquemment. Ici l’exemple est simpliste, mais on imagine aisément des cas bien plus complexes avec des jointures et des contraintes autrement plus alambiquées. C’est précisément dans ce genre de situation que la notion de vue va nous être utile. Cela va nous permettre de simplifier notre requêtage, car il nous suffira d’interroger cette vue comme on le fait habituellement avec une table, et de laisser le SGBD s’occuper de la complexité sous-jacente.

Créons donc une vue que l’on nommera read_books et interrogeons-la.

CREATE VIEW read_books (isbn, title, name) AS
  SELECT "books"."isbn", "books"."title", "authors"."name"
  FROM "books"
  INNER JOIN "authors" ON "authors"."id" = "books"."author_id"
  WHERE ("books"."status" = 'read')
SELECT "isbn", "title", "name"
FROM "read_books"

-- +---------------|-------------------|----------------+
-- | isbn          | title             | name           |
-- |---------------|-------------------|----------------|
-- | 9782917157114 | La Zone du Dehors | Damasio, Alain |
-- | 9782361830977 | Porcelaine        | Faye, Estelle  |
-- +---------------|-------------------|----------------+
-- SELECT 2

Rails, migrations & vues SQL

Excellent. Nous savons à présent créer et manipuler des vues SQL. Mais qu’en est-il de Rails ? Depuis la version 4.2, les vues SQL sont supportées et manipulables à l’aide d’ActiveRecord comme s’il s’agissait de simples tables.

Cependant, la création de nos vues nécessitera l’écriture de migrations. Là où le bât blesse, c’est qu’il ne nous est pas possible de mettre à jour nos vues ! En effet, les outils proposés par Rails nous forcent à la réécriture complète de nos vues à chaque migration ; nous obligeant ainsi à retrouver, au beau milieu de nos migrations, la dernière en date concernant la vue que nous souhaitons altérer, au risque de repartir sur d’une migration obsolète…

Scenic à la rescousse !

C’est une nouvelle fois Thoughtbot qui apporte une solution élégante à cette problématique avec Scenic, un ensemble de méthodes pour créer et modifier des vues SQL au sein d’un projet Rails.

Comment cela fonctionne ? Une fois la gem déclarée dans votre Gemfile, il suffit d’appeler le générateur mis à disposition par Scenic :

$ rails generate scenic:view read_books
      create  db/views/read_books_v01.sql
      create  db/migrate/[TIMESTAMP]_create_read_books.rb

Puis éditer le fichier db/views/read_books_v01.sql pour y écrire la requête permettant de générer notre vue. Si l’on reprend la création de vue ci-dessus, il s’agit de la partie située à la suite du mot-clé AS.

-- db/views/read_books_v01.sql
SELECT "books"."isbn", "books"."title", "authors"."name"
FROM "books"
INNER JOIN "authors" ON "authors"."id" = "books"."author_id"
WHERE ("books"."status" = 'read')

Ensuite il ne reste plus qu’à jouer les migrations comme nous en avons l’habitude :

$ rails db:migrate

Mise à jour d’une vue

OK. C’est beau, c’est élégant, mais ce qui nous posait problème c’était la modification d’une vue, n’est-ce pas ? Ne vous inquiétez pas, c’est maintenant que Scenic dévoile tout son potentiel et se rend indispensable !

Générons de nouveau une vue portant le nom read_books. Il est important ici de lui donner le même nom que précédemment pour que Scenic sache qu’il s’agit bien de la même vue.

$ rails generate scenic:view read_books
    create  db/views/read_books_v02.sql
    create  db/migrate/[TIMESTAMP]_update_read_books_to_version_2.rb

Notre fichier db/views/read_books_v02.sql sera pré-rempli avec la requête contenue dans la version précédente de notre vue. Notre migration quant à elle se présentera sous cette forme :

class UpdateReadBooksToVersion2 < ActiveRecord::Migration
  def change
    update_view :read_books, version: 2, revert_to_version: 1
  end
end

Voilà qui est fort pratique, vous en conviendrez ! Et Scenic ne s’arrête pas là car le remplacement de vue, la suppression, ou encore les vues matérialisées sont également supporté·e·s.

Toutes ces fonctionnalités sont très bien décrites et illustrées dans la documentation de Scenic.

Pour aller plus loin…

Maintenant vous êtes au fait de l’utilisation des vues SQL et vous êtes normalement convaincu·e·s de l’intérêt de Scenic dans le cadre d’un projet Rails.

Dans un second article, je vous proposerai d’aller un peu plus loin en vous présentant comment exploiter Scenic au mieux dans le cadre d’un Rails Engine !


L’équipe Synbioz.
Libres d’être ensemble.