The Codest
  • Over ons
  • Diensten
    • Software Ontwikkeling
      • Frontend ontwikkeling
      • Backend ontwikkeling
    • Staff Augmentation
      • Frontend ontwikkelaars
      • Backend ontwikkelaars
      • Gegevensingenieurs
      • Cloud Ingenieurs
      • QA ingenieurs
      • Andere
    • Het advies
      • Audit & Consulting
  • Industrie
    • Fintech & Bankieren
    • E-commerce
    • Adtech
    • Gezondheidstechnologie
    • Productie
    • Logistiek
    • Automotive
    • IOT
  • Waarde voor
    • CEO
    • CTO
    • Leveringsmanager
  • Ons team
  • Case Studies
  • Weten hoe
    • Blog
    • Ontmoetingen
    • Webinars
    • Bronnen
Carrière Neem contact op
  • Over ons
  • Diensten
    • Software Ontwikkeling
      • Frontend ontwikkeling
      • Backend ontwikkeling
    • Staff Augmentation
      • Frontend ontwikkelaars
      • Backend ontwikkelaars
      • Gegevensingenieurs
      • Cloud Ingenieurs
      • QA ingenieurs
      • Andere
    • Het advies
      • Audit & Consulting
  • Waarde voor
    • CEO
    • CTO
    • Leveringsmanager
  • Ons team
  • Case Studies
  • Weten hoe
    • Blog
    • Ontmoetingen
    • Webinars
    • Bronnen
Carrière Neem contact op
Pijl terug KEREN TERUG
2022-03-22
Software Ontwikkeling

Subbronnen opnemen in een REST-achtige API

The Codest

Krzysztof Buszewicz

Senior Software Engineer

We zullen een boekenplank-app bouwen om boeken op te sommen met (of zonder) auteursgegevens.

Wat gaan we doen?

We zullen een boekenplank-app bouwen om boeken met (of zonder) auteursgegevens op te sommen. Er zal een enkele #index actie en wat seeds. Dit zal een voorbeeld app zijn om te laten zien hoe je een gebruiker controle kunt geven over opgenomen subbronnen in een REST-achtige API.

"Aanvaardingscriteria".

  • De gebruiker kan een lijst met boeken maken.
  • Gebruiker kan doorgeven omvat query parameter om geassocieerde bronnen te laden (auteur).
  • omvat query parameter heeft een formaat van string: door komma's gescheiden woorden, die geneste bronnen voorstellen.
  • We zouden een aantal constanten moeten hebben die bepalen welke bronnen kunnen worden opgenomen voor welke actie.

Gereedschap

We gebruiken blauwdruk als een serializer, omdat het formaat agnostisch en vrij flexibel is. Dit is een juweeltje dat we zullen toevoegen aan de standaard toolset van rails.

De app

Laten we een voorbeeldapp maken. We voegen geen testraamwerk toe omdat dat buiten ons bereik valt.

rails nieuwe boekenplank -T

Maak nu Auteur model:

rails g model auteursnaam:string
#=> active_record oproepen
#=> maak db/migrate/20211224084524_creëer_auteurs.rb
#=> app/modellen/auteur.rb aanmaken

En Boek:

rails g model boek auteur:referenties titel:string
# => active_record oproepen
# => aanmaken db/migrate/20211224084614_create_books.rb
# => app/modellen/boek.rb aanmaken

We hebben zaden nodig:

# db/zaden.rb

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

Boek.create(auteur: dumas, titel: 'De drie musketiers')
Boek.create(auteur: lewis, titel: 'De leeuw, de heks en de kleerkast')
Boek.create(auteur: martin, titel: "Schone code")

En nu zijn we klaar om migraties uit te voeren en de db te vullen:

rails db:migrate && rails db:seed

Laten we toevoegen heeft_veel voor boeken in Auteur model:

# app/modellen/author.rb

klasse Auteur < Toepassingsregistratie
  heeft_veel :boeken
einde

Het is tijd om een controller te schrijven die onze gegevens retourneert. We gebruiken API namespace, dus laten we eerst een acroniem toevoegen aan verbuigingen:

# config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acroniem 'API
einde

Oké, laten we onze serializer toevoegen aan Gemfile:

# Toevoegen aan Gemfile

Gem 'blueprinter

En natuurlijk installeren:

bundel installeren

Dan kunnen we onze blauwdrukken maken:

# app/blueprints/author_blueprint.rb

klasse AuteurBlueprint < Blueprinter::Base
  identificator :id

  velden :naam
einde
# app/blueprints/boek_blauwdruk.rb

klasse Boekblauwdruk < Blauwdruk::Basis
  identificator :id

  velden :title

  associatie :auteur, blauwdruk: AuteurBlueprint
einde

Een basiscontroller toevoegen voor API:

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

module API
  module V1
    klasse BaseController < ActionController::API
    einde
  einde
einde

En de conceptversie van onze BoekenController:

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

module API
  module V1
    klasse Boekencontroller < Basiscontroller
      def index
        boeken = Boek.alle

        json weergeven: BookBlueprint.render(books)
      einde
    einde
  einde
einde

We moeten natuurlijk ook routing definiëren:

# config/routes.rb

Rails.application.routes.draw doen
  naamruimte :api doen
    naamruimte :v1 doen
      bronnen :boeken, alleen: :index
    einde
  einde
einde

Laten we testen wat we tot nu toe hebben gedaan:

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

# => [{"id":1,"auteur":{"id":1,"naam":"Alexandre Dumas"},"titel":"De drie musketiers"},{"id":2,"auteur":{"id":2,"naam":"C.S. Lewis"},"titel":"De leeuw, de heks en de kleerkast"},{"id":3,"auteur":{"id":3,"naam":"Robert C. Martin"},"titel":"Schone Code"}]

De gegevens lijken in orde, maar hoe zit het met de logs?

# verzoeklogs (n+1)

Gestart met GET "/api/v1/books" voor 127.0.0.1 op 2021-12-24 10:19:40 +0100
Verwerking door API::V1::BooksController#index als */*
  Boeken laden (0.1ms) SELECT "books".* FROM "books".
  app/controllers/api/v1/books_controller.rb:7:in `index'.
  Auteur laden (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  app/controllers/api/v1/books_controller.rb:7:in `index'.
  Auteur laden (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Auteur laden (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Voltooid 200 OK in 6ms (Views: 0.1ms | ActiveRecord: 0.4ms | Toewijzingen: 3134)

Door associatie te gebruiken in onze serializers hebben we n+1 probleem. We willen het elimineren door de gebruiker controle te geven over wat hij opvraagt in dit eindpunt. Dus hij moet in staat zijn om ofwel alleen boeken te laden, of de includes parameter door te geven en ook auteurs te krijgen, maar bij voorkeur zonder de n+1.

Laten we een constante definiëren die informatie bijhoudt over welke associs van boeken de gebruiker kan opnemen in boeken#index actie:

# lib/constants/bookss/includes.rb

module Constanten
  module Boeken
    module Inclusief
      TOEGESTAAN = {
        index: %i[
          auteur
        ].bevriezen
      }.bevriezen
    einde
  einde
einde

Vervolgens definiëren we een naamruimte voor lege objectconstanten:

# lib/constants/empty.rb

module Constanten
  module Leeg
    HASH = {}.freeze
  einde
einde

En hier is onze hoofdservice voor het toestaan van includes. Ik denk dat de code vrij voor zichzelf spreekt, sommige stukken van magie worden alleen toegewezen in #standaard_bronnen_sleutel en #standaard_doel. Deze methodes zijn gedefinieerd zodat we permit includes kunnen aanroepen door alleen params door te geven in de controllers van rails. De uitvoer zal de hash zijn die Echt voor elke toegestane opname.

# app/services/permit_includes.rb

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

klasse ToestemmingInclusies
  Leeg = Constanten::Leeg

  COMMA = ','
  SLASH = '/'

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

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

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

    allowed_includes.index_with(true)
  einde

  privé

  def default_resources_key(params)
    raise(ArgumentError, 'params :controller key moet een string zijn') tenzij params[:controller].is_a?(String)

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

  def default_purpose(params)
    raise(ArgumentError, 'params :action key moet een string zijn') tenzij params[:action].is_a?(String)

    params[:actie].naar_sym
  einde

  def omvat_verzonden?(params)
    params.key?(:includes)
  einde

  def omvat_geldig?(params)
    return false tenzij params[:includes].is_a?(String)

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

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

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

Nu moeten we de sleutels gebruiken om includes te laden en de inlcudes hash zelf doorgeven aan de serializer:

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

module API
  module V1
    klasse Boekencontroller < Basiscontroller
      def index
        includes = PermitIncludes.new.call(params)
        boeken = Boek.bevat(includes.keys).alle

        json renderen: BookBlueprint.render(books, includes: includes)
      einde
    einde
  einde
einde

En dit is hoe we onze serializer moeten aanpassen - we laden de associatie alleen als deze is opgenomen:

# app/blueprints/boek_blauwdruk.rb
klasse Boekblauwdruk (_veld_naam, _boek, opties) {
                         options[:includes] && options[:includes][:author]
                       }
einde

Laten we het nog eens testen:

rails s
krul http://localhost:3000/api/v1/books
# => [{"id":1,"title":"The Three Musketeers"},{"id":2,"title":"The Lion, the Witch and the Wardrobe"},{"id":3,"title":"Clean Code"}]
# verzoeklogs (we laden alleen boeken)
Gestart met GET "/api/v1/books" voor ::1 op 2021-12-24 10:33:41 +0100
Verwerking door API::V1::BooksController#index als */*
   (0.1ms) SELECT sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'.
  Boek laden (0.1ms) SELECT "books".* FROM "books".
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Voltooid 200 OK in 9 ms (Views: 0.1ms | ActiveRecord: 0.9ms | Toewijzingen: 4548)

Goed, we zijn nog niet voorbij de includes dus we hebben alleen boeken, zonder auteurs. Laten we ze nu opvragen:

krul 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1,"auteur":{"id":1,"naam":"Alexandre Dumas"},"titel":"De drie musketiers"},{"id":2,"auteur":{"id":2,"naam":"C.S. Lewis"},"titel":"De leeuw, de heks en de kleerkast"},{"id":3,"auteur":{"id":3,"naam":"Robert C. Martin"},"titel":"Schone code"}]% 
# verzoeklogs (geëlimineerd n+1)

Gestart met GET "/api/v1/books?includes=author" voor ::1 op 2021-12-24 10:38:23 +0100
Verwerking door API::V1::BooksController#index als */*
  Parameters: {"includes"=>"author"}
  Boek laden (0.1ms) SELECT "books".* FROM "books".
  app/controllers/api/v1/books_controller.rb:8:in `index'.
  Auteur laden (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'
Voltooid 200 OK in 17ms (Views: 0.1ms | ActiveRecord: 0.7ms | Toewijzingen: 7373)

Gaaf! We hebben de associatie geladen en geëlimineerd n+1 probleem. De service kan worden gebruikt voor elke bron, het enige wat we willen doen is toegestane inlcudes constanten in het juiste formaat toevoegen en ze toevoegen aan PermitIncludes::ALLOWED_INCLUDES.

We moeten onthouden dat dit waarschijnlijk moet worden gebruikt met paginering (en voorzichtigheid) omdat het opnemen van associaties veel geheugen kan "vreten".

Verwante artikelen

Fintech

5 voorbeelden van het beste gebruik van Ruby

Heb je je ooit afgevraagd wat we met Ruby kunnen doen? Nou, de sky is waarschijnlijk de limit, maar we praten graag over een aantal min of meer bekende gevallen...

The Codest
Pawel Muszynski Software Engineer
Software Ontwikkeling

Polymorfisme in Ruby en GraphQL

In dit artikel presenteer ik het gebruik van polymorfisme in GraphQL. Voordat ik begin, is het echter de moeite waard om in herinnering te brengen wat polymorfisme en GraphQL zijn.

Lukasz Brzeszcz
E-commerce

Cyberbeveiligingsdilemma's: Datalekken

De prekerstdrukte is in volle gang. Op zoek naar cadeaus voor hun geliefden, zijn mensen steeds vaker bereid om online winkels te "bestormen".

The Codest
Jakub Jakubowicz CTO & medeoprichter
Software Ontwikkeling

Een eenvoudige Ruby-applicatie vanaf nul met Active Record

MVC is een ontwerppatroon dat de verantwoordelijkheden van een toepassing verdeelt om ze gemakkelijker te kunnen verplaatsen. Rails volgt dit ontwerppatroon volgens conventie.

The Codest
Damian Watroba Software Engineer

Abonneer je op onze kennisbank en blijf op de hoogte van de expertise uit de IT-sector.

    Over ons

    The Codest - Internationaal softwareontwikkelingsbedrijf met technische hubs in Polen.

    Verenigd Koninkrijk - Hoofdkantoor

    • Kantoor 303B, 182-184 High Street North E6 2JA
      Londen, Engeland

    Polen - Lokale technologieknooppunten

    • Fabryczna kantorenpark, Aleja
      Pokoju 18, 31-564 Krakau
    • Hersenambassade, Konstruktorska
      11, 02-673 Warschau, Polen

      The Codest

    • Home
    • Over ons
    • Diensten
    • Case Studies
    • Weten hoe
    • Carrière
    • Woordenboek

      Diensten

    • Het advies
    • Software Ontwikkeling
    • Backend ontwikkeling
    • Frontend ontwikkeling
    • Staff Augmentation
    • Backend ontwikkelaars
    • Cloud Ingenieurs
    • Gegevensingenieurs
    • Andere
    • QA ingenieurs

      Bronnen

    • Feiten en fabels over samenwerken met een externe partner voor softwareontwikkeling
    • Van de VS naar Europa: Waarom Amerikaanse startups besluiten naar Europa te verhuizen
    • Tech Offshore Ontwikkelingshubs Vergelijking: Tech Offshore Europa (Polen), ASEAN (Filippijnen), Eurazië (Turkije)
    • Wat zijn de grootste uitdagingen voor CTO's en CIO's?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Gebruiksvoorwaarden website

    Copyright © 2025 door The Codest. Alle rechten voorbehouden.

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