Finding Your Way Around Rails
在完成 Depot 项目后,下一步准备深入研究 Rails 一番。在本书的余下部分,我们将对 Rails 的一个个主题进行探讨(也就是一个一个模块地进行)。也许在此之前你已经了解过相关的知识,不过我们并不会每个模块都讲到,但论述的模块我们会对如何对其扩展或替换以及如此做的必要性进行讲解。
Part III 中的章节会遍及 Rails 的子系统,包括 Active Record、Active Resource、Action Pack(包括 Action Controller 和 Action View)及 Active Support,甚至还会深入地了解 migration。
接着我们还将深入 Rails 的核心,了解其中的组件如何组装、如何启动及如何替换。了解了 Rails 中组件如何组装后,将以流行的替换组件调查结束本书,其中的许多组件都可以不基于 Rails 进行使用。
现在先讲一下本章的主题,本章覆盖了你需要了解的所有高级别事物,包括文件夹结构、配置和环境。
事物安置何处
Rails 设计了一个运行时文件夹布局,并且提供了应用和脚手架生成器,这些生成器将为你创建相应的布局。比如,如果通过 rails new my_app
命令生成 my_app 应用,应用中的顶级层级结构将如下图所示。让我们从应用路径中最高层级的文本文件开始了解。
-
config.ru 可配置 Rack Webserver 接口,可用于创建 Rails Metal 应用或在 Rails 应用中使用 Rack 中间件。相关细节在 Rails Guide 中了解。
-
Gemfile 可指定 Rails 应用的依赖。在向 Depot 添加 bcrypt-ruby gem 时你已经亲眼看过如何使用。应用依赖还包括数据库、web 服务器及部署脚本。技术上来说,这个文件不由 Rails 使用,而是由应用本身操作。你可以在 config/application.rb 和 config/boot.rb 中查找到对 Bundler 的调用。
Joe 提问:那么 Rails 又在哪里?
Rails 最有意思的其中一点在于它的组件化。从开发者的角度看来,你花费了所有的时间处理高级别的组件,比如 Active Record 和 Action View。这些是 Rails 的组件,不过底层还存在其他组件,这些组件默默运作协调各个组件。如果没有 Rails 组件,许多功能都不可能实现。但同时,这些底层组件中的小部分才是与开发者日常工作息息相关的。在剩下的章节中我们主要讨论与日常工作关联较强的部分。
-
Gemfile.lock 记录 Rails 应用依赖的指定版本。此文件由 Bundler 维护,应该在仓库中进行检查。
-
Rakefile 定义了运行测试、创建文档、提取 schema 结构 及其他种种功能的任务。通过
rake -T
可以列举所有任务,rake -D task
可以查看指定任务的完整描述。 -
README 包含了 Rails 框架的生成信息。
现在,让我们继续了解一下每个文件夹中的内容(尽管不需要按顺序进行)。
应用所在之地
我们的许多开发都在 app 文件夹中进行。应用功能的主要代码都处于 app 文件夹中,如同下图所示。关于 app 中的目录结构会在 Rails 的其他模块中讲解,比如 Active Record、Action Controller 和 Action View 都会在本书后面的章节中讨论。
The main code for our application lives in the app directory测试所在之地
Rails 对于应用的测试提供了丰富的支持,而 test 文件夹就是所有测试相关资源的归属之地,当然也包括定义测试数据原料的 fixture。
文档所在之地
doc 文件夹并不是一个必须的文件夹。Rails 提供 doc:app
的 Rake 命令用于生成文档,而生成的文档将被归置于 doc/ 路径下。除了此命令,Rails 还提供了其他 Rake 命令用于生成文档,比如 doc:rails
将生成正在运行的 Rails 版本文档,doc:guides
将生成操作指南。在构建操作指南前需要向 Gemfile 添加并安装 redcarpet。
除此之处 Rails 还提供了其他的文档相关操作,可以通过 rake -T doc
查看所有文档相关命令。
辅助库所在之地
lib 文件夹存放不适宜单独放置于某一个 model、view 和 controller 的应用代码。比如,你编写了用于创建 PDF 接收的库,便于用户进行 PDF 下载。此接收器直接从 controller 向浏览器传送数据(通过 send_data)方法。创建 PDF 接收器的方法自然就应该放置于 lib 文件夹中。
lib 文件夹也是一个放置 model、view 和 controller 公用代码的好地方。或者需要一个用于验证信用卡余额的库,亦或者进行金融计算的库,也可能是计算东部时间的库时,这些功能都不适合存放在某个 model、view 或 controller 中,而是应该放置在 lib 路径下。
不要觉得只能将一堆文件直接放置在 lib 路径中,其实可以根据函数的相关性组织相应的子文件夹。例如,Pragmatic Programmer 网站中生成接收器的代码,以及生成指导购物的用户指南和其他的一些 PDF 文件的代码,都可以组织在 lib/pdf_stuff 路径中。
前一版本的 Rails 中,可以通过 require 声明自动引入 lib 路径下的代码。不过现在它变成了一个可以开启关闭的选项,只需要在 config/application.rb 中添加如下代码即可:
config.autoload_paths += %W(#{Rails.root}/lib)
只要在 lib 路径下存在文件,并且按上述方式添加了 lib 路径为自动加载,便可以在应用的其他地方使用。如果文件中包含类和 module,并且文件使用类或 module 的名字小写命名,Rails 会自动将其加载。比如,在 lib/pdf_stuff 路径下 receipt.rb 文件中创建了 PDF 接收器,只要类命名为 PdfStuff::Receipt 就能够被 Rails 查找到并自动加载。
对于无法按条件被自动加载的库,可以通过 require 方式处理。如果文件处于 lib 文件夹中,可以直接通过名字引入。比如,lib/easter.rb 为东部时间计算库,需要引用时按下方代码处理即可:
require "easter"
如果相应库处于 lib 的子文件夹中,在 require 声明时需要包含相应的路径名。比如,要引入航空邮件的购物计算库需要按下方代码处理:
require "shipping/airmail"
Rake Task 所在之地
在 lib 中还可以看见一个叫做 tasks 的空文件夹。你可以通过在此添加自定义的 Rake 任务定制应用的自动化操作。本书的主体并不是关于 Rake 的,所以不会深入讲解,不过我们会列举一个简单的例子。
Rails 会提供 Rake 任务告知你最后被执行的 migration,不过如果能查看所有被执行过后 migration 会更有帮助。我们就将编写一个 Rake 任务列举 schema_migration 表中的版本。这些任务都是 Ruby 代码,不过相应的源文件需要使用 .rake 扩展名。db_schema_migrations.rake 如下:
# lib/tasks/db_schema_migration.rake
namespace :db do
desc "Prints the migrated version"
task :schema_migrations => :environment do
puts ActiveRecord::Base.connection.select_values("select version from schema_migrations order by version")
end
end
编写完任务后便可以在命令行中运行:
rake db:schema_migrations
如果想了解更多关于 Rake 任务的知识可以点击 http://rubyrake.org 查看相应文档。
日志所在之地
在 Rails 运行时会产生许多有用的日志文件,它们都被存储在 log 路径下(默认情况下)。在此处可以找到三个主要的日志文件,分别是 development.log、test.log 和 production.log。日志中有许多用于追踪的信息,包括日志记录时间、缓存信息和数据库执行语句。
不同的日志文件记录相应运行环境的日志(在后面讨论到 config 文件夹时再详细讲解环境)。
静态网页所在之地
public 文件夹是对外的门面,web 服务器会将此件夹作为应用的基础。在此路径中使用静态文件(换句话说,也就是不会改变的文件)运行于服务器。
脚本所在之地
如果你需要编写一些有用的脚本,并且可以通过命令行执行,操作应用中的多个运维任务,类似脚本的封装器放在 bin 文件夹中再合适不过了,还可以通过 bundle binstubs
填充此文件夹。
bin 文件夹也存放 Rails 脚本,你在命令行中运行的 rails 命令脚本都位于此路径中,而且向脚本传递的第一个参数表示 Rails 将要执行的函数。
console
允许与 Rails 应用的方法交互
dbconsole
通过命令行直接与数据库交互
destroy
移除通过 generate
命令生成的相关文件
generate
代码生成器,可以通过它创建 controller、mailer、model、scaffold 和 web service。可以在无参数的情况运行指定的 generate
生成器,示例如下:
rails generate migration
new
生成 Rails 应用代码
runner
在应用之外以 web 上下文运行其中的方法。相当于无交互的 rails console
。通过这种方式可以无缓存地调用来自 cron 任务或处理器的邮件方法。
server
在内嵌的 web 服务器中运行 Rails 应用,一般情况下是采用 Mongrel 或 WEBrick。在开发 Depot 期间我们已经使用过 WEBrick 了。
临时文件所在之地
按常理来说 tmp 文件夹就是 Rails 存放临时文件的地方。在此路径中你会看到用于缓存内容、session 和 socket 的子路径。一般情况下这些文件都由 Rails 自动清理,不过偶尔出现错误时就需要查看一下此文件夹并自己清理旧文件。
第三方代码所在之地
vendor 文件夹一般用于存放第三方代码,可以将 Rails 及其所有依赖安装至此路径,就像我们之前部署时一样。
如果你希望使用系统级的 gem 版本,可以直接将 vendor/cache 文件夹删除。
配置文件所在之地
config 文件夹包含了 Rails 的配置文件。在开发 Depot 的过程中,我们在此路径下配置了一些路由和数据库,创建了初始工具,修改了本地化配置,还定义了部署指令,而其他的配置通过 Rails 即可方便地实现。
在运行应用之前,Rails 会加载执行 config/environment.rb 和 config/application.rb。而标准环境的设置就是通过这些文件及下面路径中的其他文件自动加载形成:
-
app/controllers 文件夹及其子文件夹
-
app/models 文件夹
-
vendor 文件夹及 lib 中包含的每个 plugin 子文件夹
-
app、app/helpers、app/mailers、app/services 和 lib 文件夹
只要这些文件夹存在都会被加载到路径中。
而且 Rails 还将加载一个预环境配置文件,此文件位于 environments 文件夹中,你可以在其中替换与依赖环境不同的配置项。
如此操作是因为 Rails 希望根据不同的需求进行相应的处理,作为一个开发人员,在编写代码、测试代码和生产环境运行代码时是非常不同的。在写代码时,你肯定希望大量的日志记录,修改源文件后便于重新加载,还有明显的错误提示等等。在测试阶段,你需要一个孤立的系统,以便可以重复运行测试结果。在生产环境时,系统应该以性能调节为首,并且要避免用户遇到问题。
切换运行时环境的操作是在应用之外,也就是说应用在从开发转移到测试,再转移至生产的过程中不需要修改任何代码。在 233 页我们通过指定 rake 命令的 RAILS_ENV 参数确定相应的运行环境,而且 Phusion Passenger 也可以通过 Apache 配置文件中的 RailsEnv 选项切换环境。当使用 rails server 命令启动 WEBrick 时也可以通过 -e 参数指定运行环境。
rails server -e development
rails server -e test
rails server -e production
如果你有定制需求,比如你拥有其他阶段的环境时也可以创建自己的环境。创建新的环境需要在数据库配置文件中添加新的部分,并且在 config/environment 中创建新文件。
放入的这些配置文件完全为你所用,而且在通过 rake doc:guides
命令生成的 Rails 应用配置指南中也可以看到你设置的配置参数列表,同时这些信息也可线上查看。
命名规范
刚接触 Rails 的人员对于它能够自动处理物料的名称感到疑惑。当人们调用 model 类 Person 时 Rails 会知道需要查找数据库中 people 表这点让大家十分惊讶。这节中你将了解这些隐藏的命名运行方式。
这些规则都是使用 Rails 时的默认约定,不过也可以通过修改配置项改变相应的规范。
大小写混合,下划线及复数
通常我们都是通过短语命名变量和类。在 Ruby 中,变量命名的规范是字母全部小写,单词之间由下划线分隔。类和 module 的命名规范略有不同,它们不使用下划线,而是短语中的单词都首字母大写(包括第一个词)。(我们又称这种形式为 mixed case(大小写混合),至于原因的话显而易见)。这些规范导致变量名像 order_status 一样,而类名如同 LineItem 一样。
Rails 继承了这些规范同时在两方面进行了扩展。首先是对数据库表名进行了设计,使其如同变量名一般,都是小写字符同时在单词间使用下划线,其次是约定表名都为复数形式。这也就使表名如同 orders 和 third_parties 一类。
在另一个维度,Rails 也约定文件名使用小写,并通过下划线连接单词。
Rails 利用这些命名规范自动转换各种名称。比如,应用中可能存在一个用于处理 line item 的 model 类,你按照 Ruby 命名规范将其取名为 LineItem。通过名字 Rails 便可以自动推演出下列结论:
-
相应的数据库表叫做 line_items,也就是将类名小写,再通过下划线连接单词以及将其复数化。
-
Rails 还知道在 line_item.rb (app/models 路径下)的文件中可以查找到类的定义。
除此之外 Rails controller 还有其他规范。如果应用中有 store controller,那下列现象就会发生:
-
Rails 会假设类被取名为 StoreController,并且此文件为 app/controllers 路径下的 store_controller.rb 文件。
-
Rails 还会查找 app/helpers/store_helper.rb 中的 StoreHelper 辅助 module。
-
并且 Rails 还会在 app/views/store 中查找 controller 相应的 view 模板。
-
Rails 会默认将这些 view 作为输出,并且将它们包装至包含 app/views/layouts 路径下的 store.html.erb 或 store.html.erb 文件的布局模板中。
所有的规范都展示在下表中:
Model 命名
Table | line_items |
File | app/models/line_item.rb |
Class | LineItem |
Controller 命名
URL | http://../store/list |
File | app/controllers/store_controller.rb |
Class | StoreController |
Method | list |
Layout | app/views/layouts/store.html.erb |
View 命名
URL | http://../store/list |
File | app/views/store/list.html.erb(or .builder) |
Helper | module StoreHelper |
File | app/helpers/store_helper.rb |
除了上述的约定之处还有一个额外的诀窍,一般情况下 Ruby 代码使用相应的类和 module 之前都需要通过 require 引入相应的源文件。不过因为 Rails 了解文件名和类名之间的关联性,所以一般情况下 Rails 应用中是不需要 require 声明的。当你第一次使用未知的类或 module 时,Rails 会通过命名规范将类名转换为文件名,并在后台进行加载。典型事例就是使用 model 类时,此 model 类会自动加载至应用中。
module 中的分组 controller
直到如今,我们所有的 controller 都放置于 app/controllers 路径下,有时它十分便于向其中添加更多的结构。比如,商店应用需要几个关联的 controller 运行,但管理方法并没有交集。相比污染顶级命名空间我们宁愿选择将其组织至 admin 命名空间中。
Rails 只是使用了一个简单的命名规范。如果请求携带 controller 名称 admin/book,Rails 就将查找 app/controllers/admin 中的 book_controller。也就是说 controller 名字的最后部分表示此文件叫做 name_controller.rb,而其他部分的信息都将用于在 app/controllers 的路径下进行导航。
想象一下,如果程序中有两组 controller(分别是 admin/xxx 和 content/xxx ),并且两组 controller 中都定义了 book controller。也就是在 app/controllers 的 admin 和 content 子路径中都有 book_controller.rb 文件,而且文件中都定义了类 BookController。如果 Rails 没有更进一步的考虑,这两个类将发现冲突。
为了处理这个问题,Rails 将 app/controllers 子路径中的 controller 采用 Ruby 的 module 命名方式。所以,admin 子路径中的 book controller 声明如下:
class Admin::BookController < ActionController::Base
#...
end
而 content 子路径中的 book controller 就要声明为 Content module 中的类:
class Content::BookController < ActionController::Base
#...
end
如此便可以保证两个 controller 在应用中是可以区分的。
这两个 controller 的模板也分别在 app/views 对应子文件夹中。因此,view 模板与请求也是一致的:
http://my.app/admin/book/edit/1234
对应下面文件:
app/views/admin/book/edit.html.erb
你肯定十分乐意了解这种 module 中 controller 的生成方式,只需要按下面的命令创建即可:
rails generate controller Admin::Book action1 action2 ...
David 提问:为什么表名要为复数?
因为这样更加符合习惯,实际使用时就会如同「Select a Product from products」及「Order has_many :line_items」这样。
其实目的是为了构建编程语言与利于沟通的领域语言的桥梁。有这样的语言就可以阻止奇怪的翻译转换问题,而不会与客户讨论的是「product description」但实现出来是 「merchandise body」了。这样的交流问题与导致的错误其实息息相关。
如果按照 Rails 的约定将减少大量的自定义配置。开发人员也将因此获益,他们将降低不同习惯的争执而获得更大的生产力解放。
总结
每个部分在 Rails 中都有所属之地,本章我们系统地探索了各个角落。在每个地方,文件和数据都遵从着相应的命名约定,同时我们也可以按规范将它们互相转换。本章的内容填补了下列几个知识空白。
-
生成 Rails 应用的 API 和用户指南
-
添加 Rake 任务显示 migration 版本
-
展示如何配置每个 Rails 的执行环境
接下来,我们将介绍 Rails 中的主要子系统,就先从其中最大的子系统 Active Record 开始吧。
本文翻译自《Agile Web Development with Rails 4》,目的为学习所用,如有转载请注明出处。