rubyonrails.org에서 더 보기:

GitHub이 아닌 https://guides.rubyonrails.org에서 가이드를 읽으세요.

Engine 시작하기

이 가이드에서는 engine이 무엇이며, host application에 추가 기능을 제공하기 위해 어떻게 깔끔하고 사용하기 쉬운 interface를 통해 사용될 수 있는지 배우게 됩니다.

이 가이드를 읽고나면 다음을 알게 될 것입니다:

  • engine을 구성하는 요소
  • engine을 생성하는 방법
  • engine을 위한 기능을 만드는 방법
  • engine을 application에 연결하는 방법
  • application에서 engine 기능을 재정의하는 방법
  • Load and Configuration Hook을 사용하여 Rails framework 로딩을 피하는 방법

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.jsapplication.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 디렉토리 내에는 애플리케이션과의 유사성 때문에 익숙할 imagesstylesheets 디렉토리가 있습니다. 하지만 여기서 한 가지 차이점은 각 디렉토리가 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.rbtest/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_manyBlorgh 모듈 내부의 클래스에서 정의되어 있기 때문에, Rails는 이러한 객체들에 대해 Blorgh::Comment 모델을 사용하고자 한다는 것을 알 수 있습니다. 따라서 여기서 :class_name 옵션을 사용하여 지정할 필요가 없습니다.

다음으로, 아티클에 댓글을 생성할 수 있는 폼이 필요합니다. 이를 추가하기 위해, app/views/blorgh/articles/show.html.erbrender @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/commentsPOST 요청을 보내게 되며, 이는 Blorgh::CommentsControllercreate 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_articlesblorgh_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.rbauthor_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_accessorcattr_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/assetslib/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.cssadmin.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


맨 위로