window.pipedriveLeadboosterConfig = { base : 'leadbooster-chat.pipedrive.com', companyId : 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version : 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster existe déjà') } else { w.LeadBooster = { q : [], on : function (n, h) { this.q.push({ t : 'o', n : n, h : h }) }, trigger : function (n) { this.q.push({ t : 't', n : n }) }, } } })() Inclure des sous-ressources dans une API REST - The Codest
The Codest
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Industries
    • Fintech et banque
    • E-commerce
    • Adtech
    • Santé (Healthtech)
    • Fabrication
    • Logistique
    • Automobile
    • IOT
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
Flèche arrière RETOUR
2022-03-22
Développement de logiciels

Inclure des sous-ressources dans une API REST

The Codest

Krzysztof Buszewicz

Senior Software Engineer

Nous allons créer une application de bibliothèque pour lister les livres avec (ou sans) données sur les auteurs.

Qu'allons-nous faire ?

Nous allons créer une application de bibliothèque pour lister les livres avec (ou sans) données sur les auteurs. Il y aura une seule application #index et quelques graines. Il s'agit d'un exemple d'application pour montrer comment on peut donner à un utilisateur le contrôle sur des éléments inclus. sous-ressources dans une API de type REST.

"Critères d'acceptation

  • L'utilisateur peut dresser la liste des livres.
  • L'utilisateur peut passer comprend pour charger les ressources associées (auteur).
  • comprend a un format de chaîne : mots séparés par des virgules, représentant des ressources imbriquées.
  • Nous devrions avoir des constantes qui définissent quelles ressources peuvent être incluses dans telle ou telle action.

Outils

Nous utiliserons imprimante bleue comme sérialiseur, parce qu'il est indépendant du format et assez flexible. C'est une gemme que nous ajouterons à l'ensemble des outils standards de rails.

L'application

Créons un exemple d'application. Nous n'ajouterons pas de cadre de test car cela n'entre pas dans notre champ d'application.

rails nouvelle étagère -T

Créez maintenant Auteur modèle :

rails g model author name:string
#=> invoke active_record
#=> create db/migrate/20211224084524_create_authors.rb
#=> créer app/models/author.rb

Et Livre:

rails g model book author:references title:string
# => invoke active_record
# => créer db/migrate/20211224084614_create_books.rb
# => créer app/models/book.rb

Nous aurons besoin de semences :

# db/seeds.rb

dumas = Author.create(name : 'Alexandre Dumas')
lewis = Author.create(nom : 'C.S. Lewis')
martin = Author.create(nom : 'Robert C. Martin')

Book.create(author : dumas, title : 'Les Trois Mousquetaires')
Book.create(author : lewis, title : 'Le Lion, la Sorcière et l'Armoire')
Book.create(author : martin, title : 'Clean Code')

Nous sommes maintenant prêts à exécuter les migrations et à ensemencer la base de données :

rails db:migrate && rails db:seed

Ajoutons a_beaucoup pour les livres en Auteur modèle :

# app/models/author.rb

class Author < ApplicationRecord
  has_many :books
end

Il est temps d'écrire un contrôleur qui renverra nos données. Nous utiliserons API Nous allons donc commencer par ajouter un acronyme aux inflexions :

# config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronyme 'API'
end

Ok, ajoutons notre sérialiseur à Fichier de gemmes:

# Ajouter à Gemfile

gem 'blueprinter'

Et bien sûr, l'installer :

bundle install

Nous pourrons alors élaborer nos plans :

# app/blueprints/author_blueprint.rb

class AuthorBlueprint < Blueprinter::Base
  identifiant :id

  champs :name
end
# app/blueprints/book_blueprint.rb

class BookBlueprint < Blueprinter::Base
  identifiant :id

  champs :title

  association :author, blueprint : AuthorBlueprint
fin

Ajouter un contrôleur de base pour API:

# app/controllers/api/v1/base_controller.rb

module API
  module V1
    classe BaseController < ActionController::API
    end
  fin
fin

Et la version préliminaire de notre Contrôleur de livres:

# app/controllers/api/v1/books_controller.rb

module API
  module V1
    classe BooksController < BaseController
      def index
        books = Book.all

        rendez json : BookBlueprint.render(books)
      end
    fin
  fin
fin

Nous devons également définir le routage, bien entendu :

# config/routes.rb

Rails.application.routes.draw do
  espace de noms :api do
    espace de noms :v1 do
      ressources :books, seulement : :index
    end
  end
fin

Testons ce que nous avons fait jusqu'à présent :

rails s 
curl http://localhost:3000/api/v1/books

# => [{"id":1, "author":{"id":1, "name" : "Alexandre Dumas"}, "title" : "Les Trois Mousquetaires"},{"id":2, "author":{"id":2, "name" : "C.S. Lewis"}, "title" : "Le Lion, la Sorcière et l'Armoire"},{"id":3, "author":{"id":3, "name" : "Robert C. Martin"}, "title" : "Clean Code"}]

Les données semblent correctes, mais qu'en est-il des journaux ?

# journaux des demandes (n+1)

Démarrage de GET "/api/v1/books" pour 127.0.0.1 at 2021-12-24 10:19:40 +0100
Traitement par API::V1::BooksController#index en tant que */*
  Chargement des livres (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Chargement des auteurs (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Chargement des auteurs (0.1ms) SELECT "auteurs".* FROM "auteurs" WHERE "auteurs". "id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Chargement des auteurs (0.1ms) SELECT "auteurs".* FROM "auteurs" WHERE "auteurs". "id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Terminé 200 OK en 6ms (Vues : 0.1ms | ActiveRecord : 0.4ms | Allocations : 3134)

En utilisant l'association dans nos sérialiseurs, nous avons introduit la notion de n+1 problème. Nous voulons l'éliminer en ajoutant à l'utilisateur un contrôle sur ce qu'il demande dans ce point de terminaison. Ainsi, il devrait pouvoir charger uniquement les livres, ou passer le paramètre includes et obtenir également les auteurs, mais de préférence sans le paramètre n+1.

Définissons une constante qui gardera une information sur les assocs de livres que l'utilisateur peut inclure dans livres#index action :

# lib/constants/books/includes.rb

module Constants
  module Books
    module Includes
      ALLOWED = {
        index : %i[
          auteur
        ].freeze
      }.freeze
    fin
  fin
fin

Ensuite, nous définissons un espace de noms pour les constantes d'objets vides :

# lib/constants/empty.rb

module Constants
  module Empty
    HASH = {}.freeze
  end
fin

Et voici notre service principal pour autoriser les inclusions. Je pense que le code est assez explicite. magie ne sont alloués que dans les #default_resources_key et #default_purpose. Ces méthodes sont définies pour nous permettre d'appeler des inclusions de permis en passant uniquement des paramètres dans les contrôleurs de rails. La sortie sera le hash qui stocke vrai pour chaque inclusion autorisée.

# app/services/permit_includes.rb

require 'constants/empty'
require 'constants/books/includes'

classe PermitIncludes
  Empty = Constants::Empty

  COMMA = ','
  SLASH = '/'

  INCLUDES_FORMAT = /A[a-z]+(,[a-z]+)*z/.freeze
  ALLOWED_INCLUDES = {
    livres : Constants::Books::Includes::ALLOWED
  }.freeze

  def call(params, resources : default_resources_key(params), purpose : default_purpose(params))
    return Empty::HASH unless includes_sent ?(params)
    return Empty::HASH unless includes_valid ?(params)

    requested_includes = parse_includes(params)
    allowed_includes = filter_includes(requested_includes, resources, purpose)

    allowed_includes.index_with(true)
  fin

  privé

  def default_resources_key(params)
    raise(ArgumentError, 'params :controller key must be a string') unless params[:controller].is_a ?(String)

    params[:controller].split(SLASH).last&.to_sym
  end

  def default_purpose(params)
    raise(ArgumentError, 'params :action key must be a string') unless params[:action].is_a ?(String)

    params[:action].to_sym
  end

  def includes_sent ?(params)
    params.key ?(:includes)
  end

  def includes_valid ?(params)
    return false unless params[:includes].is_a ?(String)

    params[:includes].match ?(INCLUDES_FORMAT)
  end

  def parse_includes(params)
    params[:includes].split(COMMA).map(&:to_sym)
  end

  def filter_includes(requested_includes, resources_key, purpose)
    requested_includes & ALLOWED_INCLUDES[resources_key][purpose]
  end
fin

Nous devons maintenant utiliser les clés pour charger les inclusions et passer le hachage des inclusions lui-même au sérialiseur :

# app/controllers/api/v1/books_controller.rb

module API
  module V1
    classe BooksController < BaseController
      def index
        includes = PermitIncludes.new.call(params)
        books = Book.includes(includes.keys).all

        rendez le json : BookBlueprint.render(books, includes : includes)
      fin
    fin
  fin
fin

C'est ainsi que nous devons adapter notre sérialiseur - nous ne chargeons l'association que si elle est incluse :

# app/blueprints/book_blueprint.rb
class BookBlueprint (_field_name, _book, options) {
                         options[:includes] && options[:includes][:author]
                       }
end

Testons-le à nouveau :

rails s
curl http://localhost:3000/api/v1/books
# => [{"id":1, "title" : "Les trois mousquetaires"},{"id":2, "title" : "Le lion, la sorcière et l'armoire"},{"id":3, "title" : "Clean Code"}]
Journal des requêtes # (nous ne chargeons que des livres)
Démarrage de GET "/api/v1/books" pour ::1 at 2021-12-24 10:33:41 +0100
Traitement par API::V1::BooksController#index en tant que */*
   (0.1ms) SELECT sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Chargement des livres (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Terminé 200 OK en 9ms (Vues : 0.1ms | ActiveRecord : 0.9ms | Allocations : 4548)

Bien, nous n'avons pas passé les inclusions et n'avons donc obtenu que des livres, sans auteurs. Demandons-les maintenant :

curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1, "author":{"id":1, "name" : "Alexandre Dumas"}, "title" : "Les Trois Mousquetaires"},{"id":2, "author":{"id":2, "name" : "C.S. Lewis"}, "titre" : "Le lion, la sorcière et l'armoire"},{"id":3, "auteur":{"id":3, "nom" : "Robert C. Martin"}, "titre" : "Clean Code"}]% 
# journal des demandes (éliminé n+1)

Lancé GET "/api/v1/books?includes=author" pour ::1 at 2021-12-24 10:38:23 +0100
Traitement par API::V1::BooksController#index en tant que */*
  Paramètres : {"includes"=>"author"}
  Chargement des livres (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Chargement des auteurs (0.2ms) SELECT "auteurs".* FROM "auteurs" WHERE "auteurs". "id" IN ( ?, ?, ?) [["id", 1], ["id", 2], ["id", 3]]
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Terminé 200 OK en 17ms (Vues : 0.1ms | ActiveRecord : 0.7ms | Allocations : 7373)

Cool ! L'association est chargée et éliminée n+1 problème. Le service peut être utilisé pour n'importe quelle ressource, tout ce que nous voulons faire est d'ajouter des constantes autorisées dans le format approprié et de les ajouter à PermitIncludes::ALLOWED_INCLUDES.

Nous devons nous rappeler que cela doit être utilisé avec pagination (et prudence) parce que l'inclusion d'associations peut "manger" beaucoup de mémoire.

Articles connexes

Fintech

5 exemples de la meilleure utilisation de Ruby

Vous êtes-vous déjà demandé ce que l'on pouvait faire avec Ruby ? Eh bien, le ciel est probablement la limite, mais nous sommes heureux de parler de quelques cas plus ou moins connus...

The Codest
Pawel Muszynski Software Engineer
Développement de logiciels

Polymorphisme en Ruby et GraphQL

Dans cet article, je vais présenter l'utilisation du polymorphisme dans GraphQL. Mais avant de commencer, il convient de rappeler ce que sont le polymorphisme et GraphQL.

Lukasz Brzeszcz
E-commerce

Dilemmes de la cybersécurité : Fuites de données

La ruée vers les cadeaux de Noël bat son plein. À la recherche de cadeaux pour leurs proches, les gens sont de plus en plus enclins à "prendre d'assaut" les boutiques en ligne

The Codest
Jakub Jakubowicz CTO & Co-Fondateur
Développement de logiciels

Une application Ruby simple à partir de zéro avec Active Record

MVC est un modèle de conception qui divise les responsabilités d'une application afin d'en faciliter le déplacement. Rails suit ce modèle de conception par convention.

The Codest
Damian Watroba Software Engineer

Abonnez-vous à notre base de connaissances et restez au courant de l'expertise du secteur des technologies de l'information.

    A propos de nous

    The Codest - Entreprise internationale de développement de logiciels avec des centres technologiques en Pologne.

    Royaume-Uni - Siège

    • Bureau 303B, 182-184 High Street North E6 2JA
      Londres, Angleterre

    Pologne - Les pôles technologiques locaux

    • Parc de bureaux Fabryczna, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Varsovie, Pologne

      The Codest

    • Accueil
    • A propos de nous
    • Services
    • Études de cas
    • Savoir comment
    • Carrières
    • Dictionnaire

      Services

    • Conseil consultatif
    • Développement de logiciels
    • Développement backend
    • Développement frontal
    • Staff Augmentation
    • Développeurs backend
    • Ingénieurs en informatique dématérialisée
    • Ingénieurs des données
    • Autres
    • Ingénieurs AQ

      Ressources

    • Faits et mythes concernant la coopération avec un partenaire externe de développement de logiciels
    • Des États-Unis à l'Europe : Pourquoi les startups américaines décident-elles de se délocaliser en Europe ?
    • Comparaison des pôles de développement Tech Offshore : Tech Offshore Europe (Pologne), ASEAN (Philippines), Eurasie (Turquie)
    • Quels sont les principaux défis des CTO et des DSI ?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Conditions d'utilisation du site web

    Copyright © 2025 par The Codest. Tous droits réservés.

    fr_FRFrench
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek fr_FRFrench