Skip to content

Commit 8899f77

Browse files
committed
Merge branch 'chinakr'
2 parents a63c4c7 + f25a579 commit 8899f77

File tree

1 file changed

+289
-2
lines changed

1 file changed

+289
-2
lines changed

manuscript/engines.adoc

+289-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
[[generating-an-engine]]
4141
=== 生成引擎
4242

43-
通过运行插件生成器并传递必要的选项就可以生成引擎。在“blorgh”引擎的例子中,我们需要创建“可挂载”的引擎,为此可以在终端中运行下面的命令:
43+
通过运行插件生成器并传递必要的选项就可以生成引擎。在 Blorgh 引擎的例子中,我们需要创建“可挂载”的引擎,为此可以在终端中运行下面的命令:
4444

4545
[source,sh]
4646
----
@@ -327,7 +327,7 @@ root to: "articles#index"
327327
[[generating-a-comments-resource]]
328328
==== 生成评论资源
329329

330-
到目前为止,我们的“blorgh”引擎已经能够新建文章了,下一步应该为文章添加评论。为此,我们需要生成评论模型和评论控制器,同时修改文章脚手架,以显示文章的已有评论并提供添加评论的表单。
330+
到目前为止,我们的 Blorgh 引擎已经能够新建文章了,下一步应该为文章添加评论。为此,我们需要生成评论模型和评论控制器,同时修改文章脚手架,以显示文章的已有评论并提供添加评论的表单。
331331

332332
在引擎的根目录中运行模型生成器,以生成 `Comment` 模型,此模型具有 `article_id` 整型字段和 `text` 文本字段:
333333

@@ -479,3 +479,290 @@ Missing partial blorgh/comments/_comment with {:handlers=>[:erb, :builder],
479479

480480
[[hooking-into-an-application]]
481481
=== 把引擎挂载到应用中
482+
483+
要想在应用中使用引擎非常容易。本节介绍如何把引擎挂载到应用中并完成必要的初始化设置,以及如何把引擎连接到应用中的 `User` 类上,以便使应用中的用户拥有引擎中的文章及其评论。
484+
485+
[[mounting-the-engine]]
486+
==== 挂载引擎
487+
488+
首先,需要在应用的 Gemfile 中指定引擎。我们需要新建一个应用用于测试,为此可以在引擎文件夹之外执行 `rails new` 命令:
489+
490+
[source,sh]
491+
----
492+
$ rails new unicorn
493+
----
494+
495+
通常,只需在 Gemfile 中以普通 gem 的方式指定引擎。
496+
497+
[source,ruby]
498+
----
499+
gem 'devise'
500+
----
501+
502+
由于我们是在本地开发 `blorgh` 引擎,因此需要在 Gemfile 中指定 `:path` 选项:
503+
504+
[source,ruby]
505+
----
506+
gem 'blorgh', path: 'engines/blorgh'
507+
----
508+
509+
然后通过 `bundle` 命令安装 gem。
510+
511+
如前文所述,Gemfile 中的 gem 将在 Rails 启动时加载。上述代码首先加载引擎中的 `lib/blorgh.rb` 文件,然后加载 `lib/blorgh/engine.rb` 文件,后者定义了引擎的主要功能。
512+
513+
要想在应用中访问引擎的功能,我们需要在应用的 `config/routes.rb` 文件中挂载该引擎:
514+
515+
[source,ruby]
516+
----
517+
mount Blorgh::Engine, at: "/blog"
518+
----
519+
520+
上述代码会在应用的 `/blog` 路径上挂载引擎。通过 `rails server` 命令运行应用后,我们就可以通过 pass:[http://localhost:3000/blog] 访问引擎了。
521+
522+
NOTE: 其他一些引擎,例如 Devise,工作原理略有不同,这些引擎会在路由中自定义辅助方法(例如 `devise_for`)。这些辅助方法的作用都是在预定义路径(可以自定义)上挂载引擎的功能。
523+
524+
[[engine-setup]]
525+
==== 引擎设置
526+
527+
引擎中包含了 `blorgh_articles` 和 `blorgh_comments` 数据表的迁移。通过这些迁移在应用数据库中创建数据表之后,引擎模型才能正确查询对应的数据表。在引擎的 `test/dummy` 文件夹中运行下面的命令,可以把这些迁移复制到应用中:
528+
529+
[source,sh]
530+
----
531+
$ bin/rails blorgh:install:migrations
532+
----
533+
534+
如果需要从多个引擎中复制迁移,可以使用 `railties:install:migrations`:
535+
536+
[source,sh]
537+
----
538+
$ bin/rails railties:install:migrations
539+
----
540+
541+
第一次运行上述命令时,Rails 会从所有引擎中复制迁移。再次运行时,只会复制尚未复制的迁移。第一次运行上述命令时输出的提示信息为:
542+
543+
----
544+
Copied migration [timestamp_1]_create_blorgh_articles.blorgh.rb from blorgh
545+
Copied migration [timestamp_2]_create_blorgh_comments.blorgh.rb from blorgh
546+
----
547+
548+
其中第一个时间戳(`[timestamp_1]`)是当前时间,第二个时间戳(`[timestamp_2]`)是当前时间加上 1 秒。这样就能确保引擎的迁移总是在应用的现有迁移之后运行。
549+
550+
通过 `bin/rails db:migrate` 命令即可在应用的上下文中运行引擎的迁移。此时访问 pass:[http://localhost:3000/blog] 会看到文章列表是空的,这是因为在应用中和在引擎中创建的数据表有所不同。继续浏览刚刚挂载的这个引擎的其他页面,我们会发现引擎和应用看起来并没有什么区别。
551+
552+
通过指定 `SCOPE` 选项,我们可以只运行指定引擎的迁移:
553+
554+
[source,sh]
555+
----
556+
bin/rails db:migrate SCOPE=blorgh
557+
----
558+
559+
在需要还原并删除引擎的迁移时常常采取这种做法。通过下面的命令可以还原 `blorgh` 引擎的所有迁移:
560+
561+
[source,sh]
562+
----
563+
bin/rails db:migrate SCOPE=blorgh VERSION=0
564+
----
565+
566+
[[using-a-class-provided-by-the-application]]
567+
==== 使用应用提供的类
568+
569+
[[using-a-model-provided-by-the-application]]
570+
===== 使用应用提供的模型
571+
572+
在创建引擎时,有时需要通过应用提供的类把引擎和应用连接起来。在 `blorgh` 引擎的例子中,我们需要把文章及其评论和作者关联起来。
573+
574+
一个典型的应用可能包含 `User` 类,可用于表示文章和评论的作者。但有的应用包含的可能是 `Person` 类而不是 `User` 类。因此,我们不能通过硬编码直接在引擎中建立和 `User` 类的关联。
575+
576+
为了避免例子变得复杂,我们假设应用包含的是 `User` 类(后文将对这个类进行配置)。通过下面的命令可以在应用中生成这个 `User` 类:
577+
578+
[source,sh]
579+
----
580+
rails g model user name:string
581+
----
582+
583+
然后执行 `bin/rails db:migrate` 命令以创建 `users` 数据表。
584+
585+
同样,为了避免例子变得复杂,我们会在文章表单中添加 `author_name` 文本字段,用于输入作者名称。引擎会根据作者名称新建或查找已有的 `User` 对象,然后建立此 `User` 对象和其文章的关联。
586+
587+
具体操作的第一步是在引擎的 `app/views/blorgh/articles/_form.html.erb` 局部视图中添加 `author_name` 文本字段,添加的位置是在 `title` 字段之前:
588+
589+
[source,erb]
590+
----
591+
<div class="field">
592+
<%= f.label :author_name %><br>
593+
<%= f.text_field :author_name %>
594+
</div>
595+
----
596+
597+
接下来,需要更新 `Blorgh::ArticleController#article_params` 方法,以便使用新增的表单参数:
598+
599+
[source,ruby]
600+
----
601+
def article_params
602+
params.require(:article).permit(:title, :text, :author_name)
603+
end
604+
----
605+
606+
然后还要在 `Blorgh::Article` 模型中添加相关代码,以便把 `author_name` 字段转换为实际的 `User` 对象,并在保存文章之前把 `User` 对象和其文章关联起来。为此,需要为 `author_name` 字段设置 `attr_accessor`,也就是为其定义设值方法(setter)和读值方法(getter)。
607+
608+
为此,我们不仅需要为 `author_name` 添加 `attr_accessor`,还需要为 `author` 建立关联,并在 `app/models/blorgh/article.rb` 文件中添加 `before_validation` 调用。这里,我们暂时通过硬编码直接把 `author` 关联到 `User` 类上。
609+
610+
[source,ruby]
611+
----
612+
attr_accessor :author_name
613+
belongs_to :author, class_name: "User"
614+
615+
before_validation :set_author
616+
617+
private
618+
def set_author
619+
self.author = User.find_or_create_by(name: author_name)
620+
end
621+
----
622+
623+
通过把 `author` 对象关联到 `User` 类上,我们成功地把引擎和应用连接起来。接下来还需要通过某种方式把 `blorgh_articles` 和 `users` 数据表中的记录关联起来。由于关联的名称是 `author`,我们应该为 `blorgh_articles` 数据表添加 `author_id` 字段。
624+
625+
在引擎中运行下面的命令可以生成 `author_id` 字段:
626+
627+
[source,sh]
628+
----
629+
$ bin/rails g migration add_author_id_to_blorgh_articles author_id:integer
630+
----
631+
632+
NOTE: 通过迁移名称和所提供的字段信息,Rails 知道需要向数据表中添加哪些字段,并会将相关代码写入迁移中,因此无需手动编写迁移代码。
633+
634+
我们应该在应用中运行迁移,因此需要通过下面的命令把引擎的迁移复制到应用中:
635+
636+
[source,sh]
637+
----
638+
$ bin/rails blorgh:install:migrations
639+
----
640+
641+
注意,上述命令实际只复制了一个迁移,因为之前的两个迁移在上一次执行此命令时已经复制过了。
642+
643+
----
644+
NOTE Migration [timestamp]_create_blorgh_articles.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
645+
NOTE Migration [timestamp]_create_blorgh_comments.blorgh.rb from blorgh has been skipped. Migration with the same name already exists.
646+
Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blorgh
647+
----
648+
649+
然后通过下面的命令运行迁移:
650+
651+
[source,sh]
652+
----
653+
$ bin/rails db:migrate
654+
----
655+
656+
现在,一切都已各就各位,我们完成了作者(用应用的 `users` 数据表中的记录表示)和文章(用引擎的 `blorgh_articles` 数据表中的记录表示)的关联。
657+
658+
最后,还需要把作者名称显示在文章页面上。为此,需要在 `app/views/blorgh/articles/show.html.erb` 文件中把下面的代码添加到“Title”之前:
659+
660+
[source,erb]
661+
----
662+
<p>
663+
<b>Author:</b>
664+
<%= @article.author.name %>
665+
</p>
666+
----
667+
668+
[[using-a-controller-provided-by-the-application]]
669+
===== 使用应用提供的控制器
670+
671+
默认情况下,Rails 控制器通常会通过继承 `ApplicationController` 类实现功能共享,例如身份验证和会话变量的访问。而引擎的作用域是和宿主应用隔离开的,因此其 `ApplicationController` 类具有独立的命名空间。独立的命名空间避免了代码冲突,但是引擎的控制器常常需要访问宿主应用的 `ApplicationController` 类中的方法,为此我们可以让引擎的 `ApplicationController` 类继承自宿主应用的 `ApplicationController` 类。在 Blorgh 引擎的例子中,我们可以对 `app/controllers/blorgh/application_controller.rb` 文件进行如下修改:
672+
673+
[source,ruby]
674+
----
675+
module Blorgh
676+
class ApplicationController < ::ApplicationController
677+
end
678+
end
679+
----
680+
681+
默认情况下,引擎的控制器继承自 `Blorgh::ApplicationController` 类,因此通过上述修改,这些控制器将能够访问宿主应用的 `ApplicationController` 类中的方法,就好像它们是宿主应用的一部分一样。
682+
683+
当然,进行上述修改的前提是,宿主应用必须是具有 `ApplicationController` 类的应用。
684+
685+
[[configuring-an-engine]]
686+
==== 配置引擎
687+
688+
本节介绍如何使 `User` 类成为可配置的,然后介绍引擎的基本配置中的注意事项。
689+
690+
[[setting-configuration-settings-in-the-application]]
691+
==== 在引擎中配置所使用的应用中的类
692+
693+
接下来我们需要想办法在引擎中配置所使用的应用中的用户类。如前文所述,应用中的用户类有可能是 `User`,也有可能是 `Person` 或其他类,因此这个用户类必须是可配置的。为此,我们需要在引擎中通过 `author_class` 选项指定所使用的应用中的用户类。
694+
695+
具体操作是在引擎的 `Blorgh` 模块中使用 `mattr_accessor` 方法,也就是把下面这行代码添加到引擎的 `lib/blorgh.rb` 文件中:
696+
697+
[source,ruby]
698+
----
699+
mattr_accessor :author_class
700+
----
701+
702+
`mattr_accessor` 方法的工作原理与 `attr_accessor` 和 `cattr_accessor` 方法类似,其作用是根据指定名称为模块提供设值方法和读值方法。使用时直接调用 `Blorgh.author_class` 方法即可。
703+
704+
接下来需要把 `Blorgh::Article` 模型切换到新配置,具体操作是在 `app/models/blorgh/article.rb` 中修改模型的 `belongs_to` 关联:
705+
706+
[source,ruby]
707+
----
708+
belongs_to :author, class_name: Blorgh.author_class
709+
----
710+
711+
`Blorgh::Article` 模型的 `set_author` 方法的定义也调用了 `Blorgh.author_class` 方法:
712+
713+
[source,ruby]
714+
----
715+
self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)
716+
----
717+
718+
为了避免在每次调用 `Blorgh.author_class` 方法时调用 `constantize` 方法,我们可以在 `lib/blorgh.rb` 文件中重载 `Blorgh` 模块的 `author_class` 读值方法,在返回 `author_class` 前调用 `constantize` 方法:
719+
720+
[source,ruby]
721+
----
722+
def self.author_class
723+
@@author_class.constantize
724+
end
725+
----
726+
727+
这时上述 `set_author` 方法的定义将变为:
728+
729+
[source,ruby]
730+
----
731+
self.author = Blorgh.author_class.find_or_create_by(name: author_name)
732+
----
733+
734+
修改后的代码更短,意义更明确。`author_class` 方法本来就应该返回 `Class` 对象。
735+
736+
因为修改后的 `author_class` 方法返回的是 `Class`,而不是原来的 `String`,我们还需要修改 `Blorgh::Article` 模型中 `belongs_to` 关联的定义:
737+
738+
[source,ruby]
739+
----
740+
belongs_to :author, class_name: Blorgh.author_class.to_s
741+
----
742+
743+
为了配置引擎所使用的应用中的类,我们需要使用初始化程序。只有通过初始化程序,我们才能在应用启动并调用引擎模型前完成相关配置。
744+
745+
在安装 `blorgh` 引擎的应用中,打开 `config/initializers/blorgh.rb` 文件,创建新的初始化程序并添加如下代码:
746+
747+
[source,ruby]
748+
----
749+
Blorgh.author_class = "User"
750+
----
751+
752+
WARNING: 注意这里使用的是类的字符串版本,而非类本身。如果我们使用了类本身,Rails 就会尝试加载该类并引用对应的数据表。如果对应的数据表还未创建,就会抛出错误。因此,这里只能使用类的字符串版本,然后在引擎中通过 `constantize` 方法把类的字符串版本转换为类本身。
753+
754+
接下来我们试着添加一篇文章,整个过程和之前并无差别,只不过这次引擎使用的是我们在 `config/initializers/blorgh.rb` 文件中配置的类。
755+
756+
这样,我们再也不必关心应用中的用户类到底是什么,而只需关心该用户类是否实现了我们所需要的 API。`blorgh` 引擎只要求应用中的用户类实现了 `find_or_create_by` 方法,此方法需返回该用户类的对象,以便和对应的文章关联起来。当然,用户类的对象必需具有某种标识符,以便引用。
757+
758+
[[general-engine-configuration]]
759+
===== 引擎的基本配置
760+
761+
在引擎中,有时我们也需要使用初始化程序、国际化或其他配置选项。一般来说这些都可以实现,因为 Rails 引擎和 Rails 应用共享了相当多的功能。事实上,Rails 应用的功能就是 Rails 引擎的功能的超集。
762+
763+
引擎的初始化程序包含了需要在加载引擎之前运行的代码,其存储位置是引擎的 `config/initializers` 文件夹。“配置 Rails 应用”一文的<<configuring#initializers,初始化程序>>一节介绍了应用的 `config/initializers` 文件夹的功能,而引擎和应用的 `config/initializers` 文件夹的功能完全相同。对于标准的初始化程序,需要完成的工作都是一样的。
764+
765+
引擎的区域设置也和应用相同,只需把区域设置文件放在引擎的 `config/locales` 文件夹中即可。
766+
767+
[[testing-an-engine]]
768+
=== 测试引擎

0 commit comments

Comments
 (0)