1 Engine이란?
Engine은 host application에 기능을 제공하는 작은 application으로 볼 수 있습니다. Rails application은 실제로 Rails::Application
클래스가 Rails::Engine
에서 많은 동작을 상속받는 "강화된" engine일 뿐입니다.
따라서 engine과 application은 이 가이드 전반에 걸쳐 보게 될 미묘한 차이점을 제외하면 거의 동일한 것으로 생각할 수 있습니다. Engine과 application은 또한 공통적인 구조를 공유합니다.
Engine은 또한 plugin과도 밀접한 관련이 있습니다. 둘 다 공통적인 lib
디렉토리 구조를 공유하며, rails plugin new
generator를 사용하여 생성됩니다. 차이점은 engine이 Rails에서 "full plugin"으로 간주된다는 것입니다(generator 명령어에 전달되는 --full
옵션으로 표시됨). 여기서는 실제로 --full
의 모든 기능과 추가 기능이 포함된 --mountable
옵션을 사용할 것입니다. 이 가이드에서는 이러한 "full plugin"을 단순히 "engine"이라고 부를 것입니다. engine은 plugin이 될 수 있고, plugin은 engine이 될 수 있습니다.
이 가이드에서 생성할 engine의 이름은 "blorgh"입니다. 이 engine은 host application에 블로깅 기능을 제공하여 새로운 글과 댓글을 작성할 수 있게 해줍니다. 이 가이드의 초반부에서는 engine 자체 내에서만 작업하지만, 후반부에서는 이를 application에 어떻게 연결하는지 보게 될 것입니다.
Engine은 host application으로부터 격리될 수도 있습니다. 이는 application이 articles_path
와 같은 routing helper가 제공하는 경로를 가질 수 있고, 동일하게 articles_path
라고 불리는 경로를 제공하는 engine을 사용할 수 있다는 것을 의미합니다.
articles_path
와 두 개가 충돌하지 않을 것입니다. 이와 함께 controllers, models 그리고 테이블 이름도 네임스페이스가 지정됩니다. 이 가이드의 뒷부분에서 이를 수행하는 방법을 살펴보겠습니다.
애플리케이션이 engine보다 항상 우선순위를 가져야 한다는 점을 항상 명심해야 합니다. 애플리케이션은 자신의 환경에서 일어나는 일을 최종적으로 결정하는 객체입니다. engine은 급격한 변화를 주기보다는 단순히 기능을 향상시키는 역할만 해야 합니다.
다른 engine의 예시를 보려면 부모 애플리케이션에 인증 기능을 제공하는 Devise나 포럼 기능을 제공하는 Thredded를 확인해보세요. e-커머스 플랫폼을 제공하는 Spree와 CMS engine인 Refinery CMS도 있습니다.
마지막으로, engine은 James Adam, Piotr Sarnacki, Rails Core Team 그리고 다른 많은 분들의 노력이 없었다면 불가능했을 것입니다. 이분들을 만나게 된다면 감사 인사를 잊지 마세요!
2 Engine 생성하기
engine을 생성하려면 plugin generator를 실행하고 필요에 따라 적절한 옵션을 전달해야 합니다. "blorgh" 예제의 경우, "mountable" engine을 생성해야 하며 터미널에서 다음 명령을 실행하세요:
$ rails plugin new blorgh --mountable
plugin generator의 전체 옵션 목록은 다음을 입력하여 확인할 수 있습니다:
$ rails plugin --help
위 명령은 Rails 플러그인 제너레이터와 관련된 도움말을 보여줍니다.
--mountable
옵션은 generator에게 "mountable"하고 namespace가 격리된 engine을 생성하도록 지시합니다. 이 generator는 --full
옵션과 동일한 기본 구조를 제공합니다. --full
옵션은 generator에게 다음과 같은 기본 구조를 포함한 engine을 생성하도록 지시합니다:
app
디렉토리 트리config/routes.rb
파일:Rails.application.routes.draw do end
표준 Rails 애플리케이션의
config/application.rb
파일과 동일한 기능을 하는lib/blorgh/engine.rb
파일:module Blorgh class Engine < ::Rails::Engine end end
--mountable
옵션은 --full
옵션에 다음을 추가합니다:
- Asset manifest 파일들(
blorgh_manifest.js
와application.css
) - Namespace가 적용된
ApplicationController
stub - Namespace가 적용된
ApplicationHelper
stub - Engine을 위한 레이아웃 view 템플릿
config/routes.rb
의 namespace 격리:Blorgh::Engine.routes.draw do end
lib/blorgh/engine.rb
의 namespace 격리:module Blorgh class Engine < ::Rails::Engine isolate_namespace Blorgh end end
추가로, --mountable
옵션은 generator에게 test/dummy
에 위치한 더미 테스트 애플리케이션 내부에 engine을 마운트하도록 지시합니다.
다음을 dummy application의 routes 파일인 test/dummy/config/routes.rb
에 추가합니다:
mount Blorgh::Engine => "/blorgh"
2.1 Inside an Engine
2.1.1 Critical Files
새로 만든 엔진의 루트 디렉토리에는 blorgh.gemspec
파일이 있습니다.
나중에 이 엔진을 애플리케이션에 포함시킬 때는 Rails 애플리케이션의 Gemfile
에 다음 라인을 추가하여 수행합니다:
gem "blorgh", path: "engines/blorgh"
평소처럼 bundle install
을 실행하는 것을 잊지 마세요. Gemfile
내에 gem으로 명시함으로써, Bundler는 blorgh.gemspec
파일을 파싱하고 lib
디렉토리 내의 lib/blorgh.rb
파일을 require하면서 로드합니다. 이 파일은 blorgh/engine.rb
파일(lib/blorgh/engine.rb
에 위치)을 require하고 Blorgh
라는 기본 모듈을 정의합니다.
require "blorgh/engine"
module Blorgh
end
일부 engine은 자신의 engine에 대한 전역 설정 옵션을 이 파일에 넣기로 선택합니다. 이는 꽤 좋은 아이디어입니다. 따라서 설정 옵션을 제공하고 싶다면, engine의 module
이 정의된 파일이 이상적입니다. 메서드들을 module 내부에 배치하면 됩니다.
lib/blorgh/engine.rb
내부에는 engine의 기본 클래스가 있습니다:
module Blorgh
class Engine < ::Rails::Engine
isolate_namespace Blorgh
end
end
이 코드는 Engine을 격리된 namespace로 분리합니다. 이 코드를 사용하면 컨트롤러, 헬퍼와 모델은 Engine 내에서 정의되고, Engine의 module(Blorgh
)을 통해 네임스페이스가 지정됩니다.
Rails::Engine
클래스를 상속함으로써, 이 gem은 Rails에게 지정된 경로에 engine이 있다는 것을 알리고, 모델, mailer, controller, view를 위한 load path에 engine의 app
디렉토리를 추가하는 등의 작업을 수행하여 애플리케이션 내부에 engine을 올바르게 마운트합니다.
여기서 isolate_namespace
메서드는 특별한 주목을 받을 만합니다. 이 호출은 controller, model, route 및 기타 구성 요소들을 애플리케이션 내의 유사한 구성 요소들로부터 분리하여 자체 namespace로 격리하는 역할을 합니다. 이것이 없다면, engine의 구성 요소가 애플리케이션으로 "유출"되어 원치 않는 혼란을 일으키거나, 중요한 engine 구성 요소가 애플리케이션 내의 동일한 이름을 가진 것들에 의해 재정의될 수 있습니다. 이러한 충돌의 예 중 하나는 helper입니다. isolate_namespace
를 호출하지 않으면, engine의 helper들이 애플리케이션의 controller에 포함될 것입니다.
Engine
클래스 정의 내에 isolate_namespace
라인을 유지하는 것이 매우 권장됩니다. 이것이 없으면, engine에서 생성된 클래스들이 애플리케이션과 충돌할 수 있습니다.
이 namespace 격리가 의미하는 것은 bin/rails generate model article
과 같은 bin/rails generate model
호출로 생성된 모델이 Article
이라고 불리지 않고, 대신 namespace가 지정되어 Blorgh::Article
이라고 불린다는 것입니다. 또한, 모델의 테이블도 namespace가 지정되어 단순히 articles
가 아닌 blorgh_articles
가 됩니다. 모델 namespace 지정과 유사하게, ArticlesController
라는 controller는 Blorgh::ArticlesController
가 되고 해당 controller의 view는 app/views/articles
가 아닌 app/views/blorgh/articles
에 위치하게 됩니다. Mailer, job, helper도 마찬가지로 namespace가 지정됩니다.
마지막으로, route도 engine 내에서 격리됩니다. 이는 namespace 지정에서 가장 중요한 부분 중 하나이며, 이 가이드의 Routes 섹션에서 나중에 논의됩니다.
2.1.2 app
디렉토리
app
디렉토리 내부에는 애플리케이션에서 익숙할 표준 assets
, controllers
, helpers
, jobs
, mailers
, models
, 그리고 views
디렉토리가 있습니다. engine을 작성할 때 모델에 대해 더 자세히 살펴보겠습니다.
app/assets
디렉토리 내에는 애플리케이션과의 유사성 때문에 익숙할 images
와 stylesheets
디렉토리가 있습니다. 하지만 여기서 한 가지 차이점은 각 디렉토리가 engine 이름을 가진 하위 디렉토리를 포함한다는 것입니다. 이 engine은 namespace가 지정될 것이므로, 그 asset들도 마찬가지여야 합니다.
app/controllers
디렉토리 내에는 application_controller.rb
라는 파일이 포함된 blorgh
디렉토리가 있습니다. 이 파일은 engine의 controller들을 위한 공통 기능을 제공할 것입니다. blorgh
디렉토리는 engine의 다른 controller들이 위치할 곳입니다. 이 namespace가 지정된 디렉토리 내에 이들을 배치함으로써, 애플리케이션의 controller들과 충돌하는 것을 방지할 수 있습니다.
엔진 내의 다른 엔진이나 어플리케이션과 같은 이름의 컨트롤러가 중복되는 것을 방지합니다.
엔진 내의 ApplicationController
클래스는 애플리케이션을 엔진으로 더 쉽게 변환할 수 있도록 Rails 애플리케이션과 동일한 방식으로 이름이 지정됩니다.
app/controllers
와 마찬가지로, app/helpers
, app/jobs
, app/mailers
, app/models
디렉토리 아래에 공통 기능을 모아둔 관련 application_*.rb
파일이 포함된 blorgh
서브디렉토리가 있습니다. 이 서브디렉토리에 파일을 두고 객체에 네임스페이스를 지정함으로써, 다른 엔진이나 애플리케이션 내의 동일한 이름을 가진 요소들과 충돌하는 것을 방지할 수 있습니다.
마지막으로, app/views
디렉토리에는 blorgh/application.html.erb
파일이 있는 layouts
폴더가 포함되어 있습니다. 이 파일을 통해 엔진의 레이아웃을 지정할 수 있습니다. 이 엔진이 독립형 엔진으로 사용될 경우, 애플리케이션의 app/views/layouts/application.html.erb
파일이 아닌 이 파일에 레이아웃 커스터마이징을 추가하게 됩니다.
엔진 사용자에게 레이아웃을 강제하고 싶지 않다면, 이 파일을 삭제하고 엔진의 컨트롤러에서 다른 레이아웃을 참조할 수 있습니다.
2.1.3 bin
디렉토리
이 디렉토리에는 bin/rails
파일 하나가 있으며, 이를 통해 애플리케이션에서처럼 rails
서브 커맨드와 generator를 사용할 수 있습니다. 즉, 다음과 같은 명령어를 실행하여 이 엔진을 위한 새로운 컨트롤러와 모델을 쉽게 생성할 수 있습니다:
$ bin/rails generate model
물론 Engine 클래스에서 isolate_namespace
를 가진 engine 내부에서 이러한 명령으로 생성된 모든 것은 namespace가 적용될 것이라는 점을 유의하세요.
2.1.4 test
디렉토리
test
디렉토리는 engine의 test가 위치하는 곳입니다. engine을 테스트하기 위해 test/dummy
에 간단한 버전의 Rails 애플리케이션이 포함되어 있습니다. 이 애플리케이션은 test/dummy/config/routes.rb
파일에서 engine을 mount합니다:
Rails.application.routes.draw do
mount Blorgh::Engine => "/blorgh"
end
이 라인은 엔진을 /blorgh
경로에 마운트하며, 이를 통해 애플리케이션에서 해당 경로로만 접근할 수 있게 됩니다.
test 디렉토리 내부에는 test/integration
디렉토리가 있으며, 여기에 엔진의 integration test를 배치해야 합니다. test
디렉토리 내에 다른 디렉토리들도 생성할 수 있습니다. 예를 들어, model test를 위한 test/models
디렉토리를 생성할 수 있습니다.
3 Engine 기능 제공하기
이 가이드에서 다루는 엔진은 게시글 작성과 댓글 기능을 제공하며, Getting Started Guide와 비슷한 흐름을 따르되 몇 가지 새로운 변화가 있습니다.
이 섹션에서는 반드시 blorgh
엔진의 루트 디렉토리에서 명령어를 실행하도록 하세요.
3.1 Article 리소스 생성하기
블로그 엔진을 위해 가장 먼저 생성할 것은 Article
모델과 관련 controller입니다. 이를 빠르게 생성하기 위해 Rails scaffold generator를 사용할 수 있습니다.
$ bin/rails generate scaffold article title:string text:text
이 명령어는 다음 정보를 출력할 것입니다:
invoke active_record
create db/migrate/[timestamp]_create_blorgh_articles.rb
create app/models/blorgh/article.rb
invoke test_unit
create test/models/blorgh/article_test.rb
create test/fixtures/blorgh/articles.yml
invoke resource_route
route resources :articles
invoke scaffold_controller
create app/controllers/blorgh/articles_controller.rb
invoke erb
create app/views/blorgh/articles
create app/views/blorgh/articles/index.html.erb
create app/views/blorgh/articles/edit.html.erb
create app/views/blorgh/articles/show.html.erb
create app/views/blorgh/articles/new.html.erb
create app/views/blorgh/articles/_form.html.erb
create app/views/blorgh/articles/_article.html.erb
invoke resource_route
invoke test_unit
create test/controllers/blorgh/articles_controller_test.rb
create test/system/blorgh/articles_test.rb
invoke helper
create app/helpers/blorgh/articles_helper.rb
invoke test_unit
scaffold 제네레이터가 가장 먼저 하는 일은 active_record
제네레이터를 호출하여 리소스에 대한 migration과 model을 생성하는 것입니다. 하지만 여기서 주목할 점은 migration의 이름이 일반적인 create_articles
대신 create_blorgh_articles
라는 점입니다. 이는 Blorgh::Engine
클래스 정의에서 호출된 isolate_namespace
메서드 때문입니다. 여기서 model도 마찬가지로 namespace가 적용되어 있는데, Engine
클래스 내의 isolate_namespace
호출로 인해 app/models/article.rb
대신 app/models/blorgh/article.rb
에 위치하게 됩니다.
다음으로, 이 model에 대해 test_unit
제네레이터가 호출되어 test/models/article_test.rb
대신 test/models/blorgh/article_test.rb
에 모델 테스트를, test/fixtures/articles.yml
대신 test/fixtures/blorgh/articles.yml
에 fixture를 생성합니다.
그 다음, engine의 config/routes.rb
파일에 리소스를 위한 한 줄이 삽입됩니다. 이 줄은 단순히 resources :articles
로, engine의 config/routes.rb
파일은 다음과 같이 됩니다:
Blorgh::Engine.routes.draw do
resources :articles
end
여기서 라우트는 YourApp::Application
클래스가 아닌 Blorgh::Engine
객체에 정의됩니다. 이는 엔진의 라우트가 엔진 자체에 국한되도록 하고 test directory 섹션에서 보여진 것처럼 특정 지점에 마운트될 수 있도록 하기 위함입니다. 또한 엔진의 라우트가 애플리케이션 내의 라우트와 격리되도록 합니다. 이 가이드의 Routes 섹션에서 자세히 설명합니다.
다음으로, scaffold_controller
제너레이터가 호출되어 Blorgh::ArticlesController
컨트롤러(app/controllers/blorgh/articles_controller.rb
)와 app/views/blorgh/articles
의 관련 뷰들을 생성합니다. 이 제너레이터는 또한 컨트롤러 테스트(test/controllers/blorgh/articles_controller_test.rb
와 test/system/blorgh/articles_test.rb
)와 헬퍼(app/helpers/blorgh/articles_helper.rb
)도 생성합니다.
이 제너레이터가 생성한 모든 것은 깔끔하게 네임스페이스화되어 있습니다. 컨트롤러의 클래스는 Blorgh
모듈 내에 정의됩니다:
module Blorgh
class ArticlesController < ApplicationController
# ...
end
end
ArticlesController
클래스는 애플리케이션의 ApplicationController
가 아닌 Blorgh::ApplicationController
를 상속합니다.
app/helpers/blorgh/articles_helper.rb
내부의 helper도 마찬가지로 namespace가 지정되어 있습니다:
module Blorgh
module ArticlesHelper
# ...
end
end
이는 article 리소스가 있을 수 있는 다른 engine이나 애플리케이션과의 충돌을 방지하는 데 도움이 됩니다.
engine의 루트에서 scaffold generator가 생성한 migration을 실행하기 위해 bin/rails db:migrate
를 실행하고, test/dummy
에서 bin/rails server
를 실행하면 지금까지 engine이 갖고 있는 것들을 확인할 수 있습니다. http://localhost:3000/blorgh/articles
를 열면 생성된 기본 scaffold를 볼 수 있습니다. 이것저것 클릭해보세요! 방금 여러분의 첫 engine의 첫 기능들을 생성했습니다.
콘솔에서 실험해보고 싶다면, Rails 애플리케이션처럼 bin/rails console
도 동작할 것입니다. 기억하세요: Article
모델은 namespace가 지정되어 있으므로, 참조하려면 Blorgh::Article
로 호출해야 합니다.
irb> Blorgh::Article.find(1)
=> #<Blorgh::Article id: 1 ...>
이 엔진에 대한 마지막 사항은 엔진의 articles
리소스가 엔진의 루트가 되어야 한다는 것입니다. 누군가 엔진이 마운트된 루트 경로로 이동할 때마다 게시글 목록이 표시되어야 합니다. 엔진 내부의 config/routes.rb
파일에 다음 라인을 추가하면 이를 구현할 수 있습니다:
root to: "articles#index"
이는 애플리케이션의 루트 경로(/
)가 articles
controller의 index
action으로 매핑되도록 지정합니다.
이제 사람들은 모든 articles를 보기 위해 /articles
에 방문할 필요 없이 engine의 root로만 가면 됩니다. 이는 http://localhost:3000/blorgh/articles
대신 http://localhost:3000/blorgh
로만 가면 된다는 의미입니다.
3.2 Comments Resource 생성하기
이제 엔진이 새로운 article들을 생성할 수 있으므로, 댓글 기능도 추가하는 것이 합리적일 것입니다. 이를 위해서는 comment model, comment controller를 생성하고, article scaffold를 수정하여 댓글을 표시하고 새로운 댓글을 생성할 수 있도록 해야 합니다.
엔진의 루트 디렉토리에서 model generator를 실행하세요. article_id
integer와 text
text column을 가진 테이블을 생성하고 Comment
모델을 생성하라고 지정하세요.
$ bin/rails generate model Comment article_id:integer text:text
다음과 같이 출력됩니다:
invoke active_record
create db/migrate/[timestamp]_create_blorgh_comments.rb
create app/models/blorgh/comment.rb
invoke test_unit
create test/models/blorgh/comment_test.rb
create test/fixtures/blorgh/comments.yml
이 generator 호출은 필요한 model 파일들만 생성하는데, 파일들은 blorgh
디렉토리 아래에 namespace로 구성되며 Blorgh::Comment
라는 model 클래스를 생성합니다. 이제 blorgh_comments 테이블을 생성하기 위해 migration을 실행하세요:
$ bin/rails db:migrate
아티클의 댓글들을 보여주기 위해, app/views/blorgh/articles/show.html.erb
를 수정하고 "Edit" 링크 앞에 다음 라인을 추가하세요:
<h3>댓글</h3>
<%= render @article.comments %>
이 라인은 현재는 존재하지 않는 Blorgh::Article
model에 comments에 대한 has_many
association이 정의되어 있어야 합니다. 이를 정의하기 위해, app/models/blorgh/article.rb
를 열고 model에 다음 라인을 추가하세요:
has_many :comments
많은 comments를 가지고 있음을 지정합니다.
모델을 다음과 같이 만듭니다:
module Blorgh
class Article < ApplicationRecord
has_many :comments
end
end
has_many
가 Blorgh
모듈 내부의 클래스에서 정의되어 있기 때문에, Rails는 이러한 객체들에 대해 Blorgh::Comment
모델을 사용하고자 한다는 것을 알 수 있습니다. 따라서 여기서 :class_name
옵션을 사용하여 지정할 필요가 없습니다.
다음으로, 아티클에 댓글을 생성할 수 있는 폼이 필요합니다. 이를 추가하기 위해, app/views/blorgh/articles/show.html.erb
의 render @article.comments
호출 아래에 다음 라인을 넣으세요:
<%= render "blorgh/comments/form" %>
다음으로, 이 line이 렌더링할 partial이 존재해야 합니다. app/views/blorgh/comments
에 새로운 디렉토리를 생성하고 그 안에 _form.html.erb
라는 새로운 파일을 만들어 필요한 partial을 다음과 같이 생성합니다:
<h3>새 댓글</h3>
<%= form_with model: [@article, @article.comments.build] do |form| %>
<p>
<%= form.label :text %><br>
<%= form.textarea :text %>
</p>
<%= form.submit %>
<% end %>
이 form이 제출되면, engine 내에서 /articles/:article_id/comments
route로 POST
요청을 시도할 것입니다. 이 route는 현재 존재하지 않지만, config/routes.rb
내의 resources :articles
라인을 다음 라인들로 변경하여 생성할 수 있습니다:
resources :articles do
resources :comments
end
resource route를 선언할 때 또 다른 resource route를 중첩시킬 수 있습니다. 예를 들어 Article이 많은 Comment를 가지고 있다고 가정해보겠습니다. 위와 같은 Rails Router를 선언하면 다음과 같은 routing table이 생성됩니다.
이것은 form에서 필요로 하는 comments를 위한 nested route를 생성합니다.
이제 route는 존재하지만, 이 route가 연결될 controller는 존재하지 않습니다. 이를 생성하기 위해 engine root에서 다음 명령어를 실행하세요:
$ bin/rails generate controller comments
다음과 같은 것들이 생성됩니다:
create app/controllers/blorgh/comments_controller.rb
invoke erb
exist app/views/blorgh/comments
invoke test_unit
create test/controllers/blorgh/comments_controller_test.rb
invoke helper
create app/helpers/blorgh/comments_helper.rb
invoke test_unit
[파일 구조와 명령어가 포함된 내용이라 그대로 유지]
form은 /articles/:article_id/comments
로 POST
요청을 보내게 되며, 이는 Blorgh::CommentsController
의 create
action과 연결됩니다. 이 action은 app/controllers/blorgh/comments_controller.rb
의 클래스 정의 안에 다음과 같은 코드를 추가하여 생성할 수 있습니다:
def create
@article = Article.find(params[:article_id])
@comment = @article.comments.create(comment_params)
flash[:notice] = "댓글이 생성되었습니다!"
redirect_to articles_path
end
private
def comment_params
params.expect(comment: [:text])
end
새로운 댓글 폼을 작동시키기 위해 필요한 마지막 단계입니다. 하지만 댓글을 표시하는 부분은 아직 올바르지 않습니다. 만약 지금 댓글을 작성하면 다음과 같은 에러가 표시될 것입니다:
blorgh/comments/_comment partial이 {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}와 함께 누락되었습니다. 다음 위치에서 검색했습니다: *
"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" *
"/Users/ryan/Sites/side_projects/blorgh/app/views"
엔진이 comments를 렌더링하는 데 필요한 partial을 찾을 수 없습니다.
Rails는 먼저 애플리케이션(test/dummy
)의 app/views
디렉토리를 찾은 다음 엔진의 app/views
디렉토리를 찾습니다. 찾을 수 없을 때는 이 에러를 발생시킵니다. 엔진이 blorgh/comments/_comment
를 찾아야 하는 이유는 받은 모델 객체가 Blorgh::Comment
클래스에서 왔기 때문입니다.
이 partial은 현재로서는 comment 텍스트를 렌더링하는 역할만 담당할 것입니다.
app/views/blorgh/comments/_comment.html.erb
에 새 파일을 만들고 다음 줄을 추가하세요:
<%= comment_counter + 1 %>. <%= comment.text %>
comment_counter
로컬 변수는 <%= render @article.comments %>
호출에 의해 자동으로 정의되어 각 comment를 반복할때마다 카운터를 증가시킵니다. 이 예제에서는 comment가 생성될때 옆에 작은 숫자를 표시하는데 사용됩니다.
이로써 블로깅 엔진의 comment 기능이 완성되었습니다. 이제 애플리케이션에서 사용할 시간입니다.
4 애플리케이션에 연결하기
애플리케이션 내에서 engine을 사용하는 것은 매우 쉽습니다. 이 섹션에서는 engine을 애플리케이션에 mount하는 방법과 필요한 초기 설정, 그리고 engine 내의 article과 comment에 대한 소유권을 제공하기 위해 애플리케이션에서 제공하는 User
클래스에 engine을 연결하는 방법을 다룹니다.
4.1 엔진 마운트하기
먼저 엔진을 어플리케이션의 Gemfile
안에 명시해야 합니다. 테스트해볼 수 있는 어플리케이션이 없다면, 엔진 디렉토리 밖에서 rails new
명령어를 사용하여 새로 하나 생성하세요:
$ rails new unicorn
일반적으로 Gemfile
내에서 engine을 지정하는 것은 평범한 일상적인 gem을 지정하는 것과 같은 방식으로 이루어집니다.
gem "devise"
하지만, 로컬 머신에서 blorgh
engine을 개발하고 있기 때문에, Gemfile
에 :path
옵션을 명시해야 합니다:
gem "blorgh", path: "engines/blorgh"
그런 다음 gem을 설치하기 위해 bundle
을 실행합니다.
앞서 설명한 대로, gem을 Gemfile
에 배치하면 Rails가 로드될 때 함께 로드됩니다. 먼저 engine에서 lib/blorgh.rb
를 require하고, 그 다음 engine의 주요 기능을 정의하는 파일인 lib/blorgh/engine.rb
를 require합니다.
engine의 기능을 애플리케이션 내에서 접근 가능하게 하려면, 해당 애플리케이션의 config/routes.rb
파일에 mount해야 합니다:
mount Blorgh::Engine, at: "/blog"
이 라인은 애플리케이션 내의 /blog
에 engine을 마운트합니다. bin/rails server
로 애플리케이션을 실행하면 http://localhost:3000/blog
에서 접근할 수 있게 됩니다.
Devise와 같은 다른 engine들은 routes에서 커스텀 헬퍼(예: devise_for
)를 지정하도록 하여 이를 약간 다르게 처리합니다. 이러한 헬퍼들은 정확히 같은 일을 하며, engine의 기능들을 커스터마이징 가능한 미리 정의된 경로에 마운트합니다.
4.2 Engine 설정
engine은 blorgh_articles
와 blorgh_comments
테이블을 위한 migration들을 포함하고 있으며, 이는 engine의 model이 올바르게 쿼리할 수 있도록 애플리케이션의 데이터베이스에 생성되어야 합니다. 이러한 migration을 애플리케이션으로 복사하려면 애플리케이션의 루트에서 다음 명령어를 실행하세요:
$ bin/rails blorgh:install:migrations
복사해야 할 migration이 필요한 engine이 여러 개 있는 경우, 대신 railties:install:migrations
을 사용하세요:
$ bin/rails railties:install:migrations
MIGRATIONS_PATH를 지정하여 migrations에 대한 소스 엔진의 사용자 지정 경로를 지정할 수 있습니다.
$ bin/rails railties:install:migrations MIGRATIONS_PATH=db_blourgh
여러 개의 database가 있는 경우 DATABASE를 지정하여 대상 database를 지정할 수 있습니다.
$ bin/rails railties:install:migrations DATABASE=animals
이 명령어를 처음 실행하면 engine의 모든 migration들을 복사합니다. 다음에 실행할 때는 아직 복사되지 않은 migration들만 복사합니다. 이 명령어를 처음 실행하면 다음과 같은 출력이 나타납니다:
Blorgh로부터 마이그레이션 [timestamp_1]_create_blorgh_articles.blorgh.rb 복사됨
Blorgh로부터 마이그레이션 [timestamp_2]_create_blorgh_comments.blorgh.rb 복사됨
첫 번째 timestamp([timestamp_1]
)는 현재 시간이 되고, 두 번째 timestamp([timestamp_2]
)는 현재 시간에 1초를 더한 값이 됩니다. 이렇게 하는 이유는 engine의 migration이 애플리케이션의 기존 migration 이후에 실행되도록 하기 위해서입니다.
애플리케이션의 컨텍스트에서 이 migration을 실행하려면 간단히 bin/rails db:migrate
를 실행하면 됩니다. http://localhost:3000/blog
를 통해 engine에 접근하면 article이 비어있는 것을 확인할 수 있습니다. 이는 애플리케이션 내에 생성된 테이블이 engine 내에 생성된 테이블과 다르기 때문입니다. 새로 마운트된 engine을 가지고 자유롭게 실험해보세요. engine만 있을 때와 동일하게 동작하는 것을 확인할 수 있습니다.
하나의 engine에서만 migration을 실행하고 싶다면, SCOPE
를 지정하여 실행할 수 있습니다:
$ bin/rails db:migrate SCOPE=blorgh
엔진을 제거하기 전에 엔진의 migration을 되돌리고 싶을 때 유용할 수 있습니다. blorgh 엔진의 모든 migration을 되돌리려면 다음과 같은 코드를 실행할 수 있습니다:
$ bin/rails db:migrate SCOPE=blorgh VERSION=0
4.3 애플리케이션에서 제공하는 클래스 사용하기
4.3.1 애플리케이션에서 제공하는 Model 사용하기
엔진이 생성될 때, 엔진의 구성요소와 애플리케이션의 구성요소를 연결하기 위해 애플리케이션의 특정 클래스를 사용하고자 할 수 있습니다. blorgh
엔진의 경우, article과 comment에 author를 추가하는 것이 합리적일 것입니다.
일반적인 애플리케이션에서는 article이나 comment의 author를 나타내기 위해 User
클래스를 사용할 수 있습니다. 하지만 애플리케이션에서 이 클래스를 Person
과 같이 다른 이름으로 부를 수도 있습니다. 이러한 이유로, 엔진은 User
클래스에 대한 association을 하드코딩해서는 안 됩니다.
이 경우를 단순화하기 위해, 애플리케이션은 애플리케이션의 사용자를 나타내는 User
라는 클래스를 가질 것입니다(이를 구성 가능하도록 만드는 것은 나중에 다루겠습니다). 애플리케이션 내에서 다음 명령어를 사용하여 생성할 수 있습니다:
$ bin/rails generate model user name:string
bin/rails db:migrate
명령어를 실행하여 애플리케이션이 향후 사용할 users
테이블을 가지고 있도록 해야 합니다.
또한 간단하게 하기 위해, articles 폼에 사용자들이 자신의 이름을 입력할 수 있는 author_name
이라는 새로운 text field를 추가할 것입니다. 그러면 엔진은 이 이름을 가져와서 새로운 User
객체를 생성하거나, 이미 해당 이름을 가진 객체를 찾을 것입니다. 그리고 엔진은 article을 찾거나 생성된 User
객체와 연결할 것입니다.
먼저, author_name
text field를 엔진 내의 app/views/blorgh/articles/_form.html.erb
partial에 추가해야 합니다. 이는 다음 코드로 title
필드 위에 추가할 수 있습니다:
<div class="field">
<%= form.label :author_name %><br>
<%= form.text_field :author_name %>
</div>
다음으로, 새로운 form parameter를 허용하기 위해 Blorgh::ArticlesController#article_params
메서드를 업데이트해야 합니다:
def article_params
params.expect(article: [:title, :text, :author_name])
end
위 코드는 허용된 article parameter를 선언하는 방법입니다. params로부터 title, text, author_name 만을 허용합니다.
Blorgh::Article
모델은 author_name
필드를 실제 User
객체로 변환하여 해당 article의 author
로 연결하는 코드가 필요합니다. 이는 article이 저장되기 전에 수행되어야 합니다. 또한 이 필드에 대한 setter와 getter 메서드를 정의하기 위해 attr_accessor
를 설정해야 합니다.
이 모든 작업을 수행하기 위해서는 app/models/blorgh/article.rb
에 author_name
에 대한 attr_accessor
, author에 대한 association, 그리고 before_validation
호출을 추가해야 합니다. author
association은 당분간 User
클래스로 하드코딩될 것입니다.
attr_accessor :author_name
belongs_to :author, class_name: "User"
before_validation :set_author
private
def set_author
self.author = User.find_or_create_by(name: author_name)
end
author
연관관계를 User
클래스로 표현함으로써 engine과 애플리케이션 사이에 링크가 설정됩니다. blorgh_articles
테이블의 레코드와 users
테이블의 레코드를 연결할 수 있는 방법이 필요합니다. 연관관계가 author
로 불리기 때문에, blorgh_articles
테이블에 author_id
컬럼이 추가되어야 합니다.
이 새로운 컬럼을 생성하기 위해서, engine 내에서 다음 명령어를 실행하세요:
$ bin/rails generate migration add_author_id_to_blorgh_articles author_id:integer
migration의 이름과 그 뒤의 컬럼 명세 덕분에 Rails는 특정 테이블에 컬럼을 추가하고자 한다는 것을 자동으로 알고 이를 migration에 작성합니다. 이 이상의 내용을 알려줄 필요가 없습니다.
이 migration은 애플리케이션에서 실행되어야 합니다. 이를 위해서는 먼저 다음 명령어를 사용하여 복사해야 합니다:
$ bin/rails blorgh:install:migrations
rails generate migration
명령어가 여기서는 한 개의 마이그레이션만 복사한 것에 주목하세요. 이는 처음 두 개의 마이그레이션이 이 명령어가 처음 실행되었을 때 이미 복사되었기 때문입니다.
참고 blorgh의 [timestamp]_create_blorgh_articles.blorgh.rb migration이 건너뛰어졌습니다. 같은 이름의 migration이 이미 존재합니다.
참고 blorgh의 [timestamp]_create_blorgh_comments.blorgh.rb migration이 건너뛰어졌습니다. 같은 이름의 migration이 이미 존재합니다.
blorgh의 [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb migration이 복사되었습니다.
다음을 사용하여 migration을 실행하세요:
$ bin/rails db:migrate
이제 모든 준비가 완료되었으니, author를 (이는 users
테이블의 레코드로 표현됩니다) engine의 blorgh_articles
테이블로 표현되는 article과 연결하는 액션이 실행될 것입니다.
마지막으로, author의 이름이 article 페이지에 표시되어야 합니다. app/views/blorgh/articles/_article.html.erb
내의 "Title" 출력부분 위에 다음 코드를 추가하세요:
<p>
<strong>작성자:</strong>
<%= article.author.name %>
</p>
4.3.2 Application에서 제공하는 Controller 사용하기
Rails controller들은 일반적으로 인증이나 session 변수에 접근하는 것과 같은 코드를 공유하기 때문에, 기본적으로 ApplicationController
를 상속받습니다. 하지만 Rails engine은 메인 application과 독립적으로 실행되도록 scope가 지정되어 있어서, 각 engine은 scope가 지정된 ApplicationController
를 가집니다. 이 namespace는 코드 충돌을 방지하지만, 종종 engine controller가 메인 application의 ApplicationController
에 있는 메소드에 접근해야 할 필요가 있습니다. 이러한 접근을 제공하는 쉬운 방법은 engine의 scope가 지정된 ApplicationController
가 메인 application의 ApplicationController
를 상속하도록 변경하는 것입니다. Blorgh engine의 경우 app/controllers/blorgh/application_controller.rb
를 다음과 같이 변경하면 됩니다:
module Blorgh
class ApplicationController < ::ApplicationController
end
end
기본적으로 engine의 controller들은 Blorgh::ApplicationController
를 상속받습니다. 따라서 이 변경을 하면 engine이 마치 main application의 일부인 것처럼 main application의 ApplicationController
에 접근할 수 있게 됩니다.
이 변경사항을 적용하려면 engine이 ApplicationController
를 가진 Rails application에서 실행되어야 합니다.
4.4 Engine 구성하기
이 섹션에서는 User
클래스를 구성 가능하게 만드는 방법과 engine에 대한 일반적인 구성 팁을 다룹니다.
4.4.1 애플리케이션에서 구성 설정하기
다음 단계는 애플리케이션에서 User
를 나타내는 클래스를 engine에서 커스터마이즈 가능하도록 만드는 것입니다. 이는 앞서 설명했듯이 해당 클래스가 항상 User
가 아닐 수 있기 때문입니다. 이 설정을 커스터마이즈 가능하게 하기 위해, engine은 author_class
라는 구성 설정을 가지게 될 것이며, 이는 애플리케이션 내에서 사용자를 나타내는 클래스를 지정하는 데 사용됩니다.
이 구성 설정을 정의하기 위해서는 engine의 Blorgh
모듈 내부에 mattr_accessor
를 사용해야 합니다. engine 내의 lib/blorgh.rb
에 다음 라인을 추가하세요:
mattr_accessor :author_class
클래스 레벨의 읽기/쓰기 accessor를 정의합니다.
이 메서드는 attr_accessor
와 cattr_accessor
형제들과 비슷하게 작동하지만, 지정된 이름으로 모듈에 setter와 getter 메서드를 제공합니다. 이를 사용하기 위해서는 Blorgh.author_class
를 사용하여 참조해야 합니다.
다음 단계는 Blorgh::Article
모델을 이 새로운 설정으로 전환하는 것입니다. 이 모델(app/models/blorgh/article.rb
) 내부의 belongs_to
관계를 다음과 같이 변경하세요:
belongs_to :author, class_name: Blorgh.author_class
Blorgh::Article
모델의 set_author
메서드는 이 클래스를 다음과 같이 사용해야 합니다:
self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)
이 코드는 author_name을 기준으로 author 클래스의 레코드를 찾거나 찾지 못할 경우 생성합니다.
저장된 author_class
결과에 매번 constantize
를 호출하지 않아도 되도록, lib/blorgh.rb
파일의 Blorgh
모듈 안에서 author_class
getter 메서드를 오버라이드하여 저장된 값을 반환하기 전에 항상 constantize
를 호출하도록 할 수 있습니다:
def self.author_class
@@author_class.constantize
end
자신(self)의 author_class를 반환합니다. @@author_class 클래스 변수를 constantize하여 반환합니다.
이렇게 하면 위의 set_author
코드가 다음과 같이 바뀝니다:
self.author = Blorgh.author_class.find_or_create_by(name: author_name)
그 결과로 조금 더 짧고 동작이 더 암시적인 결과물이 나왔습니다. author_class
메서드는 항상 Class
객체를 반환해야 합니다.
author_class
메서드를 String
대신 Class
를 반환하도록 변경했기 때문에, Blorgh::Article
모델의 belongs_to
정의도 수정해야 합니다:
belongs_to :author, class_name: Blorgh.author_class.to_s
애플리케이션 내에서 이 configuration 설정을 지정하려면 initializer를 사용해야 합니다. initializer를 사용하면 애플리케이션이 시작되고 engine의 model을 호출하기 전에 configuration이 설정되며, 이 configuration 설정의 존재 여부에 따라 model이 의존할 수 있습니다.
blorgh
engine이 설치된 애플리케이션 내에 config/initializers/blorgh.rb
에 새로운 initializer를 생성하고 다음 내용을 추가하세요:
Blorgh.author_class = "User"
경고: 여기서 매우 중요한 것은 클래스 자체가 아닌 String
버전의 클래스를 사용하는 것입니다. 클래스를 직접 사용할 경우, Rails는 해당 클래스를 로드하고 관련 테이블을 참조하려고 시도할 것입니다. 테이블이 아직 존재하지 않는 경우 문제가 발생할 수 있습니다. 따라서 String
을 사용하고 나중에 엔진에서 constantize
를 사용하여 클래스로 변환해야 합니다.
새 article을 생성해보세요. 이전과 정확히 동일한 방식으로 작동하는 것을 볼 수 있습니다. 다만 이번에는 엔진이 config/initializers/blorgh.rb
의 설정을 사용하여 클래스를 확인합니다.
이제 클래스가 무엇인지에 대한 엄격한 종속성은 없으며, 클래스의 API가 어떠해야 하는지에 대한 요구사항만 있습니다. 엔진은 단순히 이 클래스가 find_or_create_by
메서드를 정의할 것을 요구하며, 이 메서드는 article이 생성될 때 연결될 해당 클래스의 객체를 반환해야 합니다. 물론 이 객체는 참조될 수 있는 어떤 종류의 식별자를 가져야 합니다.
4.4.2 일반적인 엔진 설정
엔진 내에서 initializer, 국제화 또는 기타 설정 옵션과 같은 것들을 사용하고 싶을 때가 있을 수 있습니다. 좋은 소식은 Rails 엔진이 Rails 애플리케이션과 거의 동일한 기능을 공유하기 때문에 이러한 것들이 완전히 가능하다는 것입니다. 사실, Rails 애플리케이션의 기능은 엔진이 제공하는 것의 상위 집합입니다!
엔진이 로드되기 전에 실행되어야 하는 코드인 initializer를 사용하고 싶다면, config/initializers
폴더가 그 위치입니다. 이 디렉토리의 기능은 Configuring 가이드의 Initializers 섹션에서 설명되어 있으며, 애플리케이션 내의 config/initializers
디렉토리와 정확히 동일한 방식으로 작동합니다. 표준 initializer를 사용하고 싶을 때도 마찬가지입니다.
로케일의 경우, 애플리케이션에서처럼 단순히 config/locales
디렉토리에 로케일 파일을 배치하면 됩니다.
5 엔진 테스트하기
엔진이 생성될 때, test/dummy
내부에 더 작은 더미 애플리케이션이 생성됩니다. 이 애플리케이션은 엔진의 마운팅 포인트로 사용되어 엔진 테스트를 매우 간단하게 만들어줍니다. 이 디렉토리 내에서 컨트롤러, 모델 또는 뷰를 생성하여 이 애플리케이션을 확장할 수 있으며, 이를 사용하여 엔진을 테스트할 수 있습니다.
test
디렉토리는 일반적인 Rails 테스트 환경처럼 다루어져야 하며, 단위, 기능 및 통합 테스트가 가능합니다.
5.1 Functional Tests
functional test를 작성할 때 고려해야 할 점은 테스트가 engine이 아닌 애플리케이션 - test/dummy
애플리케이션 - 에서 실행된다는 것입니다. 이것은 테스팅 환경의 셋업 때문입니다. engine은 컨트롤러와 같은 주요 기능을 테스트하기 위해서 호스트 애플리케이션이 필요합니다. 이는 컨트롤러의 functional test에서 다음과 같이 일반적인 GET
요청을 할 경우:
module Blorgh
class FooControllerTest < ActionDispatch::IntegrationTest
include Engine.routes.url_helpers
def test_index
get foos_url
# ...
end
end
end
애플리케이션이 이러한 요청을 engine으로 라우팅하는 방법을 명시적으로 알려주지 않으면 제대로 동작하지 않을 수 있습니다. 이를 해결하기 위해서는 setup 코드에서 @routes
인스턴스 변수를 engine의 route set으로 설정해야 합니다:
module Blorgh
class FooControllerTest < ActionDispatch::IntegrationTest
# Engine route helper들을 포함시킵니다
include Engine.routes.url_helpers
setup do
# 이 테스트 클래스에서 사용할 route를 Engine의 route로 설정합니다
@routes = Engine.routes
end
def test_index
get foos_url
# ...
end
end
end
이것은 해당 컨트롤러의 index
액션에 대해 여전히 GET
요청을 수행하되, 애플리케이션의 라우트 대신 엔진의 라우트를 사용하여 도달하고 싶다는 것을 애플리케이션에 알려줍니다.
또한 이를 통해 테스트에서 엔진의 URL 헬퍼들이 예상대로 작동하는 것을 보장합니다.
6 Engine 기능 개선하기
이 섹션에서는 메인 Rails 애플리케이션에서 엔진의 MVC 기능을 추가하거나 오버라이드하는 방법을 설명합니다.
6.1 Models과 Controllers 오버라이딩
부모 애플리케이션은 Engine의 models과 controllers를 확장하거나 장식하기 위해 재정의할 수 있습니다.
오버라이드는 autoloader에 의해 무시되고 to_prepare
콜백에서 미리 로드되는 전용 디렉토리 app/overrides
에서 구성될 수 있습니다:
# config/application.rb
module MyApp
class Application < Rails::Application
# ...
overrides = "#{Rails.root}/app/overrides"
Rails.autoloaders.main.ignore(overrides)
config.to_prepare do
Dir.glob("#{overrides}/**/*_override.rb").sort.each do |override|
load override
end
end
end
end
6.1.1 class_eval
을 사용해 기존 클래스 다시 열기
예를 들어, engine model을 오버라이드하기 위해
# Blorgh/app/models/blorgh/article.rb
module Blorgh
class Article < ApplicationRecord
# ...
end
end
해당 class를 reopening 하는 파일을 생성하기만 하면 됩니다:
# MyApp/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
# ...
end
재정의가 클래스나 모듈을 다시 열도록 하는 것이 매우 중요합니다. class
또는 module
키워드를 사용하면 메모리에 아직 없는 경우 이들을 정의하게 되는데, 이는 정의가 engine에 존재하기 때문에 잘못된 것입니다. 위에서 보여진 것처럼 class_eval
을 사용하면 다시 여는 것이 보장됩니다.
6.1.2 ActiveSupport::Concern을 사용해 기존 클래스 다시 열기
Class#class_eval
을 사용하는 것은 간단한 조정에는 좋지만, 더 복잡한 클래스 수정의 경우 ActiveSupport::Concern
의 사용을 고려해볼 수 있습니다. ActiveSupport::Concern은 런타임에 상호 연결된 의존 모듈과 클래스의 로드 순서를 관리하여 코드를 상당히 모듈화할 수 있게 해줍니다.
Article#time_since_created
추가와 Article#summary
재정의:
# MyApp/app/models/blorgh/article.rb
class Blorgh::Article < ApplicationRecord
include Blorgh::Concerns::Models::Article
def time_since_created
Time.current - created_at
end
def summary
"#{title} - #{truncate(text)}"
end
end
# Blorgh/app/models/blorgh/article.rb
module Blorgh
class Article < ApplicationRecord
include Blorgh::Concerns::Models::Article
end
end
# Blorgh/lib/concerns/models/article.rb
module Blorgh::Concerns::Models::Article
extend ActiveSupport::Concern
# `included do`는 모듈이 포함된 컨텍스트(예: Blorgh::Article)에서
# 블록이 평가되도록 하며, 모듈 자체에서 평가되지 않습니다.
included do
attr_accessor :author_name
belongs_to :author, class_name: "User"
before_validation :set_author
private
def set_author
self.author = User.find_or_create_by(name: author_name)
end
end
def summary
"#{title}"
end
module ClassMethods
def some_class_method
"some class method string"
end
end
end
6.2 Autoloading과 Engine
Autoloading과 engine에 대한 더 자세한 정보는 Autoloading and Reloading Constants 가이드를 참고해주세요.
6.3 View 재정의하기
Rails가 렌더링할 view를 찾을 때, 먼저 애플리케이션의 app/views
디렉토리를 확인합니다. 만약 거기서 view를 찾지 못하면, 이 디렉토리가 있는 모든 engine의 app/views
디렉토리를 확인합니다.
애플리케이션이 Blorgh::ArticlesController
의 index 액션에 대한 view를 렌더링하도록 요청받으면, 먼저 애플리케이션 내의 app/views/blorgh/articles/index.html.erb
경로를 찾습니다. 찾지 못하면 engine 내부를 찾습니다.
app/views/blorgh/articles/index.html.erb
에 새 파일을 생성하기만 하면 애플리케이션에서 이 view를 재정의할 수 있습니다. 그러면 이 view가 일반적으로 출력하는 내용을 완전히 변경할 수 있습니다.
이제 app/views/blorgh/articles/index.html.erb
에 새 파일을 생성하고 다음 내용을 넣어보세요:
<h1>게시글</h1>
<%= link_to "새 게시글", new_article_path %>
<% @articles.each do |article| %>
<h2><%= article.title %></h2>
<small>작성자: <%= article.author %></small>
<%= simple_format(article.text) %>
<hr>
<% end %>
6.4 Routes
Engine 내의 routes는 기본적으로 application으로부터 격리되어 있습니다. 이는 Engine
클래스 내부의 isolate_namespace
호출을 통해 이루어집니다. 이는 본질적으로 application과 engine이 동일한 이름의 routes를 가질 수 있으며 충돌하지 않는다는 것을 의미합니다.
Engine 내의 routes는 config/routes.rb
내의 Engine
클래스에서 다음과 같이 정의됩니다:
Blorgh::Engine.routes.draw do
resources :articles
end
이와 같이 격리된 라우트를 가지고 있을 때, 애플리케이션에서 engine의 특정 영역으로 링크하고 싶다면 engine의 라우팅 프록시 메서드를 사용해야 합니다. 애플리케이션과 engine 모두 동일한 헬퍼가 정의되어 있는 경우 articles_path
와 같은 일반적인 라우팅 메서드를 호출하면 의도하지 않은 위치로 이동할 수 있습니다.
예를 들어, 다음의 예제는 해당 템플릿이 애플리케이션에서 렌더링되었다면 애플리케이션의 articles_path
로, engine에서 렌더링되었다면 engine의 articles_path
로 이동할 것입니다:
<%= link_to "블로그 글", articles_path %>
이 route가 항상 engine의 articles_path
routing helper method를 사용하도록 하려면, engine과 동일한 이름을 가진 routing proxy method에서 해당 method를 호출해야 합니다.
<%= link_to "블로그 게시글", blorgh.articles_path %>
만약 engine 내에서 application을 참조하고 싶다면, main_app
helper를 사용하세요:
<%= link_to "홈", main_app.root_path %>
engine 내에서 이를 사용하면 항상 application의 root로 이동합니다. main_app
"routing proxy" 메서드 호출을 생략하면, 호출된 위치에 따라 engine의 root나 application의 root로 이동할 수 있습니다.
engine 내에서 렌더링된 template이 application의 routing helper 메서드 중 하나를 사용하려고 하면, 정의되지 않은 메서드 호출이 발생할 수 있습니다. 이러한 문제가 발생하면 engine 내에서 main_app
접두사 없이 application의 routing 메서드를 호출하려고 하지 않았는지 확인하세요.
6.5 Assets
엔진 내의 assets은 완전한 애플리케이션과 동일한 방식으로 작동합니다. 엔진 클래스가 Rails::Engine
을 상속하므로, 애플리케이션은 엔진의 app/assets
와 lib/assets
디렉토리에서 assets를 찾을 수 있습니다.
엔진의 다른 모든 구성요소와 마찬가지로, assets도 네임스페이스화 되어야 합니다. 즉, style.css
라는 asset이 있다면, app/assets/stylesheets/style.css
가 아닌 app/assets/stylesheets/[engine name]/style.css
에 위치해야 합니다. 이 asset이 네임스페이스화되지 않으면, 호스트 애플리케이션이 동일한 이름의 asset을 가질 수 있으며, 이 경우 애플리케이션의 asset이 우선시되고 엔진의 asset은 무시됩니다.
app/assets/stylesheets/blorgh/style.css
에 위치한 asset이 있다고 가정해봅시다. 애플리케이션 내에서 이 asset을 포함하려면, 단순히 stylesheet_link_tag
를 사용하고 해당 asset이 엔진 내부에 있는 것처럼 참조하면 됩니다:
<%= stylesheet_link_tag "blorgh/style.css" %>
또한 이러한 asset들을 processed 파일에서 Asset Pipeline require 구문을 사용하여 다른 asset들의 의존성으로 지정할 수 있습니다:
/*
*= require blorgh/style
*/
Sass나 CoffeeScript와 같은 언어를 사용하기 위해서는 해당 라이브러리를 engine의 .gemspec
에 추가해야 한다는 것을 기억하세요.
6.6 에셋 분리 및 프리컴파일
호스트 애플리케이션에서 엔진의 에셋이 필요하지 않은 경우가 있습니다. 예를 들어, 엔진에서만 존재하는 admin 기능을 만들었다고 가정해 봅시다. 이 경우, 호스트 애플리케이션은 admin.css
나 admin.js
를 필요로 하지 않습니다. 오직 gem의 admin 레이아웃만이 이러한 에셋을 필요로 합니다. 호스트 앱이 "blorgh/admin.css"
를 스타일시트에 포함하는 것은 의미가 없습니다. 이러한 상황에서는 프리컴파일을 위한 에셋을 명시적으로 정의해야 합니다. 이는 bin/rails assets:precompile
이 실행될 때 Sprockets가 엔진의 에셋을 추가하도록 지시합니다.
engine.rb
에서 프리컴파일할 에셋을 다음과 같이 정의할 수 있습니다:
initializer "blorgh.assets.precompile" do |app|
app.config.assets.precompile += %w( admin.js admin.css )
end
precompile 배열에 JavaScript와 stylesheet 파일을 추가하는 initializer입니다.
자세한 내용은 Asset Pipeline 가이드를 참고하세요.
6.7 기타 Gem Dependencies
engine 내부의 Gem dependency는 engine의 root에 있는 .gemspec
파일 안에 명시되어야 합니다. 이는 engine이 gem으로 설치될 수 있기 때문입니다. dependency가 Gemfile
안에 명시되어 있다면, 이는 일반적인 gem 설치에서 인식되지 않아 설치되지 않으므로 engine이 제대로 동작하지 않을 수 있습니다.
일반적인 gem install
중에 engine과 함께 설치되어야 하는 dependency를 명시하려면, engine의 .gemspec
파일 안의 Gem::Specification
블록 안에 명시하세요:
s.add_dependency "moo"
애플리케이션의 development dependency로만 설치되어야 하는 의존성을 지정하려면 다음과 같이 지정하세요:
s.add_development_dependency "moo"
애플리케이션 내에서 bundle install
을 실행하면 두 종류의 의존성 모두가 설치됩니다. gem의 development dependencies는 engine의 개발과 테스트가 실행될 때만 사용됩니다.
engine이 require될 때 즉시 dependencies를 require하고 싶다면, engine의 초기화 전에 require해야 합니다. 예를 들어:
require "other_engine/engine"
require "yet_another_engine/engine"
module MyEngine
class Engine < ::Rails::Engine
end
end