# 定制自己的 Engine

Engine = Ruby gem + Rails MVC stack elements.

## 一，创建自己的 Engine

可用命令 `rails plugin new` 创建自己的 Engine. \
&#x20;常用可选参数 `--full` 或 `--mountable`

两者之间的区别，可以参考【Engine full vs mountable】章节。

拆分一下，步骤大概如下：

1\) 继承于 Rails::Engine，一般把它们放在 lib/ 目录下。

```ruby
# lib/my_engine.rb
module MyEngine
  class Engine < Rails::Engine
    # ... ...
  end
end
```

2\) 在 config/application.rb (或 Gemfile) 里加载本文件。

Engine 相关的 model、controller 和 helper 会被加载到 app/ 里，route 会被加载到 config/routes.rb, locale 会被加载到 config/locales, tasks 会被加载到 lib/tasks.

```ruby
# config/application.rb
require 'my_engine/engine'

# 或者

# Gemfile
gem 'my_engine', path: "/path/to/my_engine"
```

3\) 在 routes.rb 里 mount MyEngine::Engine

```ruby
Rails.application.routes.draw do
  mount MyEngine::Engine => "/engine"
end
```

## 二，编写 my\_engine/engine.rb  文件内容

每个 Engine 都会有自己的 engine.rb 文件。里面有自己的 Engine 类，它继承于 ::Rails::Engine

**1) 常用方法之 config、initializer**

在这文件里，你可以使用 config, initializer 等方法。这点和定制 Railtie 类似，但不同点是：当前 Engine 的配置和初始化，作用域仅限于当前 Engine.

```ruby
class MyEngine < Rails::Engine
  # 添加新的、额外的目录到 autoload_paths 里
  config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)

  initializer "my_engine.add_middleware" do |app|
    app.middleware.use MyEngine::Middleware
  end
end
```

config 是个方法，但同时它也是 Configuration 的实例对象，所以你可以使用 `config.generators`:

```ruby
class MyEngine < Rails::Engine
  config.generators do |g|
    g.orm             :active_record
    g.template_engine :erb
    g.test_framework  :test_unit
  end
end
```

还可使用 `config.app_generators`:

```ruby
class MyEngine < Rails::Engine
  # note that you can also pass block to app_generators in the same way you
  # can pass it to generators method
  config.app_generators.orm :datamapper
end
```

**2) 常用方法之 isolate\_namespace**

默认 Engine 和应用是在一个环境里的，这意味着应用所有 helper 和命名路由都可以在 Engine 里使用。

你可以使用 `isolate_namespace` 更改此项默认配置，将 Engine 和应用隔离出来。使用举例：

```ruby
module MyEngine
  class Engine < Rails::Engine
    isolate_namespace MyEngine
  end
end
```

此时 `MyEngine` 和应用是隔离了的，假设 MyEngine 有代码：

```
module MyEngine
  class FooController < ActionController::Base
  end
end
```

此时 FooController 仅能使用 `Engine` 里提供的 helper，以及 `MyEngine::Engine.routes` 提供的 url helper.

另外一个改变就是 Engine 里的路由不必再使用 namespace，举例：

```ruby
MyEngine::Engine.routes.draw do
  resources :articles
end
```

resources :articles 自动对应着 `MyEngine::ArticlesController`. 并且不必使用长长的 url helper，例如 `my_engine_articles_path` 可以直接使用 `articles_path`

不受 isolate\_namespace 影响的就是对于 model 的调用，仍然使用 engine\_name 做为前缀。例如以下例子的 `MyEngine::Article`

```ruby
polymorphic_url(MyEngine::Article.new) # => "articles_path"

form_for(MyEngine::Article.new) do
  text_field :title
  # => <input type="text" name="article[title]" id="article_title" />
end
```

另一个改变是对表名的更改。默认使用 engine\_name (在这里是 "my\_engine")做为表前缀，也就是说 MyEngine::Article 对应的表名应该是 my\_engine\_articles

**3) 常用方法之 paths**

Engine 默认都有自己的文件、目录结构，如果你没有定制，那么就使用默认的：

```ruby
"app",                 eager_load: true, glob: "*"
"app/assets",          glob: "*"
"app/controllers",     eager_load: true
"app/helpers",         eager_load: true
"app/models",          eager_load: true
"app/mailers",         eager_load: true
"app/views"

"app/controllers/concerns", eager_load: true
"app/models/concerns",      eager_load: true

"lib",                 load_path: true
"lib/assets",          glob: "*"
"lib/tasks",           glob: "**/*.rake"

"config"
"config/environments", glob: "#{Rails.env}.rb"
"config/initializers", glob: "**/*.rb"
"config/locales",      glob: "*.{rb,yml}"
"config/routes.rb"

"db"
"db/migrate"
"db/seeds.rb"

"vendor",              load_path: true
"vendor/assets",       glob: "*"
```

`paths` 通过它，可以更改默认的文件、目录结构。

举例，你想把 controller 文件放到 lib/ 目录下：

```ruby
class MyEngine < Rails::Engine
  paths["app/controllers"] = "lib/controllers"
end
```

再或者，controller 在 app/ 和 lib/ 下都可接受：

```ruby
class MyEngine < Rails::Engine
  paths["app/controllers"] << "lib/controllers"
end
```

Application 在 Engine 之上，它又有自己的配置和初始化。它配置了 app/ 下的文件、目录会被自动加载，所以像 app/services 会被自动加载。

**4) 常用方法之 endpoint**

Engine 内容也可以是一个 Rack Application. 当你的代码本身是 Rack Application，而又想使用 Engine 的特性时，可以这么做：

1\) 在自己定义的 Engine 里，使用 `endpoint`:

```ruby
module MyEngine
  class Engine < Rails::Engine
    # Engine 的内容就是 MyRackApplication
    endpoint MyRackApplication
  end
end
```

2\) 和平常一样，在 route 里 `mount` 你的 Engine:

```ruby
Rails.application.routes.draw do
  mount MyEngine::Engine => "/engine"
end
```

**5) 常用方法之 middleware**

Engine 内容也可以是一个 Middleware. 当你的代码本身是 Middleware，而又想使用 Engine 的特性时，可以这么做：

```ruby
module MyEngine
  class Engine < Rails::Engine
    # Engine 的内容就是 SomeMiddleware
    middleware.use SomeMiddleware
  end
end
```

**6) 常用方法之 engine\_name**

用几个场景可能会用到 engine name:

* routes: 当你使用 mount(MyEngine::Engine => '/my\_engine')
* rake task: 当你使用 my\_engine:install:migrations

Engine name 默认根据类名而来，如 `MyEngine::Engine` 对应有 `my_engine_engine`. 你可以使用 `engine_name` 进行自定义:

```ruby
module MyEngine
  class Engine < Rails::Engine
    engine_name "my_engine"
  end
end
```

## 三，编写 Engine 内容

做了上述两步后，就是编写内容。该做什么事，做什么事；该完成什么功能，完成什么功能。

## 四，在 main\_app 引入定制的 Engine

也就是：

在 config/application.rb (或 Gemfile) 里加载本文件。 \
&#x20;在 routes.rb 里 mount MyEngine::Engine


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kelby.gitbook.io/rails-beginner-s-guide/rails/engine/custom_your_engine.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
