[번역] 레일스 엔진 만들기

Hschoi han Lucius.choi님이 일 년 이상전에 작성함. 0 531 
원문 : [How to Build a Ruby on Rails Engine](http://jakeyesbeck.com/2016/03/20/how-to-build-a-ruby-on-rails-engine/) --- 레일스 엔진이란 규모가 더 큰 레일스 애플리케이션을 보조하기 위한 소형화된 애플리케이션을 말한다. 애플리케이션으로부터 특정 기능이 별개로 존재할 수 있다면, 해당 기능을 엔진형태로 만들어, 놀라울 정도의 캡슐화를 구현할 수 있다. 최근에, 라우팅에 관련된 어려움을 덜어주기 위해 `Passages Ruby on Rails Engine`을 작성해서 젬으로 만들었다. 이 젬을 사용해서 레일스 엔진을 만드는 과정을 소개할 것이다. ## 엔진 속도 올리기 하나의 엔진을 만드는 방법은 두가지가 있다. 한가지는 내장된 제너레이터를 사용해서 디렉토리와 더미 클래스를 생성하는 것이다. 이 제너레이터는 일반적인 레일스 제너레이터와 동일하게 동작한다. 이와 같이 하나의 엔진을 만들기 위해서 제너레이터에 내장된 `플러그인(plugins)`를 사용할 것이다. ``` $ rails plugin new passages --mountable ``` > 주의 : 레일스에서 엔진과 플러그인은 정확히 동일한 것은 아니지만 플러그인 제너레이터에 `--mountable` 플래그를 지정해 주면 완전한 엔진을 생성하게 된다. 엔진을 생성하는 다른 방법은 직접 필요한 디렉토리와 파일을 생성하는 것이다. 이런 방식으로 `Passages`엔진을 만들었고 디렉토리 구조는 아래와 같다. ``` |-app |--controllers |---passages |----<controller directories> |--views |---passages |----<views directories> |-config |--routes.rb |--initializers |---assets.rb |-lib |--passages |---engine.rb ``` 여기에는 레일스 제너레이터가 생성하는 디렉토리 몇개가 빠져 있다(`models`, `helpers`, `mailer`). `Passages` 엔진에서는 이 디렉토리들이 필요하지 않았다. 이 중에서 가장 중요한 파일은 `engine.rb` 이다. 이 파일에서 엔진을 정의하게 되고 나중에 엔진이 사용하게 될 기능을 추가할 때 사용하게 될 것이다. 이 파일의 내용은 아래와 같다. ``` module Passages class Engine < ::Rails::Engine isolate_namespace(Passage) end end ``` 이 파일에서 흥미로운 부분은 `isolated_namespace` 메소드 호출이다. 이 메소드는 자신의 컨트롤러, 헬퍼메소드, 뷰, 라우트, 그리고 애플리케이션과 엔진 사이에 공유하게 되는 리소스들까지도 분리하므로써 엔진을 완전하게 캡슐화하도록 해준다. 이와 같은 완벽한 캡슐화 덕분에 엔진은 애플리케이션의 클래스 또는 모듈명과의 충돌을 걱정할 필요가 없게 된다. 또한, 엔진은 네임스페이스로 자신의 이름을 설정하여 나중에 아래와 같이 접근할 수 있게 된다. ``` Passages::Engine.engine_name ``` `Passages` 엔진은 젬 형태를 가지기 때문에, `lib` 디렉토리에 `passages.rb`이라는 파일을 가지게 된다. ``` module Passages end require 'passages/engine' require 'controllers/passages/routes_controller' ``` 이 파일은 `Passages` 모듈을 정의하고 엔진을 `require` 한다. 이것은 젬 로직에 대한 진입점이 된다. ## 완벽한 캡슐화 좋은 레일스 엔진을 디자인할 때 무엇보다 중요한 것은 캡슐화 정도다. 애플리케이션은 여러가지 보조 엔진들로부터 발생하는 부작용이 있어서는 안된다. 단지 애플리케이션을 보조하기 위해 새로운 기능만을 제공해 주어야 한다. 이런 캡슐화를 구현하기 위해서, 레일스는 네임스페이스를 위한 모듈과 해당 디렉토리 구조 내에 엔진에서 사용하는 컨트롤러와 뷰, 그리고 각종 애셋을 포함해야할 필요가 있다. ## 컨트롤러 `Passages` 엔진에서는 `RoutesController`가 이러한 디렉토릴 구조를 잘 설명해 준다. ``` |-app |--controllers |---passages |----routes_controller.rb ``` ``` module Passages class RoutesController < ActionController::Base # ... end end ``` 뷰와 애셋에 대해서도 동일한 폴더 구조를 사용한다. ## 라우트 물론, 컨트롤러를 라우트에서 사용하지 않는다면, 무슨 소용이 있겠는가? 레일스 엔진에서도 자신만의 라우트를 정의할 수 있다. 컨트롤러와는 달리, `routes.rb` 파일은 `passage` 폴더에도 `module Passages` 어디에도 포함되지 않는다. ``` |-config |--routes.rb ``` ``` Passages::Engine.routes.draw do root to: 'routes#index' end ``` 일반 레일스 애플리케이션에서는 라우트 파일의 첫번째 줄에 `Rails.application.routes.draw` 코드가 표시되지만 엔진 내에서는 `Rails.application` 부분이 엔진 이름으로 대체된다. 간단한 라우트를 작성한 상태에서 `Passages` 엔진을 사용하는 애플리케이션에서 `rake routes`를 실행하면 아래와 같이 보이게 된다. ``` $ rake routes Prefix Verb URI Pattern passages /passages users GET /users POST /users new_user GET /users/new edit_user GET /users/:id/edit user GET /users/:id PATCH /users/:id PUT /users/:id DELETE /users/:id Routes for Passages::Engine: root GET / passages/routes#index ``` > 노트 : 위의 결과는 `/passages` 경로에 엔진이 마우트되어 있는 것으로 가정한 것이다. `Passages` 라우트는 일반 라우트와 구분하기 위해서 별도로 구분해서 표시한다. 위에서 언급했던 `isolate_namespace` 메소드를 기억할 것이다. 별도의 네임스페이스를 사용하지 않고 애플리케이션 라우트를 찾게되면 이로 인해 발생할 수 있는 부작용 중의 한가지를 보게 될 것이다. 이 메소드를 코멘트처리할 경우 ``` module Passages class Engine < ::Rails::Engine # isolate_namespace(Passages) end end ``` `rake routes`를 실행하면 다른 결과를 보여주게 된다. ``` $ rake routes Prefix Verb URI Pattern passages_engine /passages users GET /users POST /users new_user GET /users/new edit_user GET /users/:id/edit user GET /users/:id PATCH /users/:id PUT /users/:id DELETE /users/:id Routes for Passages::Engine: root GET / routes#routes ``` `Passages` 엔진의 `root` 라우트는 더 이상 네이스페이스가 앞에 표시되지 않게 된 것을 주의해야 한다. 이렇게 되므로써 레일스는 `routes_controller`를 엉뚱한 곳에서 찾게 된다. ``` ActionController::RoutingError (uninitialized constant RoutesController): ``` 어떤 애플리케이션에서는 큰 문제가 아닐 수도 있지만, 엔진이 최상위 컨트롤러를 호출한다는 것이 우려스러운 것이다. 만일 애플리케이션이 `RoutesController`를 가진다면 `Passages` 엔진은 자신의 것이 아닌 최상위의 컨트롤러를 호출할 수 있게 된다. 또한 반대의 경우, 별도의 네임스페이스가 없는 엔진의 경우 애플리케이션의 중요한 컨트롤러를 오버라이드하여 오류를 유발할 수도 있을 것이다. ## 애셋 컨트롤러와 뷰같이, 엔진의 애셋 역시 엔진이름의 폴더 아래에 위치한다. ``` |-app |--assets |---javascripts |----passages |-----application.js |---stylesheets |----passages |-----application.css ``` 이러한 디렉토리 구성은 엔진 내의 레이아웃과 다른 뷰 파일들이 애플리케이션의 `application.js`와 `application.css` 파일을 참조하지 않고 필요로하는 파일만을 로드할 수 있도록 해 준다. ``` <%= stylesheet_link_tag 'passages/application', media: 'all' %> <%= javascript_include_tag 'passages/application' %> ``` 컴파일된 애셋을 사용할 수 있도록 하기 위해서, 동일한 `enging.rb` 파일에 몇줄 더 추가해 줄 필요가 있다. ``` module Passages class Engine < ::Rails::Engine isolate_namespace(Passages) initializer("passages.assets.precompile") do |app| app.config.assets.precompile += [ 'application.css', 'application.js' ] end end end ``` `initializer` 코드라인은 `rake assets:precompile` 명령을 실행하여 애셋이 컴파일될 때 연관 `railties` 내에 초기화 작업을 생성하여 실행되도록 해준다. 이로써 애플리케이션은 자신의 애셋 뿐만 아니라 `Passages` 엔진의 애셋까지도 문제없이 컴파일할 수 있게 된다. ## 엔진 마운트 레일스 엔진은 애플리케이션에서 사용하기 전에 먼저 마운트될 필요가 있다. 이 작업은 애플리케이션의 `routes.rb` 파일에서 한다. ``` Rails.application.routes.draw do mount Passages::Engine, at: '/passages' end ``` `/passages` 문자열은 원하는 것으로 변경할 수 있는데, 엔진이 이에 대해서는 전혀 문제 삼지 않기 때문이다. 다른 방법으로는, 동일한 `engine.rb` 파일 내 동일한 `initializer` 메소드를 이용하여 엔진 스스로 마운트될 수 있게 할 수 있다. ``` module Passages class Engine < ::Rails::Engine isolate_namespace(Passages) initializer('passages', after: :load_config_initializers) do |app| Rails.application.routes.prepend do mount Passages::Engine, at: '/passages' end end end end ``` 이 경우, `initializer`는 설정 초기화 메소드가 로드된 후 어떤 작업을 추가할 수 있는 시점을 제공해 준다. 이 때 `initializer`는 애플리케이션에 `Rails.application.routes.prepend` 코드를 추가하고 애플리케이션 라우트 파일 최상단에 마운트 코드라인을 추가해 준다. 애플리케이션 라우트 파일 시작부분에 엔진 마운트가 추가되기 때문에, 엔진의 마운트 경로(`/passages`)가 이후에 추가되는 애플리케이션의 동일한 이름으로 덥혀 씌여 질 수 있다. 애플리케이션이 `Passages` 엔진을 마우트한 이후에야 `/passages` 경로로 이동할 수 있게 된다. 이것은 일반적인 레일스 애플리케이션 요청과 같이 `Passages` 엔진이 처리하게 될 것이다. ## 사용시 주의사항 자동으로 마운트되는 엔진은 좋아 보이지만 자동 마운트 여부는 사용자에게 맡기는 것이 최선일 수 있다. 이를 위한 해결법은 자동 마운트 옵션을 지정할 수 있도록 기능을 추가하는 것이다. 이와 같은 로직을 일종의 설정 변수로 지정할 수 있게 하여 이 엔진을 사용하는 개발자가 명시적으로 옵션을 지정한 경우에만 자동 마운트 기능을 선택할 수 있도록 한다. ## 다음 단계 이와 같은 기본 골격하에, 일반 레일스 애플리케이션을 만드는 것 처럼 새로운 엔진을 만들 수 있다. 컨트롤러와 해당 뷰 파일들을 만든 후 적당한 네임스페이스와 폴더하에 둘 수 있다. 이 컨트롤러들을 사용하는 라우트 또한 추가할 수 있다. 애플리케이션과 엔진이 동일한 ORM(또는 적어도 호환성이 있는 ORM)을 사용한다는 가정하에 모델까지도 생성할 수 있다. `Passages` 엔진은 독자적으로 실행될 수 있는 젬으로 만들어졌다. 일반 젬을 만드는 과정과 동일하게 만들어졌다. `.gemspec` 파일이 만들어졌고 하나의 버전을 가지는 젬으로 `Rubygems.org`로 최종 배포되었다. `Passages` 엔진을 사용하고자 할 때는 애플리케이션의 Gemfile에 `gem 'passages'` 코드라인을 추가하여 설치할 수 있다. 이와 같은 개발형식은 독자적으로 실행가능한 레일스 엔진을 만들 때 사용할 수 있다. 그러나, 이 엔진을 사요할 수 있는 특정 레일스 버전을 선택하는 것이 현명할 수 있다. 예를 들어 `Passages` 엔진은 레일스 4.X 버전을 지원하기 위해서 만들어 졌기 때문에 레일스 3.X 에서는 호환성이 없다. ## 내구성 더 나아가 뷰 파일, 초기화 파일, 마이그레이션, 모델을 생성하는 일들을 일반 레일스 애플리케이션에서와 동일한 방식으로 할 수 있다. 이 가이드에서 `Passges` 엔진의 모든 것을 설명할 수 없지만 소스 코드를 읽으면 더 많은 정보를 알 수 있을 것이다. 또한, 저자는 `github`로 새로운 이슈를 올리고 새로운 기능을 `pull request`하는 열정적인 기여자를 찾고 있다. --- 역자: 최효성

Comments (0)