window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster가 이미 존재합니다') } else { w.LeadBooster = { q: [], on: 함수 (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: 함수 (n) { this.q.push({ t: 't', n: n }) }, } } })() REST 방식의 API에 하위 리소스 포함 - The Codest
The Codest
  • 회사 소개
  • 서비스
    • 소프트웨어 개발
      • 프론트엔드 개발
      • 백엔드 개발
    • Staff Augmentation
      • 프론트엔드 개발자
      • 백엔드 개발자
      • 데이터 엔지니어
      • 클라우드 엔지니어
      • QA 엔지니어
      • 기타
    • IT 자문
      • 감사 및 컨설팅
  • 산업 분야
    • 핀테크 및 뱅킹
    • E-commerce
    • 애드테크
    • 헬스 테크
    • 제조
    • 물류
    • 자동차
    • IOT
  • 가치
    • CEO
    • CTO
    • 배달 관리자
  • 우리 팀
  • Case Studies
  • 방법 알아보기
    • 블로그
    • 모임
    • 웹 세미나
    • 리소스
채용 정보 연락하기
  • 회사 소개
  • 서비스
    • 소프트웨어 개발
      • 프론트엔드 개발
      • 백엔드 개발
    • Staff Augmentation
      • 프론트엔드 개발자
      • 백엔드 개발자
      • 데이터 엔지니어
      • 클라우드 엔지니어
      • QA 엔지니어
      • 기타
    • IT 자문
      • 감사 및 컨설팅
  • 가치
    • CEO
    • CTO
    • 배달 관리자
  • 우리 팀
  • Case Studies
  • 방법 알아보기
    • 블로그
    • 모임
    • 웹 세미나
    • 리소스
채용 정보 연락하기
뒤로 화살표 뒤로 가기
2022-03-22
소프트웨어 개발

REST 방식의 API에 하위 리소스 포함하기

The Codest

크지슈토프 부제비츠

시니어 Software Engineer

저자 데이터가 있는(또는 없는) 책을 나열하는 책장 앱을 구축할 것입니다.

어떻게 할까요?

저자 데이터가 있는(또는 없는) 책을 나열하는 책장 앱을 구축할 것입니다. 하나의 1TP63인덱스 액션과 몇 가지 시드를 추가합니다. 이 앱은 사용자에게 포함된 항목에 대한 제어 권한을 부여하는 방법을 보여주는 예제 앱입니다. REST형 API의 하위 리소스.

"수락 기준"

  • 사용자가 책을 나열할 수 있습니다.
  • 사용자 통과 가능 포함 쿼리 매개변수를 사용하여 관련 리소스를 로드합니다(작성자).
  • 포함 쿼리 매개변수의 형식은 쉼표로 구분된 단어로 중첩된 리소스를 나타내는 문자열입니다.
  • 어떤 리소스를 어떤 액션에 포함할 수 있는지 정의하는 몇 가지 상수가 있어야 합니다.

도구

우리는 다음을 사용할 것입니다. 블루프린터 를 직렬화 도구로 사용하는 이유는 형식에 구애받지 않고 매우 유연하기 때문입니다. 이것은 Rails의 표준 도구 세트에 추가될 유일한 기능입니다.

앱

예제 앱을 만들어 보겠습니다. 테스트 프레임워크는 저희 범위를 벗어나므로 추가하지 않겠습니다.

레일 새 책장 -T

이제 다음을 만듭니다. 작성자 모델입니다:

레일즈 G 모델 작성자 이름:문자열
#=> active_record 호출
#=> create db/migrate/20211224084524_create_authors.rb
#=> app/models/author.rb 생성

그리고 예약:

레일즈 G 모델 북 저자:참조 제목:문자열
# => 활성 레코드 호출
# => create db/migrate/20211224084614_create_books.rb
# => app/models/book.rb 생성

씨앗이 필요합니다:

# db/seeds.rb

dumas = Author.create(이름: '알렉상드르 뒤마')
lewis = Author.create(이름: 'C.S. Lewis')
martin = Author.create(이름: '로버트 C. 마틴')

Book.create(저자: dumas, 제목: '삼총사')
Book.create(저자: lewis, 제목: '사자, 마녀 그리고 옷장')
Book.create(저자: martin, 제목: '클린 코드')

이제 마이그레이션을 실행하고 DB를 시드할 준비가 되었습니다:

레일즈 DB:마이그레이션 && 레일즈 DB:시드

다음을 추가해 보겠습니다. has_many 도서의 경우 작성자 모델입니다:

# app/models/author.rb

저자 클래스 < ApplicationRecord
  has_many :books
end

이제 데이터를 반환할 컨트롤러를 작성할 차례입니다. 여기서는 API 네임스페이스에 약어를 추가하는 것이므로 먼저 굴절에 약어를 추가해 보겠습니다:

# config/initializers/inflections.rb

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

이제 직렬화기를 다음 위치에 추가해 보겠습니다. 보석 파일:

# 보석 파일에 추가

보석 '블루프린터'

그리고 당연히 설치하세요:

번들 설치

그런 다음 청사진을 만들 수 있습니다:

# 앱/블루프린트/저자_블루프린트.rb

저자 블루프린트 클래스 < 블루프린터::베이스
  식별자 :id

  필드 :이름
end
# 앱/블루프린트/책_블루프린트.rb

책 블루프린트 클래스 < 블루프린터::베이스
  식별자 :id

  필드 :제목

  연관 :저자, 블루프린트: AuthorBlueprint
end

다음에 대한 기본 컨트롤러 추가 API:

# 앱/컨트롤러/api/v1/base_controller.rb

모듈 API
  모듈 V1
    클래스 BaseController < ActionController::API
    end
  end
end

그리고 우리의 북 컨트롤러:

# 앱/컨트롤러/api/v1/books_controller.rb

모듈 API
  모듈 V1
    클래스 BooksController < BaseController
      def index
        books = Book.all

        json을 렌더링합니다: BookBlueprint.render(books)
      end
    end
  end
end

물론 라우팅도 정의해야 합니다:

# config/routes.rb

Rails.application.routes.draw do
  네임스페이스 :API do
    네임스페이스 :v1 do
      resources :books, only: :index
    end
  end
end

지금까지 수행한 작업을 테스트해 보겠습니다:

레일 S 
curl http://localhost:3000/api/v1/books

# => [{"id":1,"author":{"id":1,"name":"알렉상드르 뒤마"},"title":"삼총사"},{"id":2,"author":{"id":2,"name":"C.S. 루이스"},"제목":"사자, 마녀 그리고 옷장"},{"id:3","저자":{"id:3","이름":"로버트 C. 마틴"},"제목":"클린 코드"}]

데이터는 괜찮아 보이는데 로그는 어떻게 되나요?

# 요청 로그(n+1)

2021-12-24 10:19:40 +0100에서 127.0.0.1에 대해 "/api/v1/books" GET 시작
API::V1::BooksController#index로 처리 */*
  도서 로드 (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  저자 로드(0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  저자 로드 (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
  저자 로드 (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  ↳ app/controllers/api/v1/books_controller.rb:7:in `index'
6ms에 200 OK 완료(조회: 0.1ms | 액티브 레코드: 0.4ms | 할당: 3134)

시리얼라이저에서 연관을 사용하여 다음과 같이 도입했습니다. n+1 문제가 있습니다. 이 엔드포인트에서 사용자가 요청하는 항목에 대한 제어 기능을 추가하여 이 문제를 해결하고자 합니다. 따라서 그는 책만 로드하거나 포함 매개 변수를 전달하고 저자도 가져올 수 있어야 하지만, 가급적이면 n+1.

사용자가 포함할 수 있는 도서 어소시에이션에 대한 정보를 보관하는 상수를 정의해 보겠습니다. books1TP63인덱스 액션:

# lib/constants/books/includes.rb

모듈 Constants
  모듈 Books
    모듈 포함
      ALLOWED = {
        index: %i[
          author
        ].freeze
      }.freeze
    end
  end
end

다음으로 빈 객체 상수를 위한 네임스페이스를 정의합니다:

# lib/constants/empty.rb

모듈 Constants
  모듈 Empty
    HASH = {}.freeze
  end
end

다음은 주요 허용 서비스입니다. 코드는 꽤 자명하다고 생각합니다. 마법 에만 할당됩니다. #디폴트_자원_키 그리고 #디폴트_목적. 이 메서드는 레일의 컨트롤러에서 매개변수만 전달하여 permit 포함을 호출할 수 있도록 정의되어 있습니다. 출력은 다음을 저장하는 해시가 됩니다. true 허용된 각 포함 항목에 대해

# 앱/서비스/퍼밋_포함.rb

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

PermitIncludes 클래스
  Empty = Constants::Empty

  COMMA = ','
  슬래시 = '/'

  INCLUDES_FORMAT = /A[a-z]+(,[a-z]+)*z/.freeze
  allowed_includes = {
    books: 상수::책::포함::허용됨
  }.freeze

  def call(params, resources: default_resources_key(params), purpose: default_purpose(params))
    include_sent?(params) 아니면 Empty::HASH를 반환합니다.
    include_valid?(params) 아니면 Empty::HASH를 반환합니다.

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

    allowed_includes.index_with(true)
  end

  private

  def default_resources_key(params)
    raise(ArgumentError, 'params :컨트롤러 키는 문자열이어야 합니다') unless params[:controller].is_a?(String)

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

  def default_purpose(params)
    raise(ArgumentError, 'params :action 키는 문자열이어야 합니다') unless params[:action].is_a?(String)

    params[:action].to_sym
  end

  def includes_sent?(params)
    params.key?(:포함)
  end

  def includes_valid?(params)
    params[:includes].is_a?(String) 아니면 false를 반환합니다.

    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)
    요청된_포함 & 허용된_포함[자원_키][목적]
  end
end

이제 키를 사용하여 포함을 로드하고 포함 해시 자체를 직렬화기에 전달해야 합니다:

# 앱/컨트롤러/api/v1/books_controller.rb

모듈 API
  모듈 V1
    클래스 BooksController < BaseController
      def index
        includes = PermitIncludes.new.call(params)
        books = Book.includes(includes.keys).all

        json을 렌더링합니다: BookBlueprint.render(books, includes: includes)
      end
    end
  end
end

그리고 이것이 바로 직렬화기를 조정하는 방법입니다. 포함된 경우에만 연관을 로드합니다:

# 앱/블루프린트/book_blueprint.rb
책 블루프린트 클래스 (_field_name, _book, options) {
                         옵션[:포함] && 옵션[:포함][:저자]
                       }
end

다시 테스트해 보겠습니다:

레일 S
curl http://localhost:3000/api/v1/books
# => [{"id:1,"title":"삼총사"},{"id:2,"title":"사자, 마녀 그리고 옷장"},{"id:3,"title":"클린 코드"}]
# 요청 로그(도서만 로드)
2021-12-24 10:33:41 +0100에서 ::1에 대해 "/api/v1/books" GET 시작
API::V1::BooksController#index로 처리 */*
   (0.1ms) SELECT sqlite_version(*)
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  도서 로드 (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
9ms에 200 OK 완료(조회: 0.1ms | 액티브 레코드: 0.9ms | 할당: 4548)

좋아요, 포함 항목을 통과하지 못해서 저자가 없는 책만 가져왔습니다. 이제 요청해 보겠습니다:

curl 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1,"author":{"id:1,"name":"알렉상드르 뒤마"},"title":"삼총사"},{"id:2,"author":{"id:2,"name":"C.S.. 루이스"},"제목":"사자, 마녀 그리고 옷장"},{"id:3,"저자":{"id:3,"이름":"로버트 C. 마틴"},"제목":"클린 코드"}]% 
# 요청 로그(n+1 제거)

2021-12-24 10:38:23 +0100에서 ::1에 대해 "/api/v1/books?includes=author" GET 시작
API::V1::BooksController#index로 처리 */*
  파라미터: {"includes"=>"author"}
  도서 로드 (0.1ms) SELECT "books".* FROM "books"
  ↳ app/controllers/api/v1/books_controller.rb:8:in `index'
  저자 로드 (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'
17ms에 200 OK 완료(조회: 0.1ms | 액티브 레코드: 0.7ms | 할당: 7373)

멋지네요! 연관성을 로드하고 제거했습니다. n+1 문제를 해결했습니다. 이 서비스는 모든 리소스에 사용할 수 있으며, 허용되는 상수를 적절한 형식으로 추가하고 해당 상수를 허가 포함::ALLOWED_INCLUDES.

연관을 포함하면 많은 메모리를 '먹어치울' 수 있으므로 페이지 매김과 함께 사용해야 한다는 점을 기억해야 합니다(그리고 주의해야 합니다).

관련 문서

핀테크

루비를 가장 잘 활용하는 5가지 예

루비로 무엇을 할 수 있는지 궁금한 적이 있으신가요? 글쎄요, 아마도 하늘이 한계일 것입니다만, 어느 정도 알려진 몇 가지 사례에 대해 이야기하게 되어 기쁩니다...

The Codest
파웰 무진스키 Software Engineer
소프트웨어 개발

루비 및 그래프QL의 다형성

이 글에서는 GraphQL에서 다형성의 사용에 대해 설명하겠습니다. 그러나 시작하기 전에 다형성과 GraphQL이 무엇인지 상기할 필요가 있습니다.

루카스 브르제슈치
E-commerce

사이버 보안 딜레마: 데이터 유출

크리스마스 전 러시가 한창입니다. 사랑하는 사람들을 위한 선물을 찾기 위해 온라인 상점을 '습격'하려는 사람들이 점점 더 많아지고 있습니다.

The Codest
야쿱 야쿠보비치 CTO & 공동 설립자
소프트웨어 개발

활성 레코드로 처음부터 간단한 루비 애플리케이션 만들기

MVC는 애플리케이션의 책임을 분담하여 더 쉽게 이동할 수 있도록 하는 디자인 패턴입니다. Rails는 관례적으로 이 디자인 패턴을 따릅니다.

The Codest
데미안 와트로바 Software Engineer

지식창고를 구독하고 IT 분야의 전문 지식을 최신 상태로 유지하세요.

    회사 소개

    The Codest - 폴란드에 기술 허브를 둔 국제 소프트웨어 개발 회사입니다.

    영국 - 본사

    • 사무실 303B, 182-184 하이 스트리트 노스 E6 2JA
      영국 런던

    폴란드 - 현지 기술 허브

    • 파브리츠나 오피스 파크, 알레야
      포코주 18, 31-564 크라쿠프
    • 뇌 대사관, 콘스트럭터스카
      11, 02-673 바르샤바, 폴란드

      The Codest

    • 홈
    • 회사 소개
    • 서비스
    • Case Studies
    • 방법 알아보기
    • 채용 정보
    • 사전

      서비스

    • IT 자문
    • 소프트웨어 개발
    • 백엔드 개발
    • 프론트엔드 개발
    • Staff Augmentation
    • 백엔드 개발자
    • 클라우드 엔지니어
    • 데이터 엔지니어
    • 기타
    • QA 엔지니어

      리소스

    • 외부 소프트웨어 개발 파트너와의 협력에 대한 사실과 오해
    • 미국에서 유럽으로: 미국 스타트업이 유럽으로 이전을 결정하는 이유
    • 테크 오프쇼어 개발 허브 비교: 테크 오프쇼어 유럽(폴란드), 아세안(필리핀), 유라시아(터키)
    • CTO와 CIO의 주요 과제는 무엇인가요?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • 웹사이트 이용 약관

    저작권 © 2025 by The Codest. 모든 권리 보유.

    ko_KRKorean
    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 ko_KRKorean