The Codest
  • Chi siamo
  • Servizi
    • Sviluppo di software
      • Sviluppo Frontend
      • Sviluppo backend
    • Staff Augmentation
      • Sviluppatori Frontend
      • Sviluppatori backend
      • Ingegneri dei dati
      • Ingegneri del cloud
      • Ingegneri QA
      • Altro
    • Consulenza
      • Audit e consulenza
  • Industrie
    • Fintech e banche
    • E-commerce
    • Adtech
    • Tecnologia della salute
    • Produzione
    • Logistica
    • Automotive
    • IOT
  • Valore per
    • CEO
    • CTO
    • Responsabile della consegna
  • Il nostro team
  • Case Studies
  • Sapere come
    • Blog
    • Incontri
    • Webinar
    • Risorse
Carriera Contattate
  • Chi siamo
  • Servizi
    • Sviluppo di software
      • Sviluppo Frontend
      • Sviluppo backend
    • Staff Augmentation
      • Sviluppatori Frontend
      • Sviluppatori backend
      • Ingegneri dei dati
      • Ingegneri del cloud
      • Ingegneri QA
      • Altro
    • Consulenza
      • Audit e consulenza
  • Valore per
    • CEO
    • CTO
    • Responsabile della consegna
  • Il nostro team
  • Case Studies
  • Sapere come
    • Blog
    • Incontri
    • Webinar
    • Risorse
Carriera Contattate
Freccia indietro TORNA INDIETRO
2022-03-22
Sviluppo di software

Includere le sottorisorse in un'API REST

The Codest

Krzysztof Buszewicz

Senior Software Engineer

Costruiremo un'applicazione libreria per elencare i libri con (o senza) i dati degli autori.

Cosa faremo?

Costruiremo un'applicazione libreria per elencare i libri con (o senza) i dati degli autori. Ci sarà un singolo #indice e alcuni semi. Si tratta di un'applicazione di esempio per mostrare come si possa dare all'utente il controllo sulle azioni incluse nel file. risorse secondarie in un'API di tipo REST.

"Criteri di accettazione

  • L'utente può elencare i libri.
  • L'utente può passare comprende per caricare le risorse associate (autore).
  • comprende Il parametro della query ha un formato di stringa: parole separate da virgole, che rappresentano risorse annidate.
  • Dovremmo avere alcune costanti che definiscono quali risorse sono includibili per quale azione.

Strumenti

Utilizzeremo stampante blu come serializzatore, perché è agnostico rispetto al formato e abbastanza flessibile. È un'unica gemma che aggiungeremo al set di strumenti standard di rails.

L'applicazione

Creiamo un'applicazione di esempio. Non aggiungeremo il framework di test, perché è fuori dal nostro scopo.

binari nuova libreria -T

Creare ora Autore modello:

rails g modello autore nome:string
#=> invocare active_record
#=> creare db/migrate/20211224084524_create_authors.rb
#=> creare app/modelli/autore.rb

E Libro:

rails g model book author:references title:string
# => invocare active_record
# => create db/migrate/20211224084614_create_books.rb
# => creare app/modelli/libri.rb

Avremo bisogno di semi:

# db/seeds.rb

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

Book.create(author: dumas, title: 'The Three Musketeers')
Book.create(author: lewis, title: 'The Lion, the Witch and the Wardrobe')
Book.create(author: martin, title: 'Clean Code')

Ora siamo pronti a eseguire le migrazioni e a seminare il database:

rails db:migrate && rails db:seed

Aggiungiamo ha_molti per i libri in Autore modello:

# app/models/author.rb

class Author < ApplicationRecord
  has_many :books
fine

È il momento di scrivere un controllore che restituisca i nostri dati. Utilizzeremo API quindi per prima cosa aggiungiamo un acronimo alle inflessioni:

# config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflettere.acronimo 'API'
fine

Ok, aggiungiamo il nostro serializzatore a Profilo delle gemme:

# Aggiungere al file delle gemme

gemma 'blueprinter'

E naturalmente installarlo:

installare il bundle

Poi possiamo costruire i nostri progetti:

# app/blueprints/author_blueprint.rb

classe AuthorBlueprint < Blueprinter::Base
  identificatore :id

  campi :nome
fine
# app/blueprints/book_blueprint.rb

class BookBlueprint < Blueprinter::Base
  identificatore :id

  campi :titolo

  associazione :autore, blueprint: AuthorBlueprint
fine

Aggiungere un controllore di base per API:

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

modulo API
  modulo V1
    classe BaseController < ActionController::API
    fine
  fine
fine

E la versione in bozza del nostro Controllore di libri:

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

modulo API
  modulo V1
    classe BooksController < BaseController
      def indice
        libri = Book.all

        rendere json: BookBlueprint.render(books)
      fine
    fine
  fine
fine

Naturalmente dobbiamo anche definire il routing:

# config/routes.rb

Rails.application.routes.draw do
  spazio dei nomi :api do
    spazio dei nomi :v1 do
      risorse :libri, solo: :index
    fine
  fine
fine

Verifichiamo quanto fatto finora:

rotaie s 
ricciolo http://localhost:3000/api/v1/books

# => [{"id":1, "autore":{"id":1, "nome": "Alexandre Dumas"}, "titolo": "I tre moschettieri"},{"id":2, "autore":{"id":2, "nome": "C.S. Lewis"}, "titolo": "Il leone, la strega e l'armadio"},{"id":3, "autore":{"id":3, "nome": "Robert C. Martin"}, "titolo": "Clean Codice"}]

I dati sembrano essere a posto, e i log?

Registri di richiesta # (n+1)

Avviata GET "/api/v1/books" per 127.0.0.1 a 2021-12-24 10:19:40 +0100
Elaborazione da parte di API::V1::BooksController#index come */*
  Caricamento dei libri (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Caricamento degli autori (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controller/api/v1/books_controller.rb:7:in `index'
  Caricamento degli autori (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controller/api/v1/books_controller.rb:7:in `index'
  Caricamento degli autori (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Completato 200 OK in 6ms (Views: 0.1ms | ActiveRecord: 0.4ms | Allocations: 3134)

Utilizzando l'associazione nei nostri serializzatori, abbiamo introdotto il concetto di n+1 problema. Vogliamo eliminarlo aggiungendo all'utente un controllo su ciò che richiede in questo endpoint. Quindi dovrebbe essere in grado di caricare solo i libri, oppure di passare il parametro includes e ottenere anche gli autori, ma preferibilmente senza il parametro n+1.

Definiamo una costante che mantenga un'informazione su quali assocs di libri l'utente può includere in libri#indice azione:

# lib/constants/books/includes.rb

modulo Costanti
  modulo Libri
    modulo Include
      CONSENTITO = {
        indice: %i[
          autore
        ].freeze
      }.freeze
    fine
  fine
fine

Si definisce quindi uno spazio dei nomi per le costanti degli oggetti vuoti:

# lib/constants/empty.rb

modulo Costanti
  modulo Vuoto
    HASH = {}.freeze
  fine
fine

Ed ecco il nostro servizio principale per consentire gli include. Penso che il codice sia abbastanza autoesplicativo, alcuni pezzi di magia sono allocati solo in #chiave_risorse_predefinite e #scopo_predefinito. Questi metodi sono definiti per consentirci di chiamare i permessi include passando solo i params nei controllori di rails. L'output sarà l'hash che memorizza vero per ogni inclusione consentita.

# app/services/permit_includes.rb

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

classe PermitIncludes
  Vuoto = Costanti::Vuoto

  COMMA = ','
  SLASH = '/'

  INCLUDE_FORMAT = /A[a-z]+(,[a-z]+)*z/.freeze
  ALLOWED_INCLUDES = {
    libri: 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)
  fine

  privato

  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
  fine

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

    params[:action].to_sym
  fine

  def include_sent?(params)
    params.key?(:includes)
  fine

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

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

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

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

Ora dobbiamo usare le chiavi per caricare gli include e passare l'hash degli include stessi al serializzatore:

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

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

        renderizzare json: BookBlueprint.render(books, includes: includes)
      fine
    fine
  fine
fine

Ed è così che dobbiamo modificare il nostro serializzatore: carichiamo l'associazione solo se è inclusa:

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

Eseguiamo un altro test:

rotaie s
curl http://localhost:3000/api/v1/books
# => [{"id":1, "titolo": "I tre moschettieri"},{"id":2, "titolo": "Il leone, la strega e l'armadio"},{"id":3, "titolo": "Codice pulito"}]
Log delle richieste # (carichiamo solo libri)
Avviata GET "/api/v1/books" per ::1 a 2021-12-24 10:33:41 +0100
Elaborazione da parte di API::V1::BooksController#index come */*
   (0,1 ms) SELEZIONA sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Caricamento dei libri (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Completato 200 OK in 9ms (Views: 0.1ms | ActiveRecord: 0.9ms | Allocations: 4548)

Bene, non abbiamo superato gli include e quindi abbiamo solo libri, senza autori. Ora richiediamoli:

curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1, "autore":{"id":1, "nome": "Alexandre Dumas"}, "titolo": "I tre moschettieri"},{"id":2, "autore":{"id":2, "nome": "C.S. Lewis"}, "titolo": "Il leone, la strega e l'armadio"},{"id":3, "autore":{"id":3, "nome": "Robert C. Martin"}, "titolo": "Codice pulito"}]% 
Registri di richiesta # (eliminati n+1)

Avviata GET "/api/v1/books?includes=author" per ::1 al 2021-12-24 10:38:23 +0100
Elaborazione da parte di API::V1::BooksController#index come */*
  Parametri: {"includes"=>"author"}
  Caricamento dei libri (0,1 ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" IN (?, ?, ?) [["id", 1], ["id", 2], ["id", 3]]
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Completato 200 OK in 17ms (Views: 0.1ms | ActiveRecord: 0.7ms | Allocations: 7373)

Forte! L'associazione è stata caricata ed eliminata n+1 problema. Il servizio può essere usato per qualsiasi risorsa, tutto ciò che vogliamo fare è aggiungere le costanti consentite nel formato corretto e aggiungerle a PermitIncludes::ALLOWED_INCLUDES.

Bisogna ricordare che questo dovrebbe essere usato probabilmente con la paginazione (e con cautela), perché l'inclusione di associazioni può "mangiare" molta memoria.

Articoli correlati

Fintech

5 esempi di utilizzo ottimale di Ruby

Vi siete mai chiesti cosa possiamo fare con Ruby? Beh, il cielo è probabilmente il limite, ma siamo felici di parlare di alcuni casi più o meno noti...

The Codest
Pawel Muszynski Software Engineer
Sviluppo di software

Polimorfismo in Ruby e GraphQL

In questo articolo presenterò l'uso del polimorfismo in GraphQL. Prima di iniziare, però, vale la pena ricordare cosa sono il polimorfismo e GraphQL.

Lukasz Brzeszcz
E-commerce

Dilemmi della sicurezza informatica: Fughe di dati

La corsa al Natale è in pieno svolgimento. Alla ricerca di regali per i propri cari, le persone sono sempre più disposte a "prendere d'assalto" i negozi on-line

The Codest
Jakub Jakubowicz CTO e cofondatore
Sviluppo di software

Una semplice applicazione Ruby da zero con Active Record

MVC è un modello di progettazione che divide le responsabilità di un'applicazione per facilitarne la gestione. Rails segue questo modello di progettazione per convenzione.

The Codest
Damian Watroba Software Engineer

Iscrivetevi alla nostra knowledge base e rimanete aggiornati sulle competenze del settore IT.

    Chi siamo

    The Codest - Società internazionale di sviluppo software con centri tecnologici in Polonia.

    Regno Unito - Sede centrale

    • Ufficio 303B, 182-184 High Street North E6 2JA
      Londra, Inghilterra

    Polonia - Poli tecnologici locali

    • Parco uffici Fabryczna, Aleja
      Pokoju 18, 31-564 Cracovia
    • Ambasciata del cervello, Konstruktorska
      11, 02-673 Varsavia, Polonia

      The Codest

    • Casa
    • Chi siamo
    • Servizi
    • Case Studies
    • Sapere come
    • Carriera
    • Dizionario

      Servizi

    • Consulenza
    • Sviluppo di software
    • Sviluppo backend
    • Sviluppo Frontend
    • Staff Augmentation
    • Sviluppatori backend
    • Ingegneri del cloud
    • Ingegneri dei dati
    • Altro
    • Ingegneri QA

      Risorse

    • Fatti e miti sulla collaborazione con un partner esterno per lo sviluppo di software
    • Dagli Stati Uniti all'Europa: Perché le startup americane decidono di trasferirsi in Europa
    • Confronto tra gli hub di sviluppo Tech Offshore: Tech Offshore Europa (Polonia), ASEAN (Filippine), Eurasia (Turchia)
    • Quali sono le principali sfide di CTO e CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Condizioni di utilizzo del sito web

    Copyright © 2025 di The Codest. Tutti i diritti riservati.

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