我很高兴人们喜欢 Rails 简介;现在你们这些捣蛋鬼可以避免我在模型-视图-控制器(MVC)模式中遇到的头痛问题了。这并不完全是一个 简介 到 MVC, 它是一个在你艰难前行时需要注意的陷阱列表 MVC 前几次。
以下是我理解的大致情况:

这个 浏览器 发出一个请求,例如 http://mysite.com/video/show/15
这个 Web 服务器 (mongrel, WEBrick, 等) 接收到请求。它使用 路由 来确定使用哪个控制器:默认路由模式是“/controller/action/id”,如
config/routes.rb中定义。在我们的例子中,是“video”控制器,“show”方法,id参数为“15”。然后Web服务器使用 调度器 来创建一个新控制器,调用该动作并传递参数。控制器 负责解析用户请求、数据提交、cookies、会话和“浏览器相关的东西”。它们就像那个指手画脚的经理,指挥着员工干活。 最好的控制器是 Dilbert 式的: 它下达命令,但不知道(也不关心)如何完成。在我们的例子中,video控制器中的show方法知道需要查找一个视频。它请求模型获取视频15,并最终将其显示给用户。
模型 是Ruby类。它们与数据库通信,存储和验证数据,执行业务逻辑,并承担繁重的工作。 他们是后屋里的胖家伙 进行数字运算。在这个例子中,模型从数据库中检索视频15。
视图 是用户所看到的: HTML, CSS, XML, Javascript, JSON. 它们是销售代表,按照经理的指示张贴传单和收集调查问卷。 视图只是傀儡 读取控制器给它们的内容。它们不知道后台发生了什么。在我们的例子中,控制器将视频15传递给“show”视图。show视图生成 HTML:div、表格、文本、描述、页脚等。
控制器将响应体(HTML、 XML, 等)和元数据(缓存头、重定向)返回给服务器。服务器将原始数据组合成适当的 HTTP 响应并发送给用户。
想象一个“胖模型、瘦控制器”的故事,而不是枯燥的“三层架构”,会更有趣。模型做苦力活,视图是笑脸,控制器是幕后主脑。
许多 MVC 讨论都忽略了Web服务器的作用。然而,提及它很重要 如何 控制器神奇地创建并接收用户信息。网络服务器是隐形网关,来回传送数据:用户从不直接与控制器交互。
超级模型
在Rails世界中,模型是胖的:它们承担繁重的工作,使控制器保持精简、专注且无需了解细节。以下是一些模型技巧:
使用ActiveRecord
class User < ActiveRecord::Base
end
代码 < ActiveRecord::Base 意味着你的低层User模型继承自ActiveRecord::Base类,并获取Rails的魔法来进行数据库查询和保存。
Ruby也能轻松处理“未定义”方法。ActiveRecord允许像“find_by_login”这样的方法,它们实际上并不存在。当你调用“find_by_login”时,Rails处理“未定义方法”调用并搜索“login”字段。假设该字段在你的数据库中,模型将基于“login”字段进行查询。不需要任何配置胶水。
定义类和实例方法
def self.foo
"Class method" # User.foo
end
def bar
"instance method" # user.bar
end
类和实例方法可能会引起混淆。
user (小写u)是一个对象,你调用实例方法如 user.save.
User (大写U)是一个类方法——你不需要对象来调用它(如 User.find)。ActiveRecord为你的模型添加了实例方法和类方法。
作为提示,定义类方法如 User.find_latest 而不是显式地将搜索条件传递给 User.find (瘦控制器更好)。
使用属性
常规Ruby对象可以像这样定义属性:
# attribute in regular Ruby
attr_accessor :name # like @name
def name=(val) # custom setter method
@name = val.capitalize # clean it up before saving
end
def name # custom getter
"Dearest " + @name # make it nice
end
关键在于:
- attr_accessor :name 在模型中创建 get 和 set 方法(name= 和 name)。这就像拥有一个公共实例变量
@name. - 定义方法
name=(val)来更改 @name 的保存方式(例如验证输入)。 - 定义方法
name来控制变量的输出方式(例如更改格式)。
在Rails中,属性可以是 令人困惑的 由于数据库魔法。关键在于:
- ActiveRecord 获取数据库字段并将其放入一个
attributes数组。它会创建默认的 getter 和 setter,但你需要调用user.save来保存它们。 如果你想 覆盖 默认的getter和setter,请使用:
# ActiveRecord: override how we access field def length=(minutes) self[:length] = minutes * 60 end def length self[:length] / 60 end
ActiveRecord定义了一个“”方法来访问原始属性(封装了write_attribute和read_attribute)。这就是你如何更改原始数据。你不能使用[]”
def length # this is bad
length / 60
end
因为这是一个无限循环(而且不好玩)。所以 self[] 是的。这是我特别头疼的一个Rails问题——如有疑问,使用 self[:field].
永远不要忘记你在使用数据库
Rails很干净。干净到你忘了你在用数据库。别忘了。
保存你的模型。 如果你做了修改,保存它。很容易忘记这个关键步骤。你也可以使用update_attributes(params)并传递一个键值对的哈希。
更改后重新加载你的模型。假设一个用户 has_many videos。你创建一个新视频,指向正确的用户,然后调用user.videos来获取列表。它能工作吗?
可能不行。如果你已经查询过videos,user.videos可能有过时的数据。你需要调用 user.reload 来获取新的查询。小心——内存中的模型就像一个缓存,可能会过时。
创建新模型
有两种创建新对象的方法:
joe = User.new( :name => "Sad Joe" ) # not saved
bob = User.create ( :name => "Happy Bob" ) # saved
User.new创建一个新对象,使用哈希设置属性。new不 保存到数据库:你必须调用user.save显式地。方法save如果模型无效,可能会失败。User.create创建一个新模型并 将其保存到数据库。验证可能会失败;user.errors是一个包含错误字段和详细消息的哈希。
注意哈希是如何传递的。借助Ruby的花括号魔法, {} 并不是明确需要的,所以
user = User.new( :name => "kalid", :site => "instacalc.com" )
变为
User.new( {:name => "kalid", :site => "instacalc.com"} )
箭头(=>)表示正在传递一个哈希。
使用关联
快速小测验,高手:假设用户有一个“状态”:active、inactive、pensive等。正确的关联是什么?
class User < ActiveRecord::Base
belongs_to :status # this?
has_one :status # or this?
end
嗯。很可能,你想要 belongs_to :status。是的,听起来很奇怪。不要考虑“has_one”和“belongs_to”这些短语,考虑其含义:
- belongs_to: links_to 另一个表。每个用户引用(关联)一个状态(status)。
- has_one: linked_from 另一个表。一个状态(status)被一个用户链接。实际上,状态不 甚至不知道 关于用户——在statuses表中根本没有提及“user”。在类Status内部,我们会写
has_many :users(has_one和has_many是同一回事——has_one只返回一个对象,该对象 links_to 这个)。
一个助记方法:
- “belongs_to”与“links_to”押韵
- “has_one”与“linked_from”押韵
嗯,它们有点押韵。配合一下,我在尽力帮忙。
这些关联实际上定义了用于查找其他类对象的方法。例如,“user belongs_to status”意味着 user.status 查询Status以获取正确的status_id。另外,“status has_many :users”意味着 status.users 查询用户表中所有具有当前状态的记录 status_id。一旦我们声明了关联,ActiveRecord 就会自动处理这些魔法。
使用自定义关联
假设我需要 二 状态,主要和次要?使用这个:
belongs_to :primary_status, :model => 'Status', :foreign_key => 'primary_status_id'
belongs_to :secondary_status, :model => 'Status', :foreign_key => 'secondary_status_id'
你定义一个新字段,并显式引用用于查找的模型和外键。例如,user.primary_status 返回一个 Status 对象,其 id 为 “primary_status_id”。非常好。
快速控制器
这一节很短,因为控制器除了指挥模型和视图之外不应做太多事情。它们通常:
- 处理会话、登录/授权、过滤器、重定向和错误等事务。
- 具有默认方法(由ActionController添加)。访问
http://localhost:3000/user/show将尝试调用“show”动作(如果存在),或者如果动作未定义,则自动渲染show.rhtml。 - 将实例变量如@user传递给视图。局部变量(不带@的变量)不会被传递。
- 难以调试。使用
render :text => "Error found" and return进行 printf 风格的调试。这是将代码放在模型中的另一个好理由,因为模型可以从控制台轻松调试。 - 使用会话在请求之间存储数据:session[:variable] = “data”。
我再重复一遍,因为我以前吃过亏: 使用 @foo(而非“foo”)将数据传递到视图.
使用视图
视图很直接。基础知识:
- 控制器动作使用同名视图(方法
show加载show.rhtml默认情况下) - 控制器实例变量(@foo)在所有视图和部分视图中可用(哇!)
在视图中使用以下方式运行代码 ERB:
<% ... %>:运行代码,但不输出任何内容。用于 if/then/else/end 和数组遍历循环。你可以注释掉部分 HTML 使用 <% if false %> Hi there <% end %>。你会得到一个空行,因为你在结束 %> 之后可能有一个换行符。<%- ... %>:运行代码,但不打印尾随换行符。在生成时使用这个 XML 或者 JSON 当为了可读性拆分 .rhtml 代码块,但又不希望在输出中出现换行时。<%= ... %>:运行代码并打印返回值,例如:<%= @foo %>(你记得在传递给视图的控制器变量前加 @ 符号,对吧?)。不要把if语句放在<%=,否则会报错。<%= h ... %>:打印代码并对输出进行 HTML 转义:>变为>。h() 实际上是一个 Ruby 函数,但通常不带括号调用,正如 Ruby 开发者习惯的那样。
刚开始时可能会有点困惑——在一个虚拟视图页面上做一些实验。
休息一下
这个 MVC 模式一次消化起来有很多内容。当你熟悉它后,任何 Rails 程序都变得易于剖析:各个部分如何组合在一起就很清楚了。 MVC 使代码保持整洁且模块化,非常便于调试和维护。
在未来的文章中,我将讨论其内部细节, MVC 以及Rails表单的工作原理(这也是让我头疼的一个问题)。如果你想快速了解这些细节,可以浏览 Rails 源码 并尝试追踪一个请求的路径:
- WEBrick server 修改以调用 Rails 路由库和调度器。
- Rails Dispatcher 实际创建控制器并传递数据给它。
- ActionController Base 定义了许多函数,包括调用控制器动作(使用适当的默认值)、渲染文本和返回响应的函数。
但别急,朋友们——我会按照我的理解来解释。如果你在接触……时有过恍然大悟的时刻, MVC, 请在下方给我留言。