- 浏览: 31911 次
- 性别:
- 来自: 四川
最新评论
使用 ActiveScaffold 增强 Ruby on Rails 的功能
http://hi.baidu.com/yishenyi/item/89db9fc52c4d2d47a9ba94c0
使用 ActiveScaffold 增强 Ruby on Rails 的功能
节省点时间,少一些头痛,使用 Ruby on Rails ActiveScaffold 插件可以创建更容易维护的页面。ActiveScaffold 可以处理用户接口所需的所有 CRUD(创建、读取、更新和删除)操作,这样可以为您节省更多时间来重点关注更有挑战(也更有趣的)问题。
为复杂应用程序编写基于 Web 的数据输入 UI 永远都不是件快乐的事,通常都是非常单调乏味的。良好用户界面的一个关键属性是一致性,但是这需要一个博学勤勉的开发团队才能设计符合这种设计标准的 Web 页面。与其他 Web 应用程序框架类似,Ruby on Rails 也有相同的问题。不过,Ruby 语言的动态特性提供了一个解决方案:ActiveScaffold。ActiveScaffold 是 Ruby on Rails (也称为 Rails)的一个插件,它可以动态地生成基于模型的视图。ActiveScaffold 不需要手工创建页面来显示模型,而是可以从内部审视 ActiveRecord 模型,并动态地生成一个 CRUD(创建、读取、更新、删除)用户界面来管理这些对象。
本文是基于 ActiveScaffold、Ruby 和 Rails 的当前(撰写本文之时)可用的最新版本来撰写的(链接和版本号请参看 参考资料)。另外,本文假设您非常熟悉 Ruby on Rails,并且正在使用 Linux® 或 Mac OS X 系统。Windows® 用户应该修改本文中给出的命令来适合自己的环境(例如,将 ‘ruby’ 添加到脚本命令最前面)。
安装 ActiveScaffold
由于 ActiveScaffold 是一个 Rails 插件,可以从一个远程 Web 或者 Subversion 服务器上安装。下面的命令将从 ActiveScaffold Subversion 服务器中获取 ActiveScaffold。
清单 1. 安装 ActiveScaffold 插件
script/plugin install http://activescaffold.googlecode.com/svn/tags/active_scaffold
注意这将获取 ActiveScaffold 的当前发行版(即最新发行版)。撰写本文时使用的是 1.0 发行版,但是也可以使用将来的发行版:ActiveScaffold 开发人员迄今为止一直很好地关注着兼容性问题。
回页首
模型
最现代的 Web 应用程序框架都基于 MVC(模型、视图、控制器)模式,Rails 也不例外。模型表示数据库中存储的数据,每个表在 Ruby 中都有一个对应的 ActiveRecord 模型类。在本文中,我们创建了一个简单的项目跟踪应用程序,其中,组织拥有很多用户和很多项目。下面的代码显示了 ActiveRecord 向应用程序和对应模型类上迁移的过程。注意模型类要比 Java 中相同的类简单很多。这是 Rails 的 DRY(不要重复自己)原则的基本例子。由于迁移早已包含了列,为什么还要在模型类中再次将它们列出来呢?
清单 2. 迁移
class AddOrganizations < ActiveRecord::Migration
def self.up
create_tablerganizations do |t|
t.column :name, :string, :limit => 50, :null => false
end
end
def self.down
drop_tablerganizations
end
end
class AddUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :first_name, :string, :limit => 50, :null => false
t.column :last_name, :string, :limit => 50, :null => false
t.column :email, :string, :limit => 100, :null => false
t.column :password_hash, :string, :limit => 64, :null => false
t.columnrganization_id, :integer, :null => false
end
add_index :users, :email, :unique => true
end
def self.down
drop_table :users
end
end
class AddProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.column :name, :string, :limit => 50, :null => false
t.columnrganization_id, :integer, :null => false
end
end
def self.down
drop_table :projects
end
end
class AddProjectsUsers < ActiveRecord::Migration
def self.up
create_table :projects_users do |t|
t.column :project_id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :role_type, :integer, :null => false
end
end
def self.down
drop_table :projects_users
end
end
清单 3. 模型
class User < ActiveRecord::Base
belongs_torganization
end
class Organization < ActiveRecord::Base
has_many :projects
has_many :users
end
class Project < ActiveRecord::Base
belongs_torganization
has_many :projects_users
has_many :administrators, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 3"
has_many :managers, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 2"
has_many :workers, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 1"
end
class ProjectsUser < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
插件和生成器的对比
Ruby on Rails 可以支持两种 “开发助手”。生成器 是静态的 — 它们只会在生成代码时运行一次。插件 是动态的 — 它们可以作为应用程序运行时的一部分运行。例如,标准的 Rails scaffold 生成器就是运行一次来基于模型中的当前字段创建一个静态 HTML 模板。如果希望添加一列,就必须重新生成视图(丢失所做的更改),或者将字段手工添加到视图中。这将给模型更改增加不必要的复杂性。
插件会在运行时生成这些视图,因此更改模型就不困难了。与生成器相比,插件并没有什么实际的劣势,但是可能会比使用生成器稍微复杂一点。
User、Organization 和 Project 表都代表域中的传统实体,而 ProjectsUsers 表则会在 Project 和 User 实体之间增加一个多对多的关系。在本例中,它会添加一个 role_type 属性,它代表用户在项目中所扮演的角色。用户可能是工人、经理和/或管理员。
在模型上创建任何用户界面所需要的关键信息都是要理解模型之间的关系。通过在模型中声明 has_many 和 belongs_to,就在它们之间定义了一种特定类型的关系。一旦 ActiveScaffold 知道了这些关系,就可以提供一个用户界面以一种用户可以理解的方式对这些对象进行操作。在这种情况下,ActiveScaffold 就可以确定某个 Project 是由某个 Organization 所有的,因此可以相应地调整用户界面。如果您更改了这种关系,则用户界面就可以相应地变化,无需开发人员更改 UI。
边注:由于 Rails 迁移框架中存在某种限制,使清单 2 中的迁移无法使用外键。为了确保数据一致性,强烈推荐使用这些外键。Redhill Consulting 提供了一个很好的 foreign_key_migrations 插件,它增加了在 Rail 数据库迁移框架中对外键的支持;有关更多信息,请参看 参考资料 中的链接。
回页首
Rails scaffold
现在我们已经充实了模型,接下来可以在上面放一个 Web 界面。Rail 提供了一个 “scaffold” 生成器,它可以为某个给定模型生成一组基本的 CRUD 页面。下面的命令用来为这个模型创建标准的 Ruby scaffold:即一个具有一组 CRUD 方法和一组对应的模型 HTML 视图的控件。
清单 4. 生成标准的 Rails scaffold
script/generate scaffold user
script/generate scaffold organization
scaffold 生成器有几个重要的限制:
* 没有关系支持:创建模型实例就意味着只能编辑实例的基本属性。如果模型需要定义一个关系(例如,Project 需要选中所有的 Organization),就需要手工修改页面将这个域添加到窗体上。
* 不能往返:对于模型反复进行的更改不支持 “往返” 操作,这是因为所生成的代码是静态的。一旦代码被修改之后,就不能在不丢失所做更改的情况下重新生成 scaffold 了。
* 缺少样式支持:所生成的页面都是最基本的黑色和白色,只有最少量的 CSS 支持。不对基本的 HTML 标记使用样式就不支持通过 CSS 使用皮肤功能。
同时具有前两个限制说明 scaffold 实际上更像一个玩具,而不像一个有用的工具。图 1 给出了 Rails 提供的默认 scaffold。
图 1. 标准的 Rails scaffold
图 1. 标准的 Rails scaffold
Rails 还包括了 dynamic scaffold,它实际上提供了相同的代码支持,而不需要提前生成控件代码。这并没有给您带来太多好处 — 因为大部分代码都位于 HTML 视图中,而且仍然需要视图代码。通过将 scaffold 方法添加到控件类中可以启用动态 scaffold。
清单 5. 添加 Rails 的标准 scaffold
class UsersController < ApplicationController
scaffold :user
end
小心!
如果标准的 Rails scaffold 代码与 ActiveScaffold 一起使用,就可能会出现问题。在切换到 ActiveScaffold 之前,请确保您已经清除了所有的 scaffold 控件和视图代码。
回页首
ActiveScaffold 默认显示
ActiveScaffold 为模型提供了一个更加有用的 UI。scaffold 的上述 3 个问题都已解决。首先,我们需要修改控件来使用 ActiveScaffold scaffold:
清单 6. 添加 ActiveScaffold scaffold
class UsersController < ApplicationController
active_scaffold :user
layout "activescaffold"
end
然后为所有 ActiveScaffold 页面添加标准布局(将下面的代码放入 app/views/layouts/activescaffold.rhtml 中):
清单 7. ActiveScaffold 标准布局
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>My Application</title>
<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>
</head>
<body>
<%= yield %>
</body>
</html>
现在用户清单看起来就好多了:
图 2. 标准的 ActiveScaffold scaffold
图 2. 标准的 ActiveScaffold scaffold
这个默认显示可以很好地用于快速构建原型或开发。然而,与所有默认情况类似,稍加定制就可以使它更加适合于您的特定需求。
回页首
定制视图
ActiveScaffold 有几个钩子函数可以让您定制如何显示模型。可以给 active_scaffold 方法传递一个用来配置 scaffold 的可选配置块。
全局配置
ActiveScaffold 的全局配置允许对所有控件进行定制:
清单 8. 全局配置
class ApplicationController < ActionController::Base
AjaxScaffold.set_defaults do |conf|
conf.list.results_per_page = 20
end
end
这个例子配置系统中所有的 ActiveScaffold scaffold 以便在显示记录时每页显示 20 个结果。
本地配置
每个控件 scaffold 都可以使用自己特有的 ActiveScaffold 配置。
清单 9. 特定控件的本地配置
class UsersController < ApplicationController
active_scaffold :user do |conf|
conf.modules.exclude :update
conf.list.label = 'People'
conf.list.sorting = [{:last_name => :ASC}, {:first_name => :ASC}]
conf.list.columns.exclude :password_hash
end
end
这个例子就不能更新模型实例、更改列表标题和定制默认用户列表排序了。sorting 让您可以控制如何从数据库中返回记录,并期望得到一个 {column => direction} 散列数组。还配置 ActiveScaffold 不显示用户不需要查看的特定列;在本例中,password_hash 列不需要在 UI 中显示,因此就将其排除了。
显示 ActiveRecord 对象
to_label 方法让您可以定制模型实例在页面中的显示方式。默认情况下,ActiveScaffold 会查找模型中的一组方法:
* to_label
* name
* label
* title
* to_s
最后一个方法是由 ActiveRecord 提供的,会显示成 “#:<Address:0xFFFFFF:>” 的形式,这对于用户来说不够友好。下面是一个更好的方法:
清单 10. 定制模型的显示
class User < ActiveRecord::Base
belongs_torganization
def to_label
first_name << ' ' << last_name
end
end
例如,用户的 to_label 可能是 John Doe。
定制属性显示
ActiveScaffold 允许开发人员完全控制模型属性的实际显示方式。默认情况下,ActiveScaffold 只会对一些简单的属性值调用 to_s,从而确定它们到 HTML 的顺序。要对此进行定制,只需要在 app/helpers/<model>_helper.rb 中将一个列显示帮助方法添加到相应的帮助类中即可。
清单 11. 定制属性显示
def birthdate_column(record)
record.birthdate.strftime("%d %B %Y")
end
在上面的帮助方法中,您拥有记录的全部访问权限。在本例中,这个帮助并不是很智能,因为它并不能说明用户所请求的现场,确定日期格式就需要用到该现场。
对于 has_many 和 has_and_belongs_to_many 关联来说,ActiveScaffold 会通过使用上面提到的 to_label 逻辑来渲染它们,从而显示前 3 个条目。这 3 个条目会链接在一起,这样在点击时,整个关联就可以显示出来了。这可以防止用户界面被大型关联集所覆盖的情况。
窗体显示
ActiveScaffold 也可以基于 Rails 的 ActiveRecord 和 ActiveView 库为模型创建一个窗体。varchar 列会变成文本输入,boolean 型变量会映射成 HTML 的复选框等等。
有一点需要注意:虚拟属性(在模型中作为属性定义,但却不真正保存在数据库中的属性)的 HTML 渲染方式可能与普通的模型属性不同。任何名字中包含 “password” 的普通模型属性在 HTML 都会渲染为一个密码输入。不过对于虚拟属性来说却并非如此,在使用虚拟属性作为密码窗体输入时,很容易发现这一点。在这种情况下,我们将使用虚拟属性来捕获窗体输入,并在保存时将这些值映射到 password_hash 列中,这样用户的纯文本输入就可以作为一个 SHA256 散列安全地保存到数据库中。
清单 12. 在用户模型中创建虚拟属性
require 'digest/sha2'
class User < ActiveRecord::Base
attr_accessor :password, :password_confirmation
validates_presence_of :password, :password_confirmation
def validate
errors.add('password', 'and confirmation do not match') \
unless password_confirmation == password
end
def before_save
self.password_hash = Digest::SHA256.hexdigest(password) if password
end
end
我们添加了两个 form_column 帮助方法将它们作为密码输入正确地进行渲染。ActiveScaffold 期望使用 field_name 参数中给定的名称对输入进行 POST 处理。
清单 13. 定制虚拟属性的窗体显示
def password_form_column(record, field_name)
password_field_tag field_name, record.password
end
def password_confirmation_form_column(record, field_name)
password_field_tag field_name, record.password_confirmation
end
回页首
关系
到现在为止,我们只考虑了基本的模型操作,例如显示或编辑简单的列值。ActiveScaffold 中最复杂的部分是确定模型之间的关系以及它们如何影响应用程序的用户界面。要正确实现操作就几乎无法避开这一部分;本节将介绍如何配置 ActiveScaffold 来正确使用模型。
列表显示
为了在模型之间进行导航,ActiveScaffold 会在一个列表视图中显示关系链接。举例来说,在查看一个组织列表时,会看到一个 Users 链接来显示一个页面,其中每个 Users 都对应一个给定的 Organization。要定制此链接,需要为该列定义一个帮助方法:
清单 14. 定制关联的显示
def users_column(record)
name = "user"
name = "users" if record.users.size > 1
"<a href="/user/list?user_id=#{record.id}">#{record.users.size} #{name}</a>"
end
窗体显示
ActiveScaffold 还提供了基于所定义的关系在模型之间进行导航的功能。以 belongs_to 关系为例。在上面的例子中,User belongs_to 一个组织。这就意味着一个 User 在创建时必须具有一个相关的 Organization(如果 Organization 是可选的,那么您就应该使用一个可以为空值的 has_one 关系)。 ActiveScaffold 可以理解这种关系,并可以使用 “select” 语句从数据库中显示一个 Organizations 列表,这样用户可以选出与正在创建的 User 关联在一起的 Organization。
这对于只有 10 到 20 个 Organization 的小型数据集而言可以很好地工作,但是却不能扩展到具有大量的 Organization 的情况。您可以使用窗体列渲染程序来重写对列的渲染。下面给出了一个简单的例子,其中,您可以在开发时获悉可能的值:
清单 15. 使用静态选择列表
def organization_form_column(record, field_name)
# simple example that just hard codes two possible values
select_tag field_name, options_for_select('IBM' => '1', 'Lenovo' => '2')
end
回页首
搜索记录
ActiveScaffold 提供了一些有用的搜索功能来查找大型表中的记录。默认情况下,scaffold 在上面的目录表中有一个 “Search” 链接,使用该链接可以打开一个文本框,用户可以在这个文本框中输入搜索条件。ActiveScaffold 会创建一条 SQL 语句为模型搜索所有的 varchar 列,这样输入诸如 “ham” 之类的条件就可以找到基于姓氏的用户记录了。与其他地方类似,这里也有几个配置选项。
实时搜索
当用户按下 Return 时,就会执行默认搜索。ActiveScaffold 可以通过启用 “实时搜索” 选项来进行实时搜索。这会基于用户当前输入每秒生成一个 Ajax 调用。记住,实时搜索可能是数据库密集型的。正如下面解释的一样,在使用这个特性之前,要确保您已经配置了要搜索的列,并且已经正确地创建了表索引。
清单 16. 在实时搜索和默认搜索之间进行切换
ActiveScaffold.set_defaults do |conf|
conf.actions.exclude :search
conf.actions.add :live_search
end
调节可用性
清单 17. 调节 scaffold 的搜索配置
active_scaffold :user do |conf|
conf.live_search.columns = [:last_name, :first_name]
conf.live_search.full_text_search = false
end
这段代码告诉 ActiveScaffold 要限制对这个 scaffold 的搜索 —— 只允许使用用户的姓和名进行搜索,禁用全文搜索。后一个选项是用于大型表的伸缩性调节选项。如果用户搜索 “ham”,默认情况下,ActiveScaffold 会生成一条 SQL 语句,其中包含以下内容:lower(column_name) LIKE "%ham%",无法为它编制索引。通过禁用全文搜索,告诉它使用 “以...开始” 的语义:lower(column_name) LIKE "ham%"。这固然限制了搜索的灵活性,但得到了更好的伸缩性。
回页首
定制操作
除了标准的 CRUD 操作之外,ActiveScaffold 还可以让您定义自己的控件操作。数据库应用程序经常需要将数据导出为 PDF、Excel、CSV 或 XML。添加此功能非常容易,首先我们可以为具有对应操作方法的控件增加一个 “操作链接”:
清单 18. 定义定制操作
class UsersController < ApplicationController
active_scaffold :user do |conf|
conf.action_links.add 'export_csv', :label => 'Export to Excel', :page => true
end
def export_csv
# find_page is how the List module gets its data. see Actions::List#do_list.
records = find_page().items
return if records.size == 0
# Note this code is very generic. We could move this method and the
# action_link configuration into the ApplicationController and reuse it
# for all our models.
data = ""
cls = records[0].class
data << cls.csv_header << "\r\n"
records.each do |inst|
data << inst.to_csv << "\r\n"
end
send_data data, :type => 'text/csv', :filename => cls.name.pluralize + '.csv'
end
end
您可以通过将实际的模型知识封装到模型中来保证代码是面向对象的。
清单 19. 定制操作的对应模型方法
class User < ActiveRecord::Base
...
# The header line lists the attribute names. ID is quoted to work
# around an issue with Excel and CSV files that start with "ID".
def self.csv_header
""ID",Last Name,First Name,Email,Birthdate"
end
# Emit our attribute values as a line of CSVs
def to_csv
id.to_s << "," << last_name << "," << first_name << "," << email <<
"," << birthdate.to_s
end
end
回页首
本地化
软件要想在全球得到广泛使用,一个关键特性是它需要能够以用户的本地语言进行操作。Ruby 和 Rails 并没有提供标准的 API 来处理 locale,因此它的集成比其他方法的集成更加困难(比如 Java 集成)。ActiveScaffold 小组决定将所有的本地化工作推迟给应用程序执行,这是通过一个简单的查找钩子 Object::as_ 方法实现的,它会嵌入到您喜欢的 Ruby 本地化插件中。在本例中,代码显示了如何将方法参数传递给 Globalize 插件(请参看 参考资料 中的链接)所提供的 _ 方法(是的,这个方法的名字就是 “_”)。
清单 20. 对 ActiveScaffold 进行本地化
# Put this at the bottom of your app/controllers/application.rb file
class Object
def as_(string, *args)
# Use Globalize's _ method to provide the actual lookup of the string.
_(string, ` *args)
end
end
Globalize 现在可以为传入这个方法的所有字符串提供本地化翻译。
回页首
定制 ActiveScaffold 的样式
ActiveScaffold 提供了一组非常丰富的 CSS 样式,可以对标准的 UI 进行调整以便提供定制的外观。您可以通过创建并重写 CSS 文件并将其包含到标准的 CSS 包含文件之后,从而重写默认样式来匹配站点的颜色方案、字体等设置。在本例中,我们包含了一个名为 public/stylesheets/as_overrides.css 的文件:
清单 21. 重写默认的 ActiveScaffold 样式
<head>
<title>My Application</title>
<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>
<%= stylesheet_include_tag "as_overrides" %>
</head>
标准的 ActiveScaffold 样式表位于 vendor/plugins/active_scaffold/frontends/default/stylesheets/stylesheet.css 中。
回页首
安全性
ActiveScaffold 提供了一个身份验证 API 来确保数据安全性。第一级是对控件进行的粗粒度的安全性控制,这并不是记录特有的。在控件上,可以定义 #{action}_authorized? 方法,其中 #{action} 是一个 ActiveScaffold 操作:create、list、 search、 show、 update 或 delete。
清单 22. 基于控件的安全性
class ProjectsController < ApplicationController
active_scaffold :project do |conf|
# Needed to inject the current_user method into the model
config.security.current_user_method = :current_user
end
protected
# only authenticated admin users are authorized to create projects
def create_authorized?
user = current_user
!user.nil? && user.is_admin?
end
def current_user
@session[:user_id] ? User.find(@session[:user_id]) : nil
end
end
第二级安全性让您可以创建更加复杂的数据特有的逻辑。举例来说,由于 Projects belongs_to Organizations,因此对项目的编辑进行限制,使得只有拥有项目的组织的管理员才能执行编辑操作,这是合理的。为此,将方法添加到模型(比如 authorized_for_#{crud_action})中,其中 #{crud_action} 是 create、read、 update 或 destroy 之一。
清单 23. 基于模型的安全性
class Project < ActiveRecord::Base
belongs_torganization
# Since projects are owned by an organization, allow only administrators
# of that organization to edit the project
def authorized_for_update?
organization.is_admin? current_user
end
end
注意 current_user 方法是可用的,因为 ActiveScaffold 根据对应控件的 current_user_method 配置将其插入了模型中。
#Ruby On Rails
使用 ActiveScaffold 增强 Ruby on Rails 的功能
节省点时间,少一些头痛,使用 Ruby on Rails ActiveScaffold 插件可以创建更容易维护的页面。ActiveScaffold 可以处理用户接口所需的所有 CRUD(创建、读取、更新和删除)操作,这样可以为您节省更多时间来重点关注更有挑战(也更有趣的)问题。
为复杂应用程序编写基于 Web 的数据输入 UI 永远都不是件快乐的事,通常都是非常单调乏味的。良好用户界面的一个关键属性是一致性,但是这需要一个博学勤勉的开发团队才能设计符合这种设计标准的 Web 页面。与其他 Web 应用程序框架类似,Ruby on Rails 也有相同的问题。不过,Ruby 语言的动态特性提供了一个解决方案:ActiveScaffold。ActiveScaffold 是 Ruby on Rails (也称为 Rails)的一个插件,它可以动态地生成基于模型的视图。ActiveScaffold 不需要手工创建页面来显示模型,而是可以从内部审视 ActiveRecord 模型,并动态地生成一个 CRUD(创建、读取、更新、删除)用户界面来管理这些对象。
本文是基于 ActiveScaffold、Ruby 和 Rails 的当前(撰写本文之时)可用的最新版本来撰写的(链接和版本号请参看 参考资料)。另外,本文假设您非常熟悉 Ruby on Rails,并且正在使用 Linux® 或 Mac OS X 系统。Windows® 用户应该修改本文中给出的命令来适合自己的环境(例如,将 ‘ruby’ 添加到脚本命令最前面)。
安装 ActiveScaffold
由于 ActiveScaffold 是一个 Rails 插件,可以从一个远程 Web 或者 Subversion 服务器上安装。下面的命令将从 ActiveScaffold Subversion 服务器中获取 ActiveScaffold。
清单 1. 安装 ActiveScaffold 插件
script/plugin install http://activescaffold.googlecode.com/svn/tags/active_scaffold
注意这将获取 ActiveScaffold 的当前发行版(即最新发行版)。撰写本文时使用的是 1.0 发行版,但是也可以使用将来的发行版:ActiveScaffold 开发人员迄今为止一直很好地关注着兼容性问题。
回页首
模型
最现代的 Web 应用程序框架都基于 MVC(模型、视图、控制器)模式,Rails 也不例外。模型表示数据库中存储的数据,每个表在 Ruby 中都有一个对应的 ActiveRecord 模型类。在本文中,我们创建了一个简单的项目跟踪应用程序,其中,组织拥有很多用户和很多项目。下面的代码显示了 ActiveRecord 向应用程序和对应模型类上迁移的过程。注意模型类要比 Java 中相同的类简单很多。这是 Rails 的 DRY(不要重复自己)原则的基本例子。由于迁移早已包含了列,为什么还要在模型类中再次将它们列出来呢?
清单 2. 迁移
class AddOrganizations < ActiveRecord::Migration
def self.up
create_tablerganizations do |t|
t.column :name, :string, :limit => 50, :null => false
end
end
def self.down
drop_tablerganizations
end
end
class AddUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :first_name, :string, :limit => 50, :null => false
t.column :last_name, :string, :limit => 50, :null => false
t.column :email, :string, :limit => 100, :null => false
t.column :password_hash, :string, :limit => 64, :null => false
t.columnrganization_id, :integer, :null => false
end
add_index :users, :email, :unique => true
end
def self.down
drop_table :users
end
end
class AddProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.column :name, :string, :limit => 50, :null => false
t.columnrganization_id, :integer, :null => false
end
end
def self.down
drop_table :projects
end
end
class AddProjectsUsers < ActiveRecord::Migration
def self.up
create_table :projects_users do |t|
t.column :project_id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :role_type, :integer, :null => false
end
end
def self.down
drop_table :projects_users
end
end
清单 3. 模型
class User < ActiveRecord::Base
belongs_torganization
end
class Organization < ActiveRecord::Base
has_many :projects
has_many :users
end
class Project < ActiveRecord::Base
belongs_torganization
has_many :projects_users
has_many :administrators, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 3"
has_many :managers, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 2"
has_many :workers, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 1"
end
class ProjectsUser < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
插件和生成器的对比
Ruby on Rails 可以支持两种 “开发助手”。生成器 是静态的 — 它们只会在生成代码时运行一次。插件 是动态的 — 它们可以作为应用程序运行时的一部分运行。例如,标准的 Rails scaffold 生成器就是运行一次来基于模型中的当前字段创建一个静态 HTML 模板。如果希望添加一列,就必须重新生成视图(丢失所做的更改),或者将字段手工添加到视图中。这将给模型更改增加不必要的复杂性。
插件会在运行时生成这些视图,因此更改模型就不困难了。与生成器相比,插件并没有什么实际的劣势,但是可能会比使用生成器稍微复杂一点。
User、Organization 和 Project 表都代表域中的传统实体,而 ProjectsUsers 表则会在 Project 和 User 实体之间增加一个多对多的关系。在本例中,它会添加一个 role_type 属性,它代表用户在项目中所扮演的角色。用户可能是工人、经理和/或管理员。
在模型上创建任何用户界面所需要的关键信息都是要理解模型之间的关系。通过在模型中声明 has_many 和 belongs_to,就在它们之间定义了一种特定类型的关系。一旦 ActiveScaffold 知道了这些关系,就可以提供一个用户界面以一种用户可以理解的方式对这些对象进行操作。在这种情况下,ActiveScaffold 就可以确定某个 Project 是由某个 Organization 所有的,因此可以相应地调整用户界面。如果您更改了这种关系,则用户界面就可以相应地变化,无需开发人员更改 UI。
边注:由于 Rails 迁移框架中存在某种限制,使清单 2 中的迁移无法使用外键。为了确保数据一致性,强烈推荐使用这些外键。Redhill Consulting 提供了一个很好的 foreign_key_migrations 插件,它增加了在 Rail 数据库迁移框架中对外键的支持;有关更多信息,请参看 参考资料 中的链接。
回页首
Rails scaffold
现在我们已经充实了模型,接下来可以在上面放一个 Web 界面。Rail 提供了一个 “scaffold” 生成器,它可以为某个给定模型生成一组基本的 CRUD 页面。下面的命令用来为这个模型创建标准的 Ruby scaffold:即一个具有一组 CRUD 方法和一组对应的模型 HTML 视图的控件。
清单 4. 生成标准的 Rails scaffold
script/generate scaffold user
script/generate scaffold organization
scaffold 生成器有几个重要的限制:
* 没有关系支持:创建模型实例就意味着只能编辑实例的基本属性。如果模型需要定义一个关系(例如,Project 需要选中所有的 Organization),就需要手工修改页面将这个域添加到窗体上。
* 不能往返:对于模型反复进行的更改不支持 “往返” 操作,这是因为所生成的代码是静态的。一旦代码被修改之后,就不能在不丢失所做更改的情况下重新生成 scaffold 了。
* 缺少样式支持:所生成的页面都是最基本的黑色和白色,只有最少量的 CSS 支持。不对基本的 HTML 标记使用样式就不支持通过 CSS 使用皮肤功能。
同时具有前两个限制说明 scaffold 实际上更像一个玩具,而不像一个有用的工具。图 1 给出了 Rails 提供的默认 scaffold。
图 1. 标准的 Rails scaffold
图 1. 标准的 Rails scaffold
Rails 还包括了 dynamic scaffold,它实际上提供了相同的代码支持,而不需要提前生成控件代码。这并没有给您带来太多好处 — 因为大部分代码都位于 HTML 视图中,而且仍然需要视图代码。通过将 scaffold 方法添加到控件类中可以启用动态 scaffold。
清单 5. 添加 Rails 的标准 scaffold
class UsersController < ApplicationController
scaffold :user
end
小心!
如果标准的 Rails scaffold 代码与 ActiveScaffold 一起使用,就可能会出现问题。在切换到 ActiveScaffold 之前,请确保您已经清除了所有的 scaffold 控件和视图代码。
回页首
ActiveScaffold 默认显示
ActiveScaffold 为模型提供了一个更加有用的 UI。scaffold 的上述 3 个问题都已解决。首先,我们需要修改控件来使用 ActiveScaffold scaffold:
清单 6. 添加 ActiveScaffold scaffold
class UsersController < ApplicationController
active_scaffold :user
layout "activescaffold"
end
然后为所有 ActiveScaffold 页面添加标准布局(将下面的代码放入 app/views/layouts/activescaffold.rhtml 中):
清单 7. ActiveScaffold 标准布局
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>My Application</title>
<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>
</head>
<body>
<%= yield %>
</body>
</html>
现在用户清单看起来就好多了:
图 2. 标准的 ActiveScaffold scaffold
图 2. 标准的 ActiveScaffold scaffold
这个默认显示可以很好地用于快速构建原型或开发。然而,与所有默认情况类似,稍加定制就可以使它更加适合于您的特定需求。
回页首
定制视图
ActiveScaffold 有几个钩子函数可以让您定制如何显示模型。可以给 active_scaffold 方法传递一个用来配置 scaffold 的可选配置块。
全局配置
ActiveScaffold 的全局配置允许对所有控件进行定制:
清单 8. 全局配置
class ApplicationController < ActionController::Base
AjaxScaffold.set_defaults do |conf|
conf.list.results_per_page = 20
end
end
这个例子配置系统中所有的 ActiveScaffold scaffold 以便在显示记录时每页显示 20 个结果。
本地配置
每个控件 scaffold 都可以使用自己特有的 ActiveScaffold 配置。
清单 9. 特定控件的本地配置
class UsersController < ApplicationController
active_scaffold :user do |conf|
conf.modules.exclude :update
conf.list.label = 'People'
conf.list.sorting = [{:last_name => :ASC}, {:first_name => :ASC}]
conf.list.columns.exclude :password_hash
end
end
这个例子就不能更新模型实例、更改列表标题和定制默认用户列表排序了。sorting 让您可以控制如何从数据库中返回记录,并期望得到一个 {column => direction} 散列数组。还配置 ActiveScaffold 不显示用户不需要查看的特定列;在本例中,password_hash 列不需要在 UI 中显示,因此就将其排除了。
显示 ActiveRecord 对象
to_label 方法让您可以定制模型实例在页面中的显示方式。默认情况下,ActiveScaffold 会查找模型中的一组方法:
* to_label
* name
* label
* title
* to_s
最后一个方法是由 ActiveRecord 提供的,会显示成 “#:<Address:0xFFFFFF:>” 的形式,这对于用户来说不够友好。下面是一个更好的方法:
清单 10. 定制模型的显示
class User < ActiveRecord::Base
belongs_torganization
def to_label
first_name << ' ' << last_name
end
end
例如,用户的 to_label 可能是 John Doe。
定制属性显示
ActiveScaffold 允许开发人员完全控制模型属性的实际显示方式。默认情况下,ActiveScaffold 只会对一些简单的属性值调用 to_s,从而确定它们到 HTML 的顺序。要对此进行定制,只需要在 app/helpers/<model>_helper.rb 中将一个列显示帮助方法添加到相应的帮助类中即可。
清单 11. 定制属性显示
def birthdate_column(record)
record.birthdate.strftime("%d %B %Y")
end
在上面的帮助方法中,您拥有记录的全部访问权限。在本例中,这个帮助并不是很智能,因为它并不能说明用户所请求的现场,确定日期格式就需要用到该现场。
对于 has_many 和 has_and_belongs_to_many 关联来说,ActiveScaffold 会通过使用上面提到的 to_label 逻辑来渲染它们,从而显示前 3 个条目。这 3 个条目会链接在一起,这样在点击时,整个关联就可以显示出来了。这可以防止用户界面被大型关联集所覆盖的情况。
窗体显示
ActiveScaffold 也可以基于 Rails 的 ActiveRecord 和 ActiveView 库为模型创建一个窗体。varchar 列会变成文本输入,boolean 型变量会映射成 HTML 的复选框等等。
有一点需要注意:虚拟属性(在模型中作为属性定义,但却不真正保存在数据库中的属性)的 HTML 渲染方式可能与普通的模型属性不同。任何名字中包含 “password” 的普通模型属性在 HTML 都会渲染为一个密码输入。不过对于虚拟属性来说却并非如此,在使用虚拟属性作为密码窗体输入时,很容易发现这一点。在这种情况下,我们将使用虚拟属性来捕获窗体输入,并在保存时将这些值映射到 password_hash 列中,这样用户的纯文本输入就可以作为一个 SHA256 散列安全地保存到数据库中。
清单 12. 在用户模型中创建虚拟属性
require 'digest/sha2'
class User < ActiveRecord::Base
attr_accessor :password, :password_confirmation
validates_presence_of :password, :password_confirmation
def validate
errors.add('password', 'and confirmation do not match') \
unless password_confirmation == password
end
def before_save
self.password_hash = Digest::SHA256.hexdigest(password) if password
end
end
我们添加了两个 form_column 帮助方法将它们作为密码输入正确地进行渲染。ActiveScaffold 期望使用 field_name 参数中给定的名称对输入进行 POST 处理。
清单 13. 定制虚拟属性的窗体显示
def password_form_column(record, field_name)
password_field_tag field_name, record.password
end
def password_confirmation_form_column(record, field_name)
password_field_tag field_name, record.password_confirmation
end
回页首
关系
到现在为止,我们只考虑了基本的模型操作,例如显示或编辑简单的列值。ActiveScaffold 中最复杂的部分是确定模型之间的关系以及它们如何影响应用程序的用户界面。要正确实现操作就几乎无法避开这一部分;本节将介绍如何配置 ActiveScaffold 来正确使用模型。
列表显示
为了在模型之间进行导航,ActiveScaffold 会在一个列表视图中显示关系链接。举例来说,在查看一个组织列表时,会看到一个 Users 链接来显示一个页面,其中每个 Users 都对应一个给定的 Organization。要定制此链接,需要为该列定义一个帮助方法:
清单 14. 定制关联的显示
def users_column(record)
name = "user"
name = "users" if record.users.size > 1
"<a href="/user/list?user_id=#{record.id}">#{record.users.size} #{name}</a>"
end
窗体显示
ActiveScaffold 还提供了基于所定义的关系在模型之间进行导航的功能。以 belongs_to 关系为例。在上面的例子中,User belongs_to 一个组织。这就意味着一个 User 在创建时必须具有一个相关的 Organization(如果 Organization 是可选的,那么您就应该使用一个可以为空值的 has_one 关系)。 ActiveScaffold 可以理解这种关系,并可以使用 “select” 语句从数据库中显示一个 Organizations 列表,这样用户可以选出与正在创建的 User 关联在一起的 Organization。
这对于只有 10 到 20 个 Organization 的小型数据集而言可以很好地工作,但是却不能扩展到具有大量的 Organization 的情况。您可以使用窗体列渲染程序来重写对列的渲染。下面给出了一个简单的例子,其中,您可以在开发时获悉可能的值:
清单 15. 使用静态选择列表
def organization_form_column(record, field_name)
# simple example that just hard codes two possible values
select_tag field_name, options_for_select('IBM' => '1', 'Lenovo' => '2')
end
回页首
搜索记录
ActiveScaffold 提供了一些有用的搜索功能来查找大型表中的记录。默认情况下,scaffold 在上面的目录表中有一个 “Search” 链接,使用该链接可以打开一个文本框,用户可以在这个文本框中输入搜索条件。ActiveScaffold 会创建一条 SQL 语句为模型搜索所有的 varchar 列,这样输入诸如 “ham” 之类的条件就可以找到基于姓氏的用户记录了。与其他地方类似,这里也有几个配置选项。
实时搜索
当用户按下 Return 时,就会执行默认搜索。ActiveScaffold 可以通过启用 “实时搜索” 选项来进行实时搜索。这会基于用户当前输入每秒生成一个 Ajax 调用。记住,实时搜索可能是数据库密集型的。正如下面解释的一样,在使用这个特性之前,要确保您已经配置了要搜索的列,并且已经正确地创建了表索引。
清单 16. 在实时搜索和默认搜索之间进行切换
ActiveScaffold.set_defaults do |conf|
conf.actions.exclude :search
conf.actions.add :live_search
end
调节可用性
清单 17. 调节 scaffold 的搜索配置
active_scaffold :user do |conf|
conf.live_search.columns = [:last_name, :first_name]
conf.live_search.full_text_search = false
end
这段代码告诉 ActiveScaffold 要限制对这个 scaffold 的搜索 —— 只允许使用用户的姓和名进行搜索,禁用全文搜索。后一个选项是用于大型表的伸缩性调节选项。如果用户搜索 “ham”,默认情况下,ActiveScaffold 会生成一条 SQL 语句,其中包含以下内容:lower(column_name) LIKE "%ham%",无法为它编制索引。通过禁用全文搜索,告诉它使用 “以...开始” 的语义:lower(column_name) LIKE "ham%"。这固然限制了搜索的灵活性,但得到了更好的伸缩性。
回页首
定制操作
除了标准的 CRUD 操作之外,ActiveScaffold 还可以让您定义自己的控件操作。数据库应用程序经常需要将数据导出为 PDF、Excel、CSV 或 XML。添加此功能非常容易,首先我们可以为具有对应操作方法的控件增加一个 “操作链接”:
清单 18. 定义定制操作
class UsersController < ApplicationController
active_scaffold :user do |conf|
conf.action_links.add 'export_csv', :label => 'Export to Excel', :page => true
end
def export_csv
# find_page is how the List module gets its data. see Actions::List#do_list.
records = find_page().items
return if records.size == 0
# Note this code is very generic. We could move this method and the
# action_link configuration into the ApplicationController and reuse it
# for all our models.
data = ""
cls = records[0].class
data << cls.csv_header << "\r\n"
records.each do |inst|
data << inst.to_csv << "\r\n"
end
send_data data, :type => 'text/csv', :filename => cls.name.pluralize + '.csv'
end
end
您可以通过将实际的模型知识封装到模型中来保证代码是面向对象的。
清单 19. 定制操作的对应模型方法
class User < ActiveRecord::Base
...
# The header line lists the attribute names. ID is quoted to work
# around an issue with Excel and CSV files that start with "ID".
def self.csv_header
""ID",Last Name,First Name,Email,Birthdate"
end
# Emit our attribute values as a line of CSVs
def to_csv
id.to_s << "," << last_name << "," << first_name << "," << email <<
"," << birthdate.to_s
end
end
回页首
本地化
软件要想在全球得到广泛使用,一个关键特性是它需要能够以用户的本地语言进行操作。Ruby 和 Rails 并没有提供标准的 API 来处理 locale,因此它的集成比其他方法的集成更加困难(比如 Java 集成)。ActiveScaffold 小组决定将所有的本地化工作推迟给应用程序执行,这是通过一个简单的查找钩子 Object::as_ 方法实现的,它会嵌入到您喜欢的 Ruby 本地化插件中。在本例中,代码显示了如何将方法参数传递给 Globalize 插件(请参看 参考资料 中的链接)所提供的 _ 方法(是的,这个方法的名字就是 “_”)。
清单 20. 对 ActiveScaffold 进行本地化
# Put this at the bottom of your app/controllers/application.rb file
class Object
def as_(string, *args)
# Use Globalize's _ method to provide the actual lookup of the string.
_(string, ` *args)
end
end
Globalize 现在可以为传入这个方法的所有字符串提供本地化翻译。
回页首
定制 ActiveScaffold 的样式
ActiveScaffold 提供了一组非常丰富的 CSS 样式,可以对标准的 UI 进行调整以便提供定制的外观。您可以通过创建并重写 CSS 文件并将其包含到标准的 CSS 包含文件之后,从而重写默认样式来匹配站点的颜色方案、字体等设置。在本例中,我们包含了一个名为 public/stylesheets/as_overrides.css 的文件:
清单 21. 重写默认的 ActiveScaffold 样式
<head>
<title>My Application</title>
<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>
<%= stylesheet_include_tag "as_overrides" %>
</head>
标准的 ActiveScaffold 样式表位于 vendor/plugins/active_scaffold/frontends/default/stylesheets/stylesheet.css 中。
回页首
安全性
ActiveScaffold 提供了一个身份验证 API 来确保数据安全性。第一级是对控件进行的粗粒度的安全性控制,这并不是记录特有的。在控件上,可以定义 #{action}_authorized? 方法,其中 #{action} 是一个 ActiveScaffold 操作:create、list、 search、 show、 update 或 delete。
清单 22. 基于控件的安全性
class ProjectsController < ApplicationController
active_scaffold :project do |conf|
# Needed to inject the current_user method into the model
config.security.current_user_method = :current_user
end
protected
# only authenticated admin users are authorized to create projects
def create_authorized?
user = current_user
!user.nil? && user.is_admin?
end
def current_user
@session[:user_id] ? User.find(@session[:user_id]) : nil
end
end
第二级安全性让您可以创建更加复杂的数据特有的逻辑。举例来说,由于 Projects belongs_to Organizations,因此对项目的编辑进行限制,使得只有拥有项目的组织的管理员才能执行编辑操作,这是合理的。为此,将方法添加到模型(比如 authorized_for_#{crud_action})中,其中 #{crud_action} 是 create、read、 update 或 destroy 之一。
清单 23. 基于模型的安全性
class Project < ActiveRecord::Base
belongs_torganization
# Since projects are owned by an organization, allow only administrators
# of that organization to edit the project
def authorized_for_update?
organization.is_admin? current_user
end
end
注意 current_user 方法是可用的,因为 ActiveScaffold 根据对应控件的 current_user_method 配置将其插入了模型中。
#Ruby On Rails
相关推荐
在过去的几年中,《Ruby on Rails Tutorial》这本书被视为介绍使用 Rails 进行 Web 开发的先驱者。 在这个全球互联的世界中,计算机编程和 Web 应用程序开发都在迅猛发展,我很期待能为中国的开发者提供 Ruby on ...
引用自Nathan Torkington的话:“使用Ruby on Rails就像观看功夫电影一样,看似弱小的新手框架却能够用各种创造性的方式打败众多强大的对手。”这句话生动地描述了Ruby on Rails的独特之处以及它在Web开发领域的影响...
使用Ruby on Rails最新版进行开发,开发者不仅可以享受到高效的开发流程,还能通过强大的社区支持和丰富的第三方库(如Gem)扩展其功能。例如,Devise用于用户认证,CanCanCan用于授权管理,Paperclip或Carrierwave...
Ruby on Rails是一款基于Ruby语言的开源Web开发框架,它遵循MVC(模型-视图-控制器)架构模式,简化了Web应用的开发流程。在Linux环境下安装Ruby on Rails需要一系列的依赖包和步骤,本资源包提供了所需的所有组件,...
《Ruby on Rails 3 Tutorial》是一本专门为初学者设计的指南,旨在帮助读者快速掌握Ruby on Rails这一强大的Web开发框架。Ruby on Rails(简称Rails)是基于Ruby语言的一个开源框架,它采用MVC(Model-View-...
Ruby on Rails,简称Rails,是一款基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web应用程序的开发。Rails由David Heinemeier Hansson于2004年创建,它提倡“约定优于配置...
- **重要概念**:本指南旨在帮助读者深入理解Ruby on Rails(以下简称Rails)4.2.5版本的核心功能与最佳实践。 - **基础假设**:读者已经具备一定的Ruby编程基础,并对Web开发有一定的了解。 #### 二、什么是Rails...
本书教您如何使用Ruby on Rails开发和部署真正的,具有工业实力的Web应用程序,Ruby on Rails是为诸如Twitter,Hulu,GitHub和Yellow Pages等顶级网站提供支持的开源Web框架。
《Ruby on Rails for Dummies》这本书将引导读者从安装Ruby和Rails环境开始,逐步学习如何创建模型、视图和控制器(MVC架构),搭建数据库,使用路由系统,以及实现CRUD(Create, Read, Update, Delete)操作。...
总的来说,Ruby on Rails实践涉及的知识点包括但不限于:Ruby语言基础、Rails框架结构、MVC模式、ActiveRecord、路由、测试驱动开发、插件和gem使用、以及部署策略。通过学习和实践,开发者能够快速构建功能丰富的...
Ruby on Rails,简称Rails,是基于Ruby语言的一个开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在使Web开发过程更加高效、简洁。本压缩包中的"Ruby on Rails入门经典代码"提供了新手学习...
通过阅读《Ruby on Rails中文指南》,你将逐步熟悉这些概念和技术,最终能够熟练地使用Rails框架构建功能丰富的Web应用。无论你是初学者还是经验丰富的开发者,这份指南都将为你提供宝贵的指导。
《Ruby on Rails入门权威经典》是一本专门为初学者设计的指南,旨在帮助读者全面掌握Ruby on Rails这一强大的Web开发框架。Ruby on Rails(简称Rails)是基于Ruby编程语言的开源框架,以其“DRY(Don't Repeat ...
【使用 Ruby on Rails 和 Eclipse 开发 iPhone 应用程序教程】是一个面向中级开发者的系列教程,旨在教读者如何利用 Ruby on Rails 框架在服务器端为 Mobile Safari 设计和提供自定义内容,以适应 iPhone 和 iPod ...
Ruby on Rails 4 Tutorial 是一本深受开发者欢迎的书籍,它详细介绍了如何使用Ruby on Rails这一强大的Web开发框架。Ruby on Rails(简称Rails)是基于Ruby语言的开源框架,以其“约定优于配置”(Convention over ...
Ruby on Rails,简称Rails,是基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-Controller)架构模式,旨在使开发过程更加简洁高效。这个“ruby on rails 教程源码”很可能是为了辅助学习者深入理解Rails的...
Ruby on Rails(简称Rails)是一种基于Ruby语言的开源Web应用程序框架,它遵循MVC(Model-View-Controller)架构模式,旨在简化Web开发过程并提高效率。在这个“ruby on rails在线考试系统”中,我们可以探讨以下几...
Ruby On Rails 框架自它提出之日起就受到广泛关注,在“不要重复自己”,“约定优于配置”等思想的指导下,Rails 带给 Web 开发者的是极高的开发效率。 ActiveRecord 的灵活让你再也不用配置繁琐的 Hibernate 即可...