Rails 引擎初探

当出现同一个功能模块需要被多个 Rails 项目使用时, Rails 引擎(engine) 就是一个很好的选择。

关于 Rails Engine, 官方文档已经写的很详细了。

这里我记录一下开发一个引擎时各部分的写法。

初始化

rails plugin new foo --mountable

这会创建一个引擎的基本骨架,这里 foo 就是引擎的名字,由于使用了 --mountable 选项。
Foo 这个单词会作为命名空间的存在,非常重要。

目录结构

一个引擎的目录简单来说就是一个加了命名空间的 rails 项目。其 controller model view 包括 assets layout 都要加命名空间(rails 中命名空间就对应文件目录)。

一个常规的 rails 项目本身也是一个引擎,宿主和引擎之间没什么本质的区别,存在的差异只是因为职责的不同而已。

由于引擎是依附于宿主 rails 应用的,因此引擎有自己的 gemspec(e.g foo.gemspec),引擎以 gem 的形式存在。

只不过相比于普通的 gem,它更加全面,mvc 三层都被覆盖到了。

不过引擎一般没有自己的持久化配置,因为引擎是要被宿主调用的,因此数据库配置一般都是由宿主决定的,引擎只提供逻辑代码。

另外引擎可以有自己的 migration,宿主可以使用命令 rake foo:install:migrations 来把存在于引擎 foo 中的 migration 复制到自己的 db/migrate 目录下,便于执行。

与宿主的交互

在编写引擎的逻辑时和一般的 rails 应用没什么区别,重点就是如何和宿主应用进行交互。

路由

一个最简单的引擎只需要被宿主挂载就可以了,只要在宿主的路由中加入一行:
mount Foo::Engine => "/foo", 意为引擎 Foobase url 就是 /foo
如果不用这个 base url 把路由区分开来的话,就有可能出现路由被覆盖的情况。
注意这里的 base url 和引擎的命名空间完全没有关系, 因此引擎当中的所有内部跳转 必须 全部使用
具名路由来表示,这样才不会被 base url 所影响。

assets

引擎的 assets 是完全独立的,要设定 precompile 的话需要在 lib/foo/engine.rb 中定义:

initializer "foo.assets.precompile" do |app|
  app.config.assets.precompile += %w(foo/admin.css foo/admin.js)
end

assets 的路径(包含 css,js,图片等)都要使用 rails 提供的 helper 方法(assets_path 等)来指定。否则会出现和路由一样的问题。

view

view 层是最不通用的一层,用过 devise 的都知道, view 层是几乎一定会被 overwrite 的。
所以写引擎的时候尽量把 view 层写的简洁一些,不要有复杂逻辑,这样 overwrite 的时候比较方便。
注意 layout 文件的位置是 layouts/foo/xxx 而不是 foo/layouts/xxx 哦。

model

引擎中的模型都是带有命名空间的(它们对应的表名也有命名空间的前缀), 在调用时最好加上命名空间(不然可能出现找不到定义的情况)。这点可以参考 Shoppe,包括在定义模型的关系时,也最好指定带有命名控件的 Class Name。

controller

这是有点麻烦的一层,跟宿主的耦合比较严重,比如说如果我想要复用宿主应用中的权限管理(在宿主的 app/controller/admin/base_controller.rb 中),那么我不得不让引擎的 base_controller 都继承 ::Admin::BaseController 才行,这是非常严重的耦合。
rails guide 是这么写的:

An easy way to provide this access is to change the engine's scoped ApplicationController to inherit from the main application's ApplicationController.

意思是说哪怕是非常简单的获得 current_user 的方法,我也要定义在宿主应用的 application_controller 中才能让引擎调用到,我写在 base_controller 中就不行了。
目前看起来这个问题无解。。。好在自己开发引擎的情况绝大多数都是给内部应用使用的,所以这也不算很大的问题。

tips

独立数据源

如果引擎需要自己的独立数据库,可以让引擎中的 model 都继承一个 base_model, 在其中配置数据源:

module Foo
  class BaseModel < ActiveRecord::Base
    self.abstract_class = true
    establish_connection "foo_#{Rails.env}".to_sym
  end
end

ps: 如果宿主使用的不是 active_record,而引擎是的话,这种情况可以不指定 establish_conecton, 一样可以做到引擎有独立的数据源, 因为框架还是会读取 config/database.yml,然后根据使用的持久化框架来自动指定对应的数据库。

元编程

在引擎中难免需要和宿主中的类和对象进行交互,这样就需要动态的修改宿主程序。
这就要用到元编程了,在 ruby 语言中,使用元编程更是家常便饭了。
在引擎中一般都在 lib/foo.rb 中个引擎同名文件中进行一些初始化工作,当然也包含元编程。
这里需要引入 decorators 这个 gem,然后就可以开始对宿主的类和对象开刀啦。
要使用 decorators,要先在 lib/foo/engine.rb 中初始化:

class << self
  attr_accessor :root
  def root
    @root ||= Pathname.new(File.expand_path('../../../', __FILE__))
  end
end
config.to_prepare do
  Decorators.register! Engine.root, Rails.root
end

常见的例子就是给宿主的 User 类添加一些和引擎的逻辑相关的方法。

require 'decorators'
module Foo
  mattr_accessor :user_class
  class << self
    def decorate_user_class!
      Foo.user_class.class_eval do
        def say_hello
          puts "hello from foo"
        end
      end
    end
    def user_class
      Object.const_get(@@user_class)
    end
  end
end

顺便说一句,这里的 User(用户)的类名是可以在宿主里指定的,只要在宿主初始化的时候定义诸如:
Foo.user_class = "Student" 这样的一行代码就行了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容