从本书开篇,我们就不断提到 Rails 中一直贯彻的约定大于配置思想。而我们已经在最近几章讲解了 Rails 的底层 gem。现在是时候将两者结合起来,Rails 提供了一组实用的默认 gem,而且这些 gem 你也可以自行添加和修改。
在 Rails 中,gem 是将函数插件化的最基本方式。为了不进行抽象的描述,我们会选择一些插件,并通过它们说明插件的安装及功能。事实上插件会为你的日常工作提供莫大帮助!
我们从金钱相关的插件开始。
使用 Active Merchant 处理信用卡流程
在 161 页我们临时处理了信用卡流程,让用户可以正常支付订单。尽管 Rails 的内核中没有相关功能,但有一个 gem 提供对信用卡的支持。
之前已经了解过如何在应用中管理 gem,所以现在要编辑 Gemfile。本章中我们要学习一些插件,所以我们一次性将它们添加至 Gemfile 中。你可以在文件的任何地方配置,我们选择在文件结尾处理。
gem 'activemerchant', '~> 1.31'
gem 'haml', '~> 4.0'
gem 'kaminari', '~> 0.14'
一定要注意指定最小版本号,这是由我们的最佳实践得来,如果提高了版本号限制可能导致兼容问题。
我们添加的几个 gem 会分不同的章节讲解,本节我们只讨论 Active Merchant。
接着使用 bundle
命令安装依赖。
bundle install
要根据自身的操作系统和使用登录用户,也许你需要以 root 身份运行。
bundle
实践上代理了许多工作。它会检查一遍 gem 依赖,查找运行配置,然后下载和安装必要的组件。不过现在我们不关心,我们只是添加了组件,然后再确保 bundler 将 gem 安装成功。
在安装或更新 gem 后还要处理一件事 —— 重启服务器。虽然 Rails 尽忠职守,也对应用进行了相应的更新,但在添加或替换整个 gem 时它也无法做到十全十美。虽然本节中不会用到服务器,但后面会用到。确认服务器正在运行 Depot。
为了展示相应功能,我们要创建一个脚本,可以将此脚本放置在 script
路径中。
credit_card = ActiveMerchant::Billing::CreditCard.new(
number: '4111111111111111',
month: '8',
year: '2009',
first_name: 'Tobias',
last_name: 'Luetke',
verification_value: '123'
)
puts "Is #{credit_card.number} valid? #{credit_card.valid?}"
脚本中的代码并不多,它创建了 ActiveMerchant::Billing::CreditCard
类的实例,然后调用了 valid?()
方法,让我们运行脚本。
rails runner script/creditcard.rb
Is 4111111111111111 valid? false
我们也没有额外做什么它就正常运行了。这些并不需要 require
声明,在 Gemfile 中列举的 gem 的函数都可以在应用中使用。
现在你已经了解了如何在应用中使用这些方法。此时,通过 migration 向 Orders
表添加字段,至于向 view 添加字段的方法你是知道的。通过之前使用的 valid?()
方法在 model 中添加验证逻辑。如果你到 Active Merchant 的官网,甚至能找到 authorize()
和 capture()
授权及获得支付信息,尽管实现这个功能需要你登录商务门户网站。当一切准备就绪,你就知道如何调用 controller 中的逻辑了。
仔细想想,其实你只要在 Gemfile 添加一行代码就行。
就像本章开篇说的一样,向 Gemfile 添加 gem 是扩展 Rails 最好的方式。这样做的好处可太多了,首先所有的依赖都由 Bundler 管理,其次要使用时可以立即预加载,最后部署时还能轻松打包。
这里只是简单地添加 gem。接下来让我们看看更重要的事,它为 Rails 依赖的 gem 提供了明确的替代方案。
使用 Haml 进行页面美化
首先让我们看看 Depot 中的 view 界面。下面的例子是首页界面:
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1><%= t('.title_html') %></h1>
<% cache ['store', Product.latest] do %>
<% @products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to t('.add_html'), line_items_path(product_id: product),
remote: true %>
</div>
</div>
<% end %>
<% end %>
<% end %>
这段代码中包含了 HTML,还有一些 Ruby 代码。在 <%= ... %>
中的表达式结果将被转换为 HTML 并显示。
这并不是最佳解决方案,但许多 Rails 应用确实有这样的需求。而且在本书开篇时假定大家已经了解 HTML 的知识,但其实许多读者常常使用 Ruby 却才第一次接触 Rails。这种情况下只能将 HTML 作为新语言介绍。
但现在你已经试过了学习困难期,让我们探究一下这门新语言,它更加接近于生产环境中的 Ruby 代码,也就是 HTML Abstraction Markup Language(Haml)。
为了启用它,先删除刚才看到的文件
rm app/views/store/index.html.erb
接着在同一个地方创建一个新文件。
- if notice
%p#notice= notice
%h1= t('.title_html')
- cache ['store', Product.latest] do
- @products.each do |product|
- cache ['entry', product] do
.entry
= image_tag(product.image_url)
%h3= product.title
= sanitize(product.description)
.price_line
%span.price= number_to_currency(product.price)
= button_to t('.add_html'), line_items_path(product_id: product),
remote: true
注意这里使用了新的扩展名 .html.haml
。这表示此模板是一个 Haml 模板而不是 ERB 模板。
你注意到的第一点应该是此文件变小了,下列是对代码中每行首字母作用的一个预览:
横线代表一个 Ruby 声明,并不产生任何输出
百分号(%)表示一个 HTML 元素
等号(=)表示一个 Ruby 表达式,它将产生一个用于显示的输出结果。可以将其处理为单独一行代码,也紧随 HTML 元素之后
点号(.)和井号(#)分别定义 class 和
id
属性。可以与前面的符号结合使用,或者单独使用。如果单独使用,表示为div
元素。表达式末尾的逗号表示连续。在上面的例子中,
button_to()
的调用横跨了两行
缩进是 Haml 中的重点。if
、循环或标签与同级别的缩进匹配。上面的例子中,在 h1
前的段落属于关闭状态,div
前的 h1
也是关闭状态,但 div
元素是内嵌的,分别包含了 h3
元素、span
元素和 button_to()
。
你也看到了,你熟悉的辅助方法都有效,比如 t()
、image_tag()
和 button_to()
。通过这种方式 Haml 便可以像 ERB 一样集成在应用中。不过你也可以混合使用,一部分使用 ERB 模板,另一部分使用 Haml 模板。
如果你已经安装了 Haml gem,就不需要再做额外工作。现在你只需要直接访问首页即可,显示结果如下图。
如果你觉得它看起来很普通,那是因为之前你已经真切地见识过。如果你仔细思考一下,它就显得不那么简单,因为布局文件依然使用 ERB 模板,而首页却使用 Haml 模板。即使这样,所有的集成依然保持无缝衔接并且影响面小。
尽管相比添加任务或辅助方法这是一个更深层次的集成。接下来我们探究一番改变 Rails 核心对象的插件。
分页
有时,我们有一些商品,一些购物车,以及每个购物车或订单中的商品,但实际上我们拥有无限的订单,并且我们希望在订单页快速地显示所有订单。kaminari
插件正满足此功能。
现在上我们生成一些测试数据。虽然我们可以重复点击但电脑更擅长此事。这并不是 seed 数据,只是临时使用的数据。让我们在 script
路径中创建一个文件。
Order.transaction do
(1..100).each do |i|
Order.create(name: "Customer #{i}", address: "#{i} Main Street",
email: "customer-#{i}@example.com", pay_type: "Check")
end
end
此代码可创建一百个订单,但其中没有购买商品。如果你愿意也可以修改脚本添加购买商品。注意它是在一个事务中完成了所有工作。虽然并不完全有这个需要,但它可以加速处理流程。
我们并不需要任何 require
声明或者向数据库的初始化。这些工作都交给 Rails 处理。
rails runner script/load_orders.rb
现在准备工作完毕,接下来是向应用做出必要的修改。首先,我们要在 controller 中调用 paginate()
,并在页面中传递需要显示的订单。
# controllers/orders_controller.rb
def index
@orders = Order.order('created_at desc').page(params[:page])
end
接着在 view 底部添加链接。
<h1>Listing orders</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Email</th>
<th>Pay type</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.name %></td>
<td><%= order.address %></td>
<td><address%= order.email %></td>
<td><%= order.pay_type %></td>
<td><%=pay_type link_to 'Show', order %></td>
<td><%= link_to 'Edit', edit_order_path(order) %></td>
<td><%= link_to 'Destroy', order, method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Order', new_order_path %>
<!-- Here's new line -->
<p><%= paginate @orders %></p>
这就是所有的内容了。默认每页显示三条数据,只有数据超过一页才会显示链接。
controller 中通过 :per_page
参数设置每页显示的订单数,可以参考下图:
总结
虽然本章讲解了一些插件,本章的目的并不是深入了解这些插件,只是介绍了一些插件的功能。
如果包括前面章节介绍的 gem,这些插件只要添加了一些新功能(Active Merchant 和 Capistrano),向 model 对象添加方法(kaminari),添加一种新模板语言(Haml),而且添加了新数据库的接口(mysql)。
认真想想,是不是没有插件无法做到的事。
在 RailsPlugins.org 发现更多插件
现在我们已经学习了三种插件。不过还有一些知识需要学习,有以下分类:
一些插件实现了 Rails 核心功能。例如,jQuery,Prototype 库是由之前的 Rails 版本默认支持。这些功能已经移入了
prototype-rails
插件。还有另一个acts_as_tree
也是非常受欢迎的插件。像rails_xss
为了辅助 migration 将 Rails 的版本特性进行移植。有一些插件实现了重要的共用逻辑甚至是用户接口。
devise
和authlogic
实现了用户授权和 session 管理。我们在 Depot 中是自己实现了这些功能,不过我们并不推荐这样做。我们已经发现了要学习偷懒,如果有人已经实现了你所需要的插件,你便可以花时间在自己的应用中。有一些插件替换了大量的 rails 功能。比如,
datamapper
替换了 ActiveRecord。cucumber
、rspec
和webrat
即可以结合使用,也可以分开使用,它们可以同时替换测试脚本中的普通文本的测试描述、specification 和浏览器模拟。airbrake
和exception_notification
可以在部署服务器中管理错误。
当然,这里只描述了少量的插件。插件还在不断增加,当然在阅读本书时插件的数量肯定又增加了。
最后,你也可以创建自己的插件。不过这已经超出了本书的知识范围,你可以在 Rails 指南和文档中查看更多细节。
本文翻译自《Agile Web Development with Rails 4》,目的为学习所用,如有转载请注明出处。