The Codest
  • O nás
  • Služby
    • Vývoj softwaru
      • Vývoj frontendů
      • Vývoj backendu
    • Staff Augmentation
      • Vývojáři frontendů
      • Vývojáři backendu
      • Datoví inženýři
      • Cloudoví inženýři
      • Inženýři QA
      • Další
    • To Advisory
      • Audit a poradenství
  • Odvětví
    • Fintech a bankovnictví
    • E-commerce
    • Adtech
    • Healthtech
    • Výroba
    • Logistika
    • Automobilový průmysl
    • IOT
  • Hodnota za
    • CEO
    • CTO
    • Manažer dodávek
  • Náš tým
  • Case Studies
  • Vědět jak
    • Blog
    • Setkání
    • Webové semináře
    • Zdroje
Kariéra Spojte se s námi
  • O nás
  • Služby
    • Vývoj softwaru
      • Vývoj frontendů
      • Vývoj backendu
    • Staff Augmentation
      • Vývojáři frontendů
      • Vývojáři backendu
      • Datoví inženýři
      • Cloudoví inženýři
      • Inženýři QA
      • Další
    • To Advisory
      • Audit a poradenství
  • Hodnota za
    • CEO
    • CTO
    • Manažer dodávek
  • Náš tým
  • Case Studies
  • Vědět jak
    • Blog
    • Setkání
    • Webové semináře
    • Zdroje
Kariéra Spojte se s námi
Šipka zpět ZPĚT
2022-03-22
Vývoj softwaru

Zahrnutí dílčích zdrojů do rozhraní API typu REST

The Codest

Krzysztof Buszewicz

Senior Software Engineer

Vytvoříme aplikaci pro seznam knih s údaji o autorech (nebo bez nich).

Co budeme dělat?

Vytvoříme aplikaci pro seznam knih s údaji o autorech (nebo bez nich). Bude existovat jedna #index akce a některá semena. Toto bude příklad aplikace, která ukáže, jak můžete dát uživateli kontrolu nad obsaženými dílčí zdroje v rozhraní API typu REST.

"Kritéria přijatelnosti"

  • Uživatel může vypsat knihy.
  • Uživatel může předat obsahuje parametr dotazu pro načtení přidružených zdrojů (autor).
  • obsahuje parametr dotazu má formát řetězce: slova oddělená čárkou, která představují vnořené zdroje.
  • Měli bychom mít nějaké konstanty, které definují, které prostředky lze zahrnout do které akce.

Nástroje

Použijeme blueprinter jako serializér, protože je formátově agnostický a poměrně flexibilní. Jedná se o jediný klenot, který přidáme do standardní sady nástrojů rails.

Aplikace

Vytvořme příklad aplikace. Nepřidáváme testovací framework, protože je mimo náš rozsah.

kolejnice nový regál -T

Nyní vytvořte Autor model:

rails g jméno autora modelu:string
#=> invoke active_record
#=> create db/migrate/20211224084524_create_authors.rb
#=> create app/models/author.rb

A Kniha:

rails g model book autor:reference název:string
# => invoke active_record
# => create db/migrate/20211224084614_create_books.rb
# => create app/models/book.rb

Budeme potřebovat semena:

# db/seeds.rb

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

Book.create(autor: dumas, název: "Tři mušketýři")
Book.create(autor: lewis, název: "The Lion, the Witch and the Wardrobe")
Book.create(autor: martin, název: "Clean Code")

Nyní jsme připraveni spustit migraci a nasadit db:

rails db:migrate && rails db:seed

Přidejme has_many pro knihy v Autor model:

# app/models/author.rb

class Author < ApplicationRecord
  has_many :books
end

Je čas napsat kontrolér, který bude vracet naše data. Použijeme API nejprve přidáme zkratku ke skloňování:

# config/initializers/inflections.rb

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

Dobře, přidejme náš serializér do příkazu Gemfile:

# Přidat do souboru drahokamů

gem 'blueprinter'

A samozřejmě ji nainstalujte:

instalace svazku

Pak můžeme vytvořit naše plány:

# app/blueprints/author_blueprint.rb

class AuthorBlueprint < Blueprinter::Base
  identifier :id

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

class BookBlueprint < Blueprinter::Base
  identifier :id

  fields :title

  asociace :author, blueprint: AuthorBlueprint
end

Přidání základního řadiče pro API:

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

modul API
  modul V1
    třída BaseController < ActionController::API
    end
  end
end

A návrh naší BooksController:

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

modul API
  modul V1
    třída BooksController < BaseController
      def index
        books = Book.all

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

Samozřejmě musíme také definovat směrování:

# config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :books, only: :index
    end
  end
end

Vyzkoušejme, co jsme dosud udělali:

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

# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "The Three Musketeers"},{"id":2, "author":{"id":2, "name": "C.S. Lewis"}, "title": "The Lion, the Witch and the Wardrobe"},{"id":3, "author":{"id":3, "name": "Robert C. Martin"}, "title": "Clean Kód"}]

Data se zdají být v pořádku, ale co protokoly?

Protokoly požadavků # (n+1)

Spuštěno GET "/api/v1/books" pro 127.0.0.1 v 2021-12-24 10:19:40 +0100
Zpracování pomocí API::V1::BooksController#index jako */*
  Načítání knih (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors". "id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
Dokončeno 200 OK za 6 ms (Views: 0.1ms | ActiveRecord: 0.4ms | Allocations: 3134)

Použitím asociace v našich serializátorech jsme zavedli n+1 problém. Chceme ho odstranit tím, že přidáme uživateli kontrolu nad tím, co v tomto koncovém bodě požaduje. Měl by tedy mít možnost buď načíst pouze knihy, nebo předat parametr includes a získat i autory, ale nejlépe bez parametru n+1.

Definujme konstantu, která bude uchovávat informaci o tom, jaké asociace knih může uživatel zahrnout do. books#index akce:

# lib/constants/books/includes.rb

modul Constants
  modul Books
    modul Includes
      ALLOWED = {
        index: %i[
          autor
        ].freeze
      }.freeze
    konec
  konec
end

Dále definujeme jmenný prostor pro prázdné konstanty objektů:

# lib/constants/empty.rb

modul Constants
  modul Empty
    HASH = {}.freeze
  end
end

A zde je naše hlavní služba pro povolení zahrnuje. Myslím, že kód je celkem srozumitelný, některé části kouzlo jsou přidělovány pouze v #default_resources_key a #default_purpose. Tyto metody jsou definovány tak, aby umožňovaly nás volání permit zahrnuje předávání pouze paramů v řadičích rails. Výstupem bude hash, který ukládá Pravda pro každé povolené zařazení.

# app/services/permit_includes.rb

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

třída PermitIncludes
  Empty = Constants::Empty

  COMMA = ','
  SLASH = '/'

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

  private

  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

Nyní musíme použít klíče k načtení include a předat samotný hash inlcudes serializátoru:

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

modul API
  modul V1
    třída BooksController < BaseController
      def index
        includes = PermitIncludes.new.call(params)
        books = Book.includes(includes.keys).all

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

A právě tak musíme upravit náš serializér - asociaci načteme pouze tehdy, je-li obsažena:

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

Vyzkoušejme to znovu:

kolejnice s
curl 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"}]
Protokoly požadavků # (načítáme pouze knihy)
Spuštěno GET "/api/v1/books" pro ::1 v 2021-12-24 10:33:41 +0100
Zpracování pomocí API::V1::BooksController#index jako */*
   (0.1ms) SELECT sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Načítání knih (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `dex'
Dokončeno 200 OK za 9 ms (Zobrazení: 0.1ms | ActiveRecord: 0.9ms | Alokace: 4548)

Dobře, neprošli jsme, takže jsme dostali jen knihy, bez autorů. Nyní si je vyžádáme:

curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1, "author":{"id":1, "name": "Alexandre Dumas"}, "title": "The Three Musketeers"},{"id":2, "author":{"id":2, "name": "C.S. Lewis"}, "title": "The Lion, the Witch and the Wardrobe"},{"id":3, "author":{"id":3, "name": "Robert C. Martin"}, "title": "Clean Code"}]% 
Protokoly požadavků # (vyřazeno n+1)

Spuštěno GET "/api/v1/books?includes=author" pro ::1 v 2021-12-24 10:38:23 +0100
Zpracování pomocí API::V1::BooksController#index jako */*
  Parametry: {"includes"=>"author"}
  Načítání knih (0.1ms) 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'
Dokončeno 200 OK za 17 ms (Zobrazení: 0,1 ms | ActiveRecord: 0,7 ms | Alokace: 7373)

Super! Sdružení jsme načetli a vyřadili n+1 problém. Služba může být použita pro jakýkoli zdroj, jediné, co chceme udělat, je přidat povolené konstanty ve správném formátu a přidat je do služby PermitIncludes::ALLOWED_INCLUDES.

Musíme si uvědomit, že by se to mělo pravděpodobně používat se stránkováním (a opatrně), protože zahrnutí asociací může "sežrat" hodně paměti.

Související články

Fintech

5 příkladů nejlepšího použití jazyka Ruby

Přemýšleli jste někdy o tom, co všechno můžeme dělat s Ruby? No, obloze se asi meze nekladou, ale rádi si povíme o některých více či méně známých případech...

The Codest
Pawel Muszynski Software Engineer
Vývoj softwaru

Polymorfismus v jazyce Ruby a GraphQL

V tomto článku představím použití polymorfismu v jazyce GraphQL. Než však začnu, je vhodné připomenout, co polymorfismus a GraphQL jsou.

Lukasz Brzeszcz
E-commerce

Dilemata kybernetické bezpečnosti: Úniky dat

Předvánoční shon je v plném proudu. Při hledání dárků pro své blízké jsou lidé stále častěji ochotni "šturmovat" internetové obchody.

The Codest
Jakub Jakubowicz CTO a spoluzakladatel
Vývoj softwaru

Jednoduchá aplikace Ruby od nuly s Active Record

MVC je návrhový vzor, který rozděluje odpovědnosti aplikace tak, aby se v ní dalo lépe pohybovat. Rails se tímto návrhovým vzorem řídí podle konvence.

The Codest
Damian Watroba Software Engineer

Přihlaste se k odběru naší znalostní databáze a získejte aktuální informace o odborných znalostech z oblasti IT.

    O nás

    The Codest - Mezinárodní společnost zabývající se vývojem softwaru s technologickými centry v Polsku.

    Spojené království - ústředí

    • Kancelář 303B, 182-184 High Street North E6 2JA
      Londýn, Anglie

    Polsko - Místní technologická centra

    • Kancelářský park Fabryczna, Aleja
      Pokoju 18, 31-564 Krakov
    • Brain Embassy, Konstruktorska
      11, 02-673 Varšava, Polsko

      The Codest

    • Home
    • O nás
    • Služby
    • Case Studies
    • Vědět jak
    • Kariéra
    • Slovník

      Služby

    • To Advisory
    • Vývoj softwaru
    • Vývoj backendu
    • Vývoj frontendů
    • Staff Augmentation
    • Vývojáři backendu
    • Cloudoví inženýři
    • Datoví inženýři
    • Další
    • Inženýři QA

      Zdroje

    • Fakta a mýty o spolupráci s externím partnerem pro vývoj softwaru
    • Z USA do Evropy: Proč se americké startupy rozhodly přesídlit do Evropy?
    • Srovnání technických vývojových center v zahraničí: Tech Offshore Evropa (Polsko), ASEAN (Filipíny), Eurasie (Turecko)
    • Jaké jsou hlavní výzvy CTO a CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2026 by The Codest. Všechna práva vyhrazena.

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