Rails 有非常多的约定,比如当我用这行命令创建一个资源,框架会帮我做很多决策,也会帮我创建相应的代码文件。
rails generate resource post title:string description:text
posts
id
公共的约定降低了初学者的门槛,一个工程师并不需要懂太多计算机知识,也不需要花费太多时间去配置框架的方方面面,就可以搭建出一个像样的网站。
在现实世界中,很多公司还会在 Rails 框架之上再抽象出一层业务框架。新人要开展工作,不但要精通技术,还要把一堆模型的逻辑关系理清楚,即使是有经验的工程师也会怀疑人生:“我是来写代码的,还是来读历史的?”
拿我公司的一个子产品入职流程来举例,这个产品帮助 HR 管理入职流程。HR 可以像搭积木一样搭建各种入职流程,“产品经理入职流程”,“程序员入职流程”,“设计师入职流程”等等。
当有新员工入职时,HR 选择一个合适的流程让他们入职,员工一步步完成任务,就可以顺利的入职了。
我的主要的工作就是开发一个个的模块(积木)。我自认为整个业务代码的设计还是比较符合 Open–closed 的原则。添加一个模块几乎不需要修改老代码,主要就是创建子类 (Concrete class),增加和某个模块相关的特殊 API,测试,locale 翻译文件,配置文件等等。
表面上不复杂,但因为涉及的业务模型太多,其他工程师没有一两个月根本插不上手,主要有这几个原因:
如果有些工作是重复的,有套路的,有什么办法让降低业务开发的门槛,提高开发效率?
很多人首先想到的办法是写文档,事无巨细都写到文档里。让后人照着做就可以了。
最人性的办法是安排一个 mentor,手把手告诉新人:”Hi,虽然有 30 个表,但是你需要加的只有这三个文件。这个,这个,还有那个。。。“
最不近人情的办法,写非常完备的测试。如果别人不按规矩提交代码,就让集成测试 (CI) 直接挂掉。这加剧了别人的痛苦,但是没提高别人的工作效率。
今天我想介绍的是另外一种办法 Rails generator。
我们每天的日常工作都会用到它。
# 创建一个 rails 项目
rails new blog
# 创建一个 db migration 文件
rails generate migration AddNameToUser name:string
# 创建一个 model
rails generate model post title:string description:text
# 创建一个 resource 对应的全套文件,model,view,路由。
rails generate resource product slug:string name:string
简单来讲,一个 generator 内部定义了模版文件,当我们运行时,它会基于模版文件生成代码文件,然后复制到目标目录中。
下面我给大家介绍制作一个简单 generator 的过程。这个 generator 的用途是,当工程师执行 rails generate onboarding_module paychex
时:
Rails 有一个专门的 generator 来创建 generator。运行这个命令来创建我自己的 onbaording_module 生成器。
bin/rails generate generator onboarding_module
它会帮我创建以下目录和文件
# 生成器的主要逻辑放在这个文件里
create lib/generators/onboarding_module/onboarding_module_generator.rb
# 帮助文档
create lib/generators/onboarding_module/USAGE
# 模版文件
create lib/generators/onboarding_module/templates
└── onboarding_module
├── USAGE
├── onboarding_module_generator.rb
└── templates 👈👈👈 看这里,这些是我创建的模版。
├── factories
│ ├── template_module.rb.erb
│ └── employee_module.rb.erb
├── mapper.rb.erb
├── models
│ ├── template_module.rb.erb
│ └── employee_module.rb.erb
├── module.yml.erb
└── spec
├── template_module_spec.rb.erb
└── employee_module_spec.rb.erb
这些模版都是 erb 文件,内部嵌有实例变量,if/else
,我可以动态的生成各种代码文件。
# templates/factories/template_module.rb.erb
FactoryBot.define do
factory :company_<%= @module_name -%>_module, class: Onboarding::<%= @module_name.camelize -%>Module do
company_onboarding_process { nil }
deleted_at { nil }
module_type { '<%= @module_name %>' }
name { "TODO: Write this module's name" }
sequence(:sequence) { |n| n - 1 }
config { }
trait :with_dependencies do
after(:create) do |mod|
<%- @actions.each do |action| -%>
create(:company_onboarding_action, :<%= action -%>, company_onboarding_module: mod)
<%- end -%>
mod.reload
end
end # end of trait
end # end of factory
end # end of top
Rails generator 提供了很多接口来操作文件,我们可以通过调用不同接口来加工文件。
一, template
方法。它可以渲染 erb 模版,生成对应的代码文件,并放到目标位置。我用这种方式生成 model, factories, rspec 测试。
template(
'models/company_module.rb.erb',
'app/models/#{file_name}_module.rb'
)
end
二, inject_into_file
,在文件的某个位置插入一段代码。
在目标文件 foo.rb 做作一个标记
ModuleList = [
# 我的标记
'w4_module',
'paychex_module',
]
在这个标记的后面插入目标文件
inject_into_file 'app/models/foo.rb', after: "# 我的标记" do
'new_module',
end
执行后的效果
ModuleList = [
# 我的标记
'new_module' # 👈 看这里,这一行是新添加的
'w4_module',
'paychex_module',
]
Rails generator 提供的接口非常多,可以添加 gem, 替换文件内容,在这里就不一一涵盖了。你可以在 官方文档 查看。
一个不熟悉业务的工程师开展工作,只需要运行 rails generate onboarding_module paychex
就可以搭好业务架子,然后填上自己的业务代码就搞定了。
这是效果图