Rails 进阶:理解模型(Model)、视图(View)和控制器(Controller)

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

以下是我理解的大致情况:

rails 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, 请在下方给我留言。

本系列其他文章

  1. Ruby on Rails 入门:我希望当初就知道的事
  2. Rails 进阶:理解模型(Model)、视图(View)和控制器(Controller)
  3. Javascript 简明而全面概述
  4. 使用 JSON 交换数据
  5. 如何为你的 Web 应用制作一个书签工具(Bookmarklet)

加入 45 万月度读者

喜欢这篇文章?还有更多内容能帮你建立持久、直观的数学理解。加入通讯以获取额外内容和最新更新。