window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versjon: 2, } ;(function () { var w = vindu if (w.LeadBooster) { console.warn('LeadBooster finnes allerede') } 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 }) }, } } })() Inkludering av underressurser i et REST-lignende API - The Codest
The Codest
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Industrier
    • Fintech og bankvirksomhet
    • E-commerce
    • Adtech
    • Helseteknologi
    • Produksjon
    • Logistikk
    • Bilindustrien
    • IOT
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
Pil tilbake GÅ TILBAKE
2022-03-22
Programvareutvikling

Inkludering av underressurser i et REST-lignende API

The Codest

Krzysztof Buszewicz

Senior Software Engineer

Vi skal lage en bokhylle-app som viser bøker med (eller uten) forfatterdata.

Hva skal vi gjøre?

Vi skal lage en bokhylle-app som viser bøker med (eller uten) forfatterdata. Det vil være en enkelt #index action og noen frø. Dette vil være et eksempel på en app som viser hvordan du kan gi en bruker kontroll over inkluderte underressurser i et REST-aktig API.

"Akseptkriterier"

  • Brukeren kan liste opp bøkene.
  • Brukeren kan passere inkluderer spørringsparameter for å laste inn tilknyttede ressurser (forfatter).
  • inkluderer har spørringsparameteren formatet string: kommaseparerte ord som representerer nestede ressurser.
  • Vi bør ha noen konstanter som definerer hvilke ressurser som kan inkluderes for hvilke handlinger.

Verktøy

Vi vil bruke blåskriver som serializer, fordi den er formatuavhengig og ganske fleksibel. Dette er den eneste perlen vi vil legge til i Rails' standardverktøysett.

Appen

La oss lage et eksempel på en app. Vi legger ikke til et testrammeverk, siden det er utenfor vårt område.

skinner ny bokhylle -T

Opprett nå Forfatter modell:

rails g modell forfatternavn:string
#=> invoke active_record
#=> create db/migrate/20211224084524_create_authors.rb
#=> create app/models/author.rb

Og Bok:

rails g model book author:referanser title:string
# => invoke active_record
# => create db/migrate/20211224084614_create_books.rb
# => create app/models/book.rb

Vi trenger noen frø:

# db/seeds.rb

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

Book.create(forfatter: dumas, tittel: 'De tre musketerer')
Book.create(forfatter: lewis, tittel: 'Løven, heksa og klesskapet')
Book.create(forfatter: martin, tittel: 'Clean Code')

Og nå er vi klare til å kjøre migreringer og seed db-en:

rails db:migrate && rails db:seed

La oss legge til har_mange for bøker i Forfatter modell:

# app/models/author.rb

class Forfatter < ApplicationRecord
  has_many :books
end

Det er på tide å skrive en kontroller som returnerer dataene våre. Vi vil bruke API navnerom, så la oss først legge til et akronym til bøyninger:

# config/initializers/inflections.rb

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

Ok, la oss legge til serialisatoren vår til Gemfile:

# Legg til i Gemfile

gem 'blueprinter'

Og selvfølgelig installere det:

pakkeinstallasjon

Så kan vi bygge våre egne tegninger:

# app/blueprints/author_blueprint.rb

class AuthorBlueprint < Blueprinter::Base
  identifikator :id

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

class BookBlueprint < Blueprinter::Base
  identifikator :id

  fields :title

  association :author, blueprint: AuthorBlueprint
end

Legg til en basekontroller for API:

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

modul API
  modul V1
    class BaseController < ActionController::API
    end
  end
end

Og utkastet til versjonen av vår BooksController:

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

modul API
  modul V1
    class BooksController < BaseController
      def index
        books = Book.all

        render json: BookBlueprint.render(bøker)
      end
    end
  end
end

Vi må selvfølgelig også definere ruting:

# config/routes.rb

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

La oss teste det vi har gjort så langt:

skinner s 
krølle http://localhost:3000/api/v1/books

# => [{"id":1,"author":{"id":1,"name":"Alexandre Dumas"},"title":"De tre musketerer"},{"id":2,"author":{"id":2,"name":"C. S. Lewis"},"title":"Løven, heksa og klesskapet"},{"id":3,"author":{"id":3,"name":"C.S. Lewis"},"title":"Løven, heksa og klesskapet"},{"id":3,"author":{"id":3,"name":"Robert C. Martin"},"title":"Clean Kode"}]

Dataene ser ut til å være i orden, men hva med loggene?

#-forespørselslogger (n+1)

Startet GET "/api/v1/books" for 127.0.0.1 kl 2021-12-24 10:19:40 +0100
Behandlet av API::V1::BooksController#index som */*
  Bokinnlasting (0,1 ms) 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'
Fullført 200 OK på 6 ms (Views: 0,1 ms | ActiveRecord: 0,4 ms | Allokeringer: 3134)

Ved å bruke assosiasjon i serialisatorene våre introduserte vi n+1 problem. Vi ønsker å eliminere det ved å gi brukeren kontroll over hva han ber om i dette endepunktet. Så han bør enten kunne laste inn bare bøker, eller sende inn parameteren includes og få forfattere i tillegg, men helst uten n+1.

La oss definere en konstant som vil holde informasjon om hvilke assosiasjoner til bøker brukeren kan inkludere i bøker#index handling:

# lib/constants/books/includes.rb

modul Konstanter
  modul Books
    modul Inkluderer
      ALLOWED = {
        index: %i[
          forfatter
        ].freeze
      }.freeze
    slutt
  slutt
slutt

Deretter definerer vi et navneområde for tomme objektkonstanter:

# lib/constants/empty.rb

modul Konstanter
  modul Empty
    HASH = {}.freeze
  end
slutt

Og her er hovedtjenesten vår for tillatelser. Jeg tror koden er ganske selvforklarende, noen deler av magi er kun allokert i #standard_ressursnøkkel og #standard_formål. Disse metodene er definert slik at vi kan kalle permit includes ved å sende bare params i rails' kontrollere. Utdataene vil være hashen som lagrer ekte for hver tillatt inkludering.

# app/services/permit_includes.rb

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

klasse PermitIncludes
  Empty = Konstanter::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)
  slutt

  privat

  def default_resources_key(params)
    raise(ArgumentError, 'params :controller-nøkkel må være en streng') unless params[:controller].is_a?(String)

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

  def default_purpose(params)
    raise(ArgumentError, 'params :action-nøkkelen må være en streng') 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 med mindre 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

Nå må vi bruke nøklene til å laste inn includes og sende selve inlcudes-hashingen til serialisereren:

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

modul API
  modul V1
    class 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

Og det er slik vi må tilpasse serialisereren vår - vi laster bare inn assosiasjonen hvis den er inkludert:

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

La oss teste det igjen:

skinner s
curl http://localhost:3000/api/v1/books
# => [{"id":1,"title":"De tre musketerer"},{"id":2,"title":"Løven, heksa og klesskapet"},{"id":3,"title":"Clean Code"}]
#-forespørselslogger (vi laster bare inn bøker)
Startet GET "/api/v1/books" for ::1 kl 2021-12-24 10:33:41 +0100
Behandlet av API::V1::BooksController#index som */*
   (0.1ms) SELECT sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  Bokinnlasting (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Fullført 200 OK på 9ms (Views: 0,1ms | ActiveRecord: 0,9ms | Allokeringer: 4548)

Bra, vi har ikke bestått inkluderingen, så vi fikk bare bøker uten forfattere. La oss nå be om dem:

curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1,"author":{"id":1,"name":"Alexandre Dumas"},"title":"De tre musketerer"},{"id":2,"author":{"id":2,"name":"C.S. Lewis"},"title":"Løven, heksa og klesskapet"},{"id":3,"author":{"id":3,"name":"C.S. Lewis"},"title":"Løven, heksa og klesskapet"},{"id":3,"author":{"id":3,"name":"Robert C. Martin"},"title":"Clean Code"}]% 
#-forespørselslogger (eliminert n+1)

Startet GET "/api/v1/books?includes=author" for ::1 kl 2021-12-24 10:38:23 +0100
Behandlet av API::V1::BooksController#index som */*
  Parametere: {"includes"=>"author"}
  Bokinnlasting (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]] [["id", 1], ["id", 2], ["id", 3]]
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
Fullført 200 OK på 17 ms (Visninger: 0,1 ms | ActiveRecord: 0,7 ms | Allokeringer: 7373)

Kult! Vi fikk foreningen lastet og eliminert n+1 problem. Tjenesten kan brukes for alle ressurser, alt vi ønsker å gjøre er å legge til tillatte konstanter i riktig format og legge dem til PermitIncludes::ALLOWED_INCLUDES.

Vi må huske at dette sannsynligvis bør brukes med paginering (og forsiktighet) fordi det å inkludere assosiasjoner kan "spise" mye minne.

Relaterte artikler

Fintech

5 eksempler på hvordan Ruby kan brukes på best mulig måte

Har du noen gang lurt på hva vi kan gjøre med Ruby? Det er nok ingen grenser, men vi snakker gjerne om noen mer eller mindre kjente tilfeller...

The Codest
Pawel Muszynski Software Engineer
Programvareutvikling

Polymorfisme i Ruby og GraphQL

I denne artikkelen vil jeg presentere bruken av polymorfisme i GraphQL. Før jeg begynner, er det imidlertid verdt å minne om hva polymorfisme og GraphQL er.

Lukasz Brzeszcz
E-commerce

Dilemmaer knyttet til cybersikkerhet: Datalekkasjer

Førjulsrushet er i full gang. På jakt etter gaver til sine kjære er folk stadig mer villige til å "storme" nettbutikkene

The Codest
Jakub Jakubowicz CTO og medgrunnlegger
Programvareutvikling

En enkel Ruby-applikasjon fra bunnen av med Active Record

MVC er et designmønster som deler opp ansvarsområdene i en applikasjon for å gjøre den enklere å flytte rundt på. Rails følger dette designmønsteret per konvensjon.

The Codest
Damian Watroba Software Engineer

Abonner på vår kunnskapsbase og hold deg oppdatert på ekspertisen fra IT-sektoren.

    Om oss

    The Codest - Internasjonalt programvareutviklingsselskap med teknologisentre i Polen.

    Storbritannia - Hovedkvarter

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

    Polen - Lokale teknologisentre

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

      The Codest

    • Hjem
    • Om oss
    • Tjenester
    • Casestudier
    • Vet hvordan
    • Karriere
    • Ordbok

      Tjenester

    • Det rådgivende
    • Programvareutvikling
    • Backend-utvikling
    • Frontend-utvikling
    • Staff Augmentation
    • Backend-utviklere
    • Ingeniører i skyen
    • Dataingeniører
    • Annet
    • QA-ingeniører

      Ressurser

    • Fakta og myter om samarbeid med en ekstern programvareutviklingspartner
    • Fra USA til Europa: Hvorfor velger amerikanske oppstartsbedrifter å flytte til Europa?
    • Sammenligning av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinene), Eurasia (Tyrkia)
    • Hva er de største utfordringene for CTO-er og CIO-er?
    • The Codest
    • The Codest
    • The Codest
    • Retningslinjer for personver
    • Vilkår for bruk av nettstedet

    Opphavsrett © 2025 av The Codest. Alle rettigheter forbeholdt.

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