Hello les petits Biscuits !
Bienvenue sur la 36Úme édition de Ruby Biscuit.
Vous ĂȘtes maintenant 591 abonnĂ©s đ„ł
Maintenant Ruby biscuit, câest aussi votre meilleur alliĂ© pour recruter des devs Ruby !
Si vous nâavez pas encore rejoint le club, RDV sur https://recrutement.rubybiscuit.fr
Bonne lecture.
Chez Capsens, jâai rĂ©cemment dĂ» crĂ©er un dashboard affichant des donnĂ©es agrĂ©gĂ©es provenant de plusieurs tables : nombre de contributions, montants collectĂ©s, statut des projets, etc. La requĂȘte SQL sous-jacente Ă©tait assez lourde et utilisĂ©e Ă plusieurs endroits de lâapplication. Jâen ai donc profitĂ© pour tester les vues SQL, une solution qui permet dâamĂ©liorer significativement les performances de la requĂȘte.
Une vue SQL, câest un peu comme une « fausse table » : elle ne stocke pas directement des donnĂ©es, mais mĂ©morise une requĂȘte SQL complexe pour quâon puisse lâutiliser comme si câĂ©tait une table classique. Super pratique pour Ă©viter les rĂ©pĂ©titions et simplifier la logique mĂ©tier dans vos requĂȘtes !
Dans cet article, je vous propose de découvrir ces fameuses vues SQL, comment les utiliser (notamment avec la gem Scenic) et quelles sont leurs limites.
Les deux types de vues SQL :
Il existe deux types de vues SQL : les vues non matérialisées (classiques) et les vues matérialisées. Voyons la différence :
1. Les vues non matérialisées (classiques)
Ce sont juste des requĂȘtes sauvegardĂ©es qui sont recalculĂ©es chaque fois que vous y accĂšdez.
Avantages :
Simplifient ton code Ruby en Ă©vitant les requĂȘtes complexes rĂ©pĂ©titives.
Centralisent la logique SQL.
Inconvénients :
Peuvent ĂȘtre lentes car recalculĂ©es Ă chaque consultation.
Quand choisir une vue non matĂ©rialisĂ©e ? đŠ
Vous pouvez envisager une vue non matérialisée quand :
Les requĂȘtes SQL sont modĂ©rĂ©ment complexes et frĂ©quemment utilisĂ©es.
Les donnĂ©es doivent toujours ĂȘtre Ă jour en temps rĂ©el (par exemple, si vous affichez un stock de produits en temps rĂ©el dans un dashboard).
Vous voulez centraliser la logique SQL afin de garder un code plus clair, cohérent et maintenable.
Exemple pratique avec Scenic, lâami des vues SQL dans Rails đ
Scenic est une gem Ruby qui simplifie considérablement la gestion des views SQL dans Rails, qu'elles soient matérialisées ou non. Elle permet une intégration propre et lisible dans ton application Rails, avec une gestion native des migrations.
Créer une vue non matérialisée :
rails generate scenic:view active_users
Cette commande génÚre automatiquement :
Un fichier SQL :
db/views/active_users_v01.sql
Une migration pour créer ta vue
Exemple du fichier SQL :
SELECT users.*
FROM users
WHERE users.active = true
ORDER BY users.created_at DESC;
Migration Rails générée :
class CreateActiveUsers < ActiveRecord::Migration[7.0]
def change
create_view :active_users
end
end
On peut mĂȘme associer un model Ă cette vue ! Et donc y stocker nos mĂ©thodes, scopes et mĂȘme les associations !
class ActiveUser < ApplicationRecord
self.table_name = 'active_users'
scope :recent, -> { order(created_at: :desc) }
end
2. Les vues matérialisées
Elles sauvegardent les rĂ©sultats physiquement dans la base de donnĂ©es et doivent ĂȘtre rafraĂźchies rĂ©guliĂšrement ou sur demande.
Avantages :
Beaucoup plus rapides, surtout pour les requĂȘtes lourdes.
Réduisent la charge sur la base de données.
Inconvénients :
Prennent plus dâespace de stockage.
Peuvent afficher des données légÚrement périmées si elles ne sont pas rafraßchies fréquemment.
Quand choisir une vue matĂ©rialisĂ©e ? đŠ
Vous pouvez envisager une vue matérialisée quand :
Vous avez as des requĂȘtes trĂšs coĂ»teuses que vous utilisez souvent (rapports, statistiques, etc.).
Les donnĂ©es nâont pas besoin d'ĂȘtre mises Ă jour en temps rĂ©el.
Exemple pratique (toujours avec Scenic đ) :
Créer une vue matérialisée :
rails generate scenic:view latest_orders --materialized
Cette commande génÚre automatiquement :
Un fichier SQL :
db/views/latest_orders_v01.sql
Une migration pour créer ta vue
Exemple du fichier SQL :
SELECT orders.*, users.email AS user_email
FROM orders
JOIN users ON users.id = orders.user_id
ORDER BY orders.created_at DESC
LIMIT 100;
Migration Rails générée :
class CreateLatestOrders < ActiveRecord::Migration[7.0]
def change
create_view :latest_orders, materialized: true
end
end
ModÚle associé à cette vue :
class LatestOrder < ApplicationRecord
self.primary_key = :id
self.table_name = 'latest_orders'
belongs_to :user, foreign_key: :user_id
scope :recent, -> { order(created_at: :desc) }
def self.refresh
Scenic.database.refresh_materialized_view(table_name, concurrently: true)
end
end
Important : si vous voulez rafraĂźchir la vue sans bloquer ton application (concurrently: true
), il faut impérativement ajouter un index unique :
class AddIndexToLatestOrders < ActiveRecord::Migration[7.0]
def change
add_index :latest_orders, :id, unique: true
end
end
Attention ! Lâoption concurrently: true
exige PostgreSQL â„ 9.4 et un index unique.
Versionner une vue SQL avec Scenic đ
Si vous souhaitez faire évoluer ta vue existante, Scenic permet facilement de gérer différentes versions :
rails generate scenic:view latest_orders --materialized --replace
Cette commande génÚre une nouvelle version du fichier SQL (latest_orders_v02.sql
) ainsi qu'une migration adaptée.
Bonus : Tester tes vues SQL đ§Ș
Voici comment vous pouvez tester facilement votre vue SQL avec RSpec par exemple :
# spec/models/latest_order_spec.rb
require 'rails_helper'
RSpec.describe LatestOrder, type: :model do
describe '.recent' do
subject { described_class.recent }
let(:user) { create(:user) }
let!(:order1) { create(:order, user: user, created_at: 1.day.ago) }
let!(:order2) { create(:order, user: user, created_at: 1.hour.ago) }
it 'returns orders ordered by created_at: :desc' do
described_class.refresh # obligatoire pour une vue matérialisée
expect(subject.pluck(:id)).to eq(
[order2.id, order1.id]
)
end
end
end
VoilĂ ! Vous connaissez maintenant les bonnes pratiques des vues SQL avec Rails, comment les versionner et mĂȘme comment les tester facilement. Ă vous de jouer avec Scenic ! đ
â Christopher