dev.to

"Rails Magic" Isn't Magic. How a Request Actually Travels Through Rails.

Rails의 마법은 마법이 아닙니다: 요청은 Rails를 어떻게 통과할까요

Rails의 요청 처리 과정을 Rack 미들웨어부터 라우팅, ActiveRecord, 뷰 렌더링까지 따라가며 관습 뒤에 숨은 Ruby 메커니즘을 설명합니다. Zeitwerk, 커넥션 풀, Strong Parameters도 다뤄 Rails 동작을 디버깅할 때 살펴볼 지점을 제시합니다.

에디터 노트

요청 흐름을 Rack부터 컨트롤러와 뷰까지 연결한 설명은 Rails 디버깅에 필요한 개념을 구체적으로 보여줍니다. 특히 view_assigns가 인스턴스 변수를 새 뷰 객체에 옮긴다는 설명과 GeneratedAttributeMethods를 포함 모듈로 둔다는 댓글의 보완은 메서드 탐색을 이해하는 데 도움이 됩니다. 다만 운영 설정에 관한 설명은 조건을 더 나눠 읽어야 합니다. 커넥션 풀은 프로세스별로 동작하므로 Puma의 스레드 수만 맞추면 충분하다는 식으로 일반화하기 어렵고, 워커 수와 데이터베이스의 전체 연결 한도도 함께 고려해야 합니다. 본문은 프로덕션에서 템플릿까지 모두 미리 읽는다고 쓰지만, 설명한 eager loading의 대상은 애플리케이션 코드와 템플릿을 구분해 볼 필요가 있습니다. Rails 내부 동작을 이해하는 입문 자료로는 유용하지만, 배포 설정의 규칙으로 그대로 받아들이기에는 설명이 단순합니다.

AI 요약

Rails에서 자동으로 처리되는 기능은 프레임워크의 관습과 Ruby의 동작을 조합해 구현합니다. 글은 Puma가 받은 HTTP 요청이 Rails 애플리케이션을 통과해 응답으로 돌아오는 흐름을 따라가며, 흔히 '마법'이라고 부르는 기능의 내부 구조를 설명합니다.

요청 처리와 라우팅

Puma는 TCP 연결에서 HTTP 요청을 읽고 헤더와 본문을 Rack 환경 해시(env)에 담습니다. Rails 애플리케이션은 Rack 인터페이스의 call(env)를 구현하며, 상태 코드와 응답 헤더, 본문으로 이뤄진 세 요소를 반환합니다. 요청은 컨트롤러에 곧바로 도착하지 않습니다. HostAuthorization, SSL, Cookies, ShowExceptions 같은 Rack 미들웨어를 거친 뒤 ActionDispatch의 라우터가 HTTP 메서드와 경로를 매칭합니다. 예를 들어 GET /articles/42 요청은 id 값을 추출하고 ArticlesController#show로 전달됩니다.

모델 속성과 뷰 변수

Article 모델에 열을 선언하지 않아도 article.title 같은 메서드를 호출하는 이유는 ActiveRecord의 스키마 반영 기능에 있습니다. ActiveRecord는 데이터베이스 카탈로그에서 열 이름과 형식, 기본값 등을 읽고, GeneratedAttributeMethods 모듈에 속성 접근 메서드를 만듭니다. 이 모듈을 모델에 포함하므로 모델에서 메서드를 재정의해도 super로 생성된 메서드에 이어 호출할 수 있다고 설명합니다.

컨트롤러의 @article이 뷰에서 보이는 과정도 별도의 전달 단계로 풀이합니다. 액션이 끝나면 view_assigns가 컨트롤러의 인스턴스 변수를 해시로 모읍니다. Rails는 새 ActionView::Base 객체를 만들고 instance_variable_set으로 값을 옮긴 뒤 ERB 템플릿을 렌더링합니다.

파일 자동 로딩과 동시성

글은 예전 Rails의 const_missing 기반 자동 로딩을 소개한 뒤, 현재는 파일 경로와 상수 이름을 대응시키는 Zeitwerk를 사용한다고 설명합니다. 개발 환경에서는 상수에 처음 접근할 때 파일을 읽고, 파일 변경 뒤에는 해당 상수를 다시 로드합니다. 프로덕션에서는 부팅 중 애플리케이션 코드를 미리 읽어들여 요청 중 파일 로딩을 피하고, Puma가 워커 프로세스를 만들기 전에 메모리 페이지를 공유하는 Copy-on-Write 이점도 얻는다고 서술합니다.

Puma의 스레드가 데이터베이스 연결을 동시에 사용하지 않도록 ActiveRecord는 커넥션 풀에서 연결을 빌려주고 요청 처리 뒤 반납합니다. 글은 스레드 수보다 풀이 작으면 연결을 기다리다 ActiveRecord::ConnectionTimeoutError가 날 수 있다고 설명합니다. 제시한 예에서는 스레드가 10개이고 풀이 5개일 때 여섯 번째 요청이 대기합니다.

Strong Parameters

과거처럼 params 해시를 그대로 모델에 전달하면 공격자가 admin 같은 속성을 끼워 넣는 Mass Assignment 문제가 생길 수 있습니다. Rails는 ActionController::Parameters를 사용하며, require(:user)로 필수 입력을 확인하고 permit(:name, :email)로 허용할 속성을 지정하게 합니다. 허용되지 않은 파라미터를 모델 쓰기 메서드에 넘기면 오류를 발생시키는 방식으로 입력을 제한합니다.

dev.to 반응

  • @joel_maniego_665deba00877 — 고맙습니다. Rails 개발자에게 정말 도움이 됐습니다.
  • @alexcodebytes — view_assigns 설명이 제가 읽은 것 중 가장 좋습니다. 여러 해 동안 주니어 개발자들에게 Rails를 설명하면서 @article이 뷰에 어떻게 도달하는지 늘 막혔는데, 이제 정확한 코드 경로인 instance_variable_set("@#{key}", value)를 알게 됐습니다. GeneratedAttributeMethods 모듈이 단지 영리한 방법만은 아니라는 점도 오랜 시간이 지나서야 알았습니다. 이 모듈 덕분에 article.title을 재정의해도 custom #{super} 같은 코드가 동작합니다. 대부분의 ORM은 메서드를 클래스 자체에 만들기 때문에 super가 작동하지 않습니다. Rails는 포함 모듈에 메서드를 두어 super가 데이터베이스 열을 읽는 메서드로 이어지게 합니다. 필요할 때까지 눈에 띄지 않지만 유용한 설계입니다. 프로덕션에서 풀 설정이 맞지 않아 ActiveRecord::ConnectionTimeoutError를 겪은 적이 있나요? 제가 본 팀 대부분은 첫 배포 때 이 부분을 잘못 설정했습니다.

원문: dev.to / 번역·요약: Trawling