window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = Fenster if (w.LeadBooster) { console.warn('LeadBooster existiert bereits') } 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 }) }, } } })() Einbindung von Unterressourcen in eine REST-ähnliche API - The Codest
Der Codest
  • Über uns
  • Dienstleistungen
    • Software-Entwicklung
      • Frontend-Softwareentwicklung
      • Backend-Softwareentwicklung
    • Staff Augmentation
      • Frontend-Entwickler
      • Backend-Entwickler
      • Daten-Ingenieure
      • Cloud-Ingenieure
      • QS-Ingenieure
      • Andere
    • IT-Beratung
      • Prüfung und Beratung
  • Branchen
    • Fintech & Bankwesen
    • E-commerce
    • Adtech
    • Gesundheitstechnik
    • Herstellung
    • Logistik
    • Automobilindustrie
    • IOT
  • Wert für
    • CEO
    • CTO
    • Delivery Manager
  • Unser Team
  • Fallstudien
  • Gewusst wie
    • Blog
    • Begegnungen
    • Webinare
    • Ressourcen
Karriere Kontakt aufnehmen
  • Über uns
  • Dienstleistungen
    • Software-Entwicklung
      • Frontend-Softwareentwicklung
      • Backend-Softwareentwicklung
    • Staff Augmentation
      • Frontend-Entwickler
      • Backend-Entwickler
      • Daten-Ingenieure
      • Cloud-Ingenieure
      • QS-Ingenieure
      • Andere
    • IT-Beratung
      • Prüfung und Beratung
  • Wert für
    • CEO
    • CTO
    • Delivery Manager
  • Unser Team
  • Fallstudien
  • Gewusst wie
    • Blog
    • Begegnungen
    • Webinare
    • Ressourcen
Karriere Kontakt aufnehmen
Pfeil zurück ZURÜCK
2022-03-22
Software-Entwicklung

Einbindung von Sub-Ressourcen in eine REST-artige API

Der Codest

Krzysztof Buszewicz

Senior Software Engineer

Wir werden eine Bücherregal-App entwickeln, die Bücher mit (oder ohne) Autorendaten auflistet.

Was werden wir tun?

Wir werden eine Bücherregal-App entwickeln, die Bücher mit (oder ohne) Autorendaten auflistet. Es wird eine einzelne #index Aktion und einige Seeds. Dies ist eine Beispielanwendung, die zeigt, wie man einem Benutzer die Kontrolle über enthaltene Sub-Ressourcen in einer REST-ähnlichen API.

"Akzeptanzkriterien"

  • Der Benutzer kann die Bücher auflisten.
  • Benutzer kann passieren enthält Abfrageparameter zum Laden zugehöriger Ressourcen (Autor).
  • enthält Abfrageparameter hat das Format String: durch Kommata getrennte Wörter, die verschachtelte Ressourcen darstellen.
  • Wir sollten einige Konstanten haben, die definieren, welche Ressourcen für welche Aktion einschließbar sind.

Werkzeuge

Wir werden die Blaudrucker als Serialisierer, weil er formatunabhängig und ziemlich flexibel ist. Dies ist ein einziges Juwel, das wir dem Standard-Toolset von Rails hinzufügen werden.

Die App

Lassen Sie uns eine Beispielanwendung erstellen. Wir fügen kein Test-Framework hinzu, da dies nicht in unseren Aufgabenbereich fällt.

Schienen neues Bücherregal -T

Jetzt erstellen Autor Modell:

schienen g modell autor name:string
#=> Aufrufen von active_record
#=> db/migrate/20211224084524_create_authors.rb erstellen
#=> app/models/author.rb erstellen

Und Buchen Sie:

schienen g model buch autor:referenzen titel:string
# => active_record aufrufen
# => db/migrate/20211224084614_create_books.rb erstellen
# => app/models/book.rb erstellen

Wir brauchen einige Samen:

# db/seeds.rb

dumas = Autor.erstellen(Name: 'Alexandre Dumas')
lewis = Autor.erstellen(Name: 'C.S. Lewis')
martin = Autor.erstellen(Name: 'Robert C. Martin')

Book.create(Autor: Dumas, Titel: 'Die drei Musketiere')
Book.create(author: lewis, title: 'Der Löwe, die Hexe und der Kleiderschrank')
Book.create(Autor: martin, Titel: 'Clean Code')

Jetzt sind wir bereit, Migrationen durchzuführen und die Datenbank zu starten:

schienen db:migrate && schienen db:seed

Fügen wir hinzu hat_viele für Bücher in Autor Modell:

# app/models/author.rb

Klasse Autor < ApplicationRecord
  has_many :books
end

Es ist an der Zeit, einen Controller zu schreiben, der unsere Daten zurückgibt. Wir verwenden API Namespace, also fügen wir zunächst ein Akronym für Beugungen hinzu:

# config/initializers/inflections.rb

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

Ok, fügen wir unseren Serialisierer zu Gemfile:

# Zum Gemfile hinzufügen

gem 'blueprinter'

Und natürlich installieren:

Bündelinstallation

Dann können wir unsere Entwürfe erstellen:

# app/blueprints/author_blueprint.rb

Klasse AuthorBlueprint < Blueprinter::Base
  Bezeichner :id

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

Klasse BookBlueprint < Blueprinter::Base
  Bezeichner :id

  Felder :title

  association :author, blueprint: AuthorBlueprint
end

Hinzufügen eines Basis-Controllers für API:

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

Baustein API
  Modul V1
    Klasse BaseController < ActionController::API
    end
  end
end

Und die Entwurfsfassung unserer BooksController:

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

Modul API
  Modul V1
    Klasse BooksController < BaseController
      def index
        books = Book.all

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

Natürlich müssen wir auch das Routing definieren:

# config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    Namensraum :v1 do
      ressourcen :bücher, nur: :index
    end
  end
end

Testen wir, was wir bisher gemacht haben:

Schienen s 
http://localhost:3000/api/v1/books locken.

# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "Die drei Musketiere"},{"id":2, "author":{"id":2, "name": "C.S. Lewis"}, "Titel": "Der Löwe, die Hexe und der Kleiderschrank"},{"id":3, "author":{"id":3, "name": "Robert C. Martin"}, "Titel": "Clean Code"}]

Die Daten scheinen in Ordnung zu sein, aber was ist mit den Protokollen?

# Anforderungsprotokolle (n+1)

Gestartet GET "/api/v1/books" für 127.0.0.1 um 2021-12-24 10:19:40 +0100
Verarbeitung durch API::V1::BooksController#index als */*
  Buch laden (0.1ms) SELECT "bücher".* FROM "bücher"
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Autor laden (0.1ms) SELECT "autoren".* FROM "autoren" WHERE "autoren". "id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Autor laden (0.1ms) SELECT "autoren".* FROM "autoren" WHERE "autoren". "id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Autor laden (0.1ms) SELECT "autoren".* FROM "autoren" WHERE "autoren". "id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Abgeschlossen 200 OK in 6ms (Views: 0.1ms | ActiveRecord: 0.4ms | Allocations: 3134)

Durch die Verwendung von Assoziation in unseren Serialisierern haben wir n+1 Problem. Wir wollen es beseitigen, indem wir dem Benutzer eine Kontrolle darüber geben, was er an diesem Endpunkt anfordert. Er sollte also in der Lage sein, entweder nur Bücher zu laden, oder den Includes-Parameter zu übergeben und auch Autoren zu erhalten, aber vorzugsweise ohne den n+1.

Definieren wir eine Konstante, die Informationen darüber enthält, welche Assoziationen von Büchern der Benutzer in Bücher#index Aktion:

# lib/constants/books/includes.rb

Modul Konstanten
  Modul Bücher
    modul Includes
      ALLOWED = {
        index: %i[
          Autor
        ].freeze
      }.freeze
    end
  end
end

Als nächstes definieren wir einen Namensraum für leere Objektkonstanten:

# lib/constants/empty.rb

Modul Konstanten
  modul Leer
    HASH = {}.freeze
  end
end

Und hier ist unser Hauptdienst für die Genehmigung von Includes. Ich denke, der Code ist ziemlich selbsterklärend, einige Teile von Magie werden nur zugewiesen in #default_resources_key und #default_purpose. Diese Methoden sind so definiert, dass wir permit includes aufrufen können, die nur Parameter in Rails-Controllern übergeben. Die Ausgabe wird der Hash sein, der Folgendes speichert wahr für jede zulässige Aufnahme.

# app/services/permit_includes.rb

require 'Konstanten/Leerzeichen'
require 'Konstanten/Bücher/Einzelteile'

Klasse PermitIncludes
  Leer = Konstanten::Leer

  COMMA = ','
  SLASH = '/'

  INCLUDES_FORMAT = /A[a-z]+(,[a-z]+)*z/.freeze
  ALLOWED_INCLUDES = {
    Bücher: Konstanten::Bücher::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)
  end

  privat

  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
end

Jetzt müssen wir die Schlüssel verwenden, um Includes zu laden und den Hash der Includes selbst an den Serialisierer zu übergeben:

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

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

        render json: BookBlueprint.render(books, includes: includes)
      end
    end
  end
end

Und so müssen wir unseren Serializer anpassen - wir laden die Assoziation nur, wenn sie enthalten ist:

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

Testen wir es noch einmal:

Schienen s
kräuseln http://localhost:3000/api/v1/books
# => [{"id":1, "title": "Die drei Musketiere"},{"id":2, "title": "Der Löwe, die Hexe und der Kleiderschrank"},{"id":3, "title": "Clean Code"}]
# Anfrageprotokolle (wir laden nur Bücher)
Startet GET "/api/v1/books" für ::1 am 2021-12-24 10:33:41 +0100
Verarbeitung durch API::V1::BooksController#index als */*
   (0.1ms) SELECT sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Buch laden (0.1ms) SELECT "bücher".* FROM "bücher"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Abgeschlossen 200 OK in 9ms (Views: 0.1ms | ActiveRecord: 0.9ms | Allocations: 4548)

Gut, wir haben die Inklusion nicht bestanden, also haben wir nur Bücher, ohne Autoren. Fordern wir sie nun an:

curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "Die drei Musketiere"},{"id":2, "author":{"id":2, "name": "C.S. Lewis"}, "Titel": "Der Löwe, die Hexe und der Kleiderschrank"},{"id":3, "author":{"id":3, "name": "Robert C. Martin"}, "Titel": "Clean Code"}]% 
#-Anforderungsprotokolle (eliminiert n+1)

GET "/api/v1/books?includes=author" für ::1 gestartet am 2021-12-24 10:38:23 +0100
Verarbeitung durch API::V1::BooksController#index als */*
  Parameter: {"includes"=>"author"}
  Buch laden (0.1ms) SELECT "bücher".* FROM "bücher"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Autor laden (0.2ms) SELECT "autoren".* FROM "autoren" WHERE "autoren". "id" IN (?, ?, ?) [["id", 1], ["id", 2], ["id", 3]]
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Abgeschlossen 200 OK in 17ms (Views: 0.1ms | ActiveRecord: 0.7ms | Allocations: 7373)

Super! Wir haben die Assoziation geladen und eliminiert n+1 Problem. Der Dienst kann für jede beliebige Ressource verwendet werden. Alles, was wir tun wollen, ist, zulässige Konstanten im richtigen Format hinzuzufügen und sie in PermitIncludes::ALLOWED_INCLUDES.

Wir müssen bedenken, dass dies wahrscheinlich mit Paginierung (und Vorsicht) verwendet werden sollte, da die Einbeziehung von Assoziationen viel Speicher "fressen" kann.

Ähnliche Artikel

Fintech

5 Beispiele für die beste Verwendung von Ruby

Haben Sie sich jemals gefragt, was wir mit Ruby alles machen können? Nun, der Himmel ist wahrscheinlich die Grenze, aber wir sprechen gerne über einige mehr oder weniger bekannte Fälle...

Der Codest
Pawel Muszynski Software Engineer
Software-Entwicklung

Polymorphismus in Ruby und GraphQL

In diesem Artikel werde ich die Verwendung von Polymorphismus in GraphQL vorstellen. Bevor ich jedoch damit beginne, sollte man sich in Erinnerung rufen, was Polymorphismus und GraphQL sind.

Lukasz Brzeszcz
E-commerce

Dilemmas der Cybersicherheit: Datenlecks

Der vorweihnachtliche Ansturm ist in vollem Gange. Auf der Suche nach Geschenken für ihre Liebsten sind die Menschen zunehmend bereit, Online-Shops zu "stürmen"

Der Codest
Jakub Jakubowicz CTO & Mitbegründer
Software-Entwicklung

Eine einfache Ruby-Anwendung von Grund auf mit Active Record

MVC ist ein Entwurfsmuster, das die Verantwortlichkeiten einer Anwendung aufteilt, um sie leichter handhabbar zu machen. Rails folgt diesem Entwurfsmuster per Konvention.

Der Codest
Damian Watroba Software Engineer

Abonnieren Sie unsere Wissensdatenbank und bleiben Sie auf dem Laufenden über das Fachwissen aus dem IT-Sektor.

    Über uns

    The Codest - Internationales Software-Unternehmen mit technischen Zentren in Polen.

    Vereinigtes Königreich - Hauptsitz

    • Büro 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokale Tech-Hubs

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polen

      Der Codest

    • Startseite
    • Über uns
    • Dienstleistungen
    • Fallstudien
    • Gewusst wie
    • Karriere
    • Wörterbuch

      Dienstleistungen

    • IT-Beratung
    • Software-Entwicklung
    • Backend-Softwareentwicklung
    • Frontend-Softwareentwicklung
    • Staff Augmentation
    • Backend-Entwickler
    • Cloud-Ingenieure
    • Daten-Ingenieure
    • Andere
    • QS-Ingenieure

      Ressourcen

    • Fakten und Mythen über die Zusammenarbeit mit einem externen Softwareentwicklungspartner
    • Aus den USA nach Europa: Warum entscheiden sich amerikanische Start-ups für eine Verlagerung nach Europa?
    • Tech Offshore Development Hubs im Vergleich: Tech Offshore Europa (Polen), ASEAN (Philippinen), Eurasien (Türkei)
    • Was sind die größten Herausforderungen für CTOs und CIOs?
    • Der Codest
    • Der Codest
    • Der Codest
    • Privacy policy
    • Website terms of use

    Urheberrecht © 2025 von The Codest. Alle Rechte vorbehalten.

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