Rails [分享] 学习 Rails 的过程中遇到的一些陷阱, 新人一定进来看看!

zw963 · 2012年07月23日 · 最后由 ash_chuangwang 回复于 2013年10月19日 · 11325 次阅读

经过断断续续几个月的学习, Rails 终于毕业啦, 非常开心!! 当然, 我说的毕业, 也就是一般大学生毕业的水平, 大概怎么样, 你懂的. 但是至少, 自己目前已经进入一个非常好的循环, 简单点说: 从一个仅仅懂 Ruby 的程序员, 成长为一个懂 Ruby 的初级 Rails 程序员.

本贴也算是本人对于咱社区 Rails 版块, 有关技术分享的第一帖!! 同时, 这又是一个对自己学习 Rails 的又一个阶段性总结.

P.S. 刚开始学 Rails 那会儿有写过一篇 使用Rails的阶段性总结 (链接: http://ruby-china.org/topics/2581)


正文开始

来 Ruby-china 都快半年啦! 这是这么久, 我都一直没有机会在 Rails 版块分享自己的经验. 主要因为自己对于 Rails 一直处在小白的阶段. 直到最近, 通过练手, 尝试独立完成了几个很小的项目, 才感觉到本人的 Rails 水平终于算是毕业啦!! 因为我本人经历了从一个完全不懂web开发的初学者对于Rails的很多实现方式有了自己的理解的过程, 再加上自己反复的 学习归纳, 跳进一个坑, 然后爬出来, 再重新学习归纳 的实践, 所以也总结出来一些使用 Rails 的心得体会. 也正因为如此, 我觉得对于刚刚接触 Rails 的新人, 一定要仔细看本贴, 因为这绝对会让你少走很多的弯路!!

也欢迎社区的各位兄弟们踊跃拍砖, 有理解不对的对方, 一定要不吝批评指正!!!


我打算经过以下几个部分来总结我的学习经验:

  • Rails 和 Ruby 的关系到底有多大?
  • 学习 Rails 需要的基础知识优先级
  • 初学 Rails, 面对的几大陷阱
  • 使用 Rails 的一些惯例

Rails 和 Ruby 的关系到底有多大?

以目前本人的经验来说, Rails 和 Ruby 的关系, 远没有想象中的那么大. 我可以毫不夸张地说: 即使没有太多的Ruby经验, 你完全可以成为一个很好的Rails程序员, 在这点上我深有体会. 不瞒大家说, 在这段时间的 Rails 学习中, _我感觉我的 Ruby 水平不但没有进步, 反而绝对是退步了, 因为作为一个初级Rails的程序员, 实在是用不到太多的 Ruby 知识.

我上面这样讲, 可能会对新人有个误导, 我不学 Ruby 就可以开始 Rails, 其实还是要学的. 但是初期完全没必要太深入. 我顺便总结下成为一个很好的Rails程序员可能会面对的Ruby知识. 除非你要对 Rails 框架进行大的改动, 或写一些 gem, 否则下面这些绝对够用了.

  • 会用 if, unless, 以及迭代器 (循环很少被使用). 我 Rails 经验尚少, 不过实际编码中, 简单的逻辑判断在编码中占大多数.

  • 了解 Ruby 中的一些特殊的命名惯例, 哈希 (hash) 的定义, 符号 (symbol) 的含义,

  • Ruby 中方法的定义, 类的定义, 方法的可见性含义. 其中要特别注意的是: Rails 中实例变量的特殊意义(稍后会讲)以及 Ruby 中self的含义. 说实在, 如果能对于 Ruby 中的 self 理解透彻, 那你 Ruby 就很靠谱了, 但是如果你仅凭 rails 来开发 web 应用, 就希望了解 Ruby 中的 self, 貌似不可能呀!!

  • 模块混入, 类的继承关系含义, 如果不懂这个, 遇到一个 gem, 它是如何加入到你 Rails 环境中, 你可能会很困扰.

  • block 的使用以及定义. 貌似大多数情况下, 都仅仅是在使用 block, 只有在定义 view 中的 helper 方法或 Model 中的 scope 时, 才会用到 block.

  • 会使用基本的正则表达式.

暂时先想起了这些, 其他的等我想到了再补充吧.


学习 Rails 需要的基础知识优先级:

首先, 我还是想老生常谈似的, 把之前的老话重提一下: Rails 不是给新人用的玩意儿.

如果你打算在 Rails 开发方面有所建树的话, 这是我所建议的基础知识的优先级:

  • 基本的HTML以及CSS一定要会看, 而且会写!!, 这一步不过关, 你甚至都无法在编码的初期展现一个 outline 出来, 下一步很难正确的进行下去, 在这点上我曾经有有过血淋淋的教训, 切记切记.

  • 要懂 SQL. 我指的不仅仅是 ORM, 包括完整的 SQL 查询. 其实 SQL 没有想象的那么难. 而且 Rails3 针对所有的 active 的查询, 都会在终端下输出对应的 SQL, 这绝对是一个学习 SQL 的好机会, 相信我, 仅凭 ORM, 而不了解 SQL, 你其实不可能总是理清表之间的关系. 这点我也是深有体会呀, 不过是正面的. :-)

  • 真正的理解 Rails 的 RESTful 路由机制, 以及命名路由, 稍后我会详细讨论.

  • 懂得基本的 Ruby 语法, 不要觉得意外, 这绝对是最低优先级


初学 Rails, 面对的几大陷阱


RESTful, 最大的一个坑!!


最坑爹, 最大的陷阱, 就是对于 RESTful 方式的路由的理解. 好大一个坑, 反反复复掉进去多次, 最后才终于明白其中猫腻. 说这个坑大, 虽然讲 RESTful 的帖子不少, 但是没有一个帖子说清楚, 到底这个 RESTful 所代表的那个固定不变的约定到底是啥? 常常刚开始觉得这个是, 后来发现这个是可以改变的, 后来又觉得那个是, 又发现那个还是错的, 当初那个头大...

其实 Rails 中有关路由的根本性约定, 在我看来只有两条, 而所有其他看似约定的东西, 其实根本不是约定, 充其量, 最多只不过是一个惯例罢了. 下面就来说说这两条:

  • 具体服务器端执行什么样的 action, 只与 URL 地址 + HTTP 动词有关. 在编写 Rails 代码时, 我们通常不关注真实的 URL, 仅仅关注对应的命名路由 (即: ???_path, ???_url 之类的东西.)

  • 所谓 RESTful, 其实是指针对数据库的某个操作(CRUD)命名路由 + 动词的唯一约定. 这就是 RESTful 中那个唯一的约定_.

我们假设具体到一个对象 product, 则:

  • 当你新建一个对象 product 时, rails 约定, 此时使用POST动词调用products命名路由.(注意, 有个 s, 是复数)
  • 当你编辑一个现有对象 product 时, rails 约定, 此时使用PUT动词调用product命名路由. (注意, 这里和下面是单数)
  • 当你删除一个对象 product 时, rails 约定, 此时使用DELETE动词调用product命名路由. 针对嵌套形式的 RESTful, 只不过是在父对象之上, 针对子对象添加 RESTFul 形式路由而已. 一样满足上面的三条约定, 请自己去体会.

这就是 RESTful 所有的猫腻... 命名路由作为一个中间媒介, 使应用程序产生了很大的弹性, 我不妨再解释一下这个路由的流程:

  • 客户端提交具体的一个网址 + HTTP动词, Rails Server 则查找路由表, 来确定需要执行的 action. 这是自下向上的流程, 这一步很好理解了.
  • 而在服务器的响应文件 (一般为 erb 文件) 中, 如果页面存在模型表单, 则会首先根据数据状态来确定生成的RESTful形式的命名路由 + HTTP动词是什么, 否则应该通过自己来指定命名路由 + HTTP动词. 而这些命名路由, 会根据由表, 动态的转化为一个具体的 URL, 并最终通过 erb 引擎, 产生最后响应给浏览器的 HTML 代码.

在这里, 我给大家几条建议:

  1. 在编写 Rails 代码时, 你只需要专注于`命名路由 + http 动词', 以及对应的 action 是什么, 你永远不应该考虑 URL 地址的形式, 因为 URL 地址是 Rails 的路由表该考虑的事情

  2. 在使用 RESTful 来操作数据库时 (例如使用 form_for 表单操作数据库), 你应该总是专注于 对象当前的被操作状态', 然后根据之前的约定, 找出对应的命名路由 + http 动词' 对应的 action

  3. 如果你希望更改默认的 RESTful 形式的路由约定 (例如对于 form_for 表单), 你可以通过指定哈希参数 `:url => ???_path' 的形式来指定一个新的 path

最后, 还有一个好大的坑, 等着你来跳 (至少我是跳过的)

就是具体调用 view 下的那个模板, 跟真实的 URL 没有任何关系.

不知道我讲清楚没有, 我觉得 Rails 初学者 (至少以我个人的经验来说), 最坑爹的就是这个 RESTful 啦! 不过这也是 Rails 的魅力所在! 有什么不对的地方欢迎指正!!


控制器中的实例变量

学过 Ruby 的人都知道, Ruby 里面, 类的实例变量的用途为: _就是将一个本地变量的 生命周期 延长到整个类对象, 并且方便在各个实例方法中分享数据, 我刚接触 Rails 控制器的实例变量也是这样想的, 后来才发现, Rails 控制器中的@实例变量完全不是干这个的. 如果你是个初学者, 对于控制器中那么多 action 中定义的那么多实例变量感到困惑, 我想用一个抽象点的说法来解释:

  • 控制器中的实例变量是横向传递的, 即总是从控制器传递到视图中
  • 控制器中的每一个实例方法彼此之间没有任何关联, 它们分别只是一个 action 而已, 这通常意味着一个 http 应答. 响应之后, 之前的所有实例变量就会消失的无影无踪了. * * * * *

url_for 的简写方式

有关url_for的简写方式 (例如: link_to, button_to, respond_to 等), 这个其实没什么好说的, 用 Rails 久了, 自然明白. 不过初期的确给像我这样的新人很多困扰, 其实就两条:

  • 如果路由表对应的 URL 地址之中, 存在URL位置参数(就是 product/:id 当中的:id 这个 symbol), 而 path 中只有这些位置参数 (没有传递其他额外的参数), 此时可以不写哈希键, 直接写命名参数的 value 即可.
  • 另一条, 地球人都知道, 我怕解释不清, 直接引用http://api.rubyonrails.org/官方给出的解释好了.

Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will trigger the named route for that record, The lookup will happen on the name of the class. So passing a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as admin_workshop_path you’ll have to call that explicitly (it’s impossible for url_for to guess that route).


坑爹的 render

其实 Render 没啥. 就是简写方式太坑爹. (如果这也算是约定优于配置的话, 在 render 上绝对发挥到极致了)

首先要讲的是: render 有两个, 一个是 view 中用, 另一个是在 Controller 中用. (不知道 Model 中有没有??)

  • 在控制器中, 默认是渲染 template, 例如: 在控制器 aaa 中, Render 'bbb', 等价于 render :template => 'aaa/bbb'.
  • 在视图中默认总是渲染 partial, 例如: 在 aaa 控制器的视图中,<%= render 'bbb' %>, 等价于 <%= render :partial => 'aaa/bbb' %>
  • 在 view 中还可以直接 <%= render @object %>, 等价于: <%= render :partial => objects/object", :locals => { :object => @object} %> 至于我后面给出的等价代码你要时不明白, 上http://api.rubyonrails.org/ 自己查好了.

请求携带的数据

这一点刚开始也让我曾经产生过困扰, 事实上, 对于任何 http 动词的任何请求, 你都可以以哈希的形式传递一个请求参数给控制器 (通过 params), 只不过纯 GET 请求, 通常都只是纯响应一些信息, 更多的情况下, 是 POST 请求, 例如一个按钮, 用来提交请求数据到控制器中的 action.


一些常见的惯例以及约定

下面是林林洒洒, 从笔记中挑捡出来的一些常见的惯例, 老生常谈的调调, 我就不提了. 大家都懂的. 我只是提一下通过自己的体会, 总结出来的其他一些惯例, 这些惯例很多其实应该都是 web 开发的惯例, 有些是 Rails 独特的惯例, 了解这些惯例, 会让你更好的理解 Web 开发.

  • 凡是需要 id 的地方, 你都可以直接传递对象.

  • 经常查询的字段 (例如外键或需要 where 的字段), 你总是应该添加索引的.

  • 发起一个请求, 在 Rails 中其一定有一个路由来匹配请求.

  • View 中包含多个参数且存在 block 的方法, 应该给普通参数添加括号.

  • 声明 has_many 时, 总是应该添加附加的约束

  • __大部分情况下, Rails 中的`符号和字符串'是可以相互替换的, 应该优先使用符号形式.

  • 提交模型表单时, 对应的浏览器 URL 都是无意义的, 甚至多数情况下, 你根本不会注意到它 (因为总是 redirect_to, 除非添加了验证)

  • redirect_to 和在浏览器内打入一个网址, 其实没啥不同

  • 在 Rails3 中, 针对 Model 的build方法'和new 方法'其实没啥不同???__ (这个需要高手来验证我的推论!!)

  • 两种形式的`命名路由', ???_path, ???_url, 在物理层面上, 对于浏览器其实没有区别.(更多的来自于语义)

  • 谨慎的使用<%= ... %> 以及 <% ... %>.

  • 局部模板在布局模板之前完成 render 操作的 其实就是把真个 View 模板作为一个 block 而已, 可以好好研究下 yield.

  • 即使在 Model 内, 也应该使用访问器方法来模型的字段. (如果使用实例变量, 当你存在`不代表字段'的实例变量时, 很容易混淆)

  • 给 Model 编写方法时, 如果可以用实例方法实现, 就不应该用类方法来实现.

  • Rails 用不到 ruby-debug, 一个好的调试惯例是:

    • 针对 Model, 使用 rails console
    • 针对控制器, 直接查看 Rails server 输出, 我的经验是, 专门编写了 snippet, 在调试结果的上下添加了醒目的彩色分隔字符.
    • 真是视图, 直接输出即可. 如果是一个对象, 推荐使用<%= debug ... %>, 输出为 YAML, 方便易读.
  • <% ... %> 表达式中可以是任意的 Ruby 表达式, <%= ... %>之中的=方法, 行为可以近似的理解为 to_s.

  • 你可以给模型表单传递一个:url 哈希参数, 来跳过 RESTful 形式的默认路由.

  • 任何时候, 你都不应该在 view 直接创建一个对象 或 声明一段对象的关系., 你总应该通过控制器来传递对象给视图. 貌似很多代码并不遵循这个约定, 例如在视图中会存在: @post.comments.each, @post.comments.new, 这应该是错误的, 你应该在控制器中声明这些逻辑, 然后在 view 内使用它. 如果你不遵循这个约定, 例如在视图中, 会经常出现一些莫名其妙的问题.

  • 如果你需要验证一个东西是否是合法的, 你首先需要验证, 这个东西是否真实存在?

  • find_by_id(:id) 和 find(:id) 两者的表现是完全不同的.

  • 路由嵌套永远不应该超过两极嵌套, 如果存在超过两级的关系, 应该写为两个 resources block, 而且, 应该总是使用 shallow 参数.

  • 有关 Model hook 方法的一些惯例:

    • before_??? 之类的钩子方法, 返回 true, false 来确定`具体是否被执行'.
    • after_??? 之类的钩子方法, 应该抛出异常 (通过事务回滚的方式), 来回滚到之前的状态.
  • 善用 rake db:schema:load

  • 你必须明白的两个很浅显的道理:

    • 浏览器不知道什么 RESTful 形式的 path, 它只知道 URL 地址和动词.
    • Rails 代码中只关心 RESTful 形式的 path, 它不应该考虑具体的 URL 是什么, 那个是 routes 该操心的事情.
  • 你应该总是首先 skip_before_filter 公共的过滤器, 然后再添加自定义的 before_filter.

  • 有一个惯例: 在控制器中的 before_filter 之中, 几乎总是使用 unless 跳转... end...

  • 使用 Ajax 刷新 partial 时, 时刻记着, 不要忘记从 action 传递所需变量给 partial 模板.

find_by_id(:id) 和 find(:id) 两者的表现是完全不同的.yes

-编辑掉-

即使没有太多的 Ruby 经验, 你完全可以成为一个很好的 Rails 程序员

这点我强烈不赞同。。。

即使对 ruby 够了解,对架构不了解的,也不可能成为好的 rails 程序员。。。我到现在还是个高级菜鸟。

#6 楼 @fredwu

嗯. 你说的是正确的. 其实, 站在我目前这个角度, 来谈这个问题, 的确不太靠谱. 事实上, 的确, 完全可能很多时候不知不觉的应用了一些 Ruby 的知识到 Rails 中, 自己都没发觉. 只是想比较而言, 数据库, HTML 更重要一些吧.

正在学习中,先 Mark 下

这样的理解是使用级别的,和一个增删改查的 java 类似。

redirect_to 和在浏览器内打入一个网址, 其实没啥不同

最后的效果相同,但是原理不同。

两种形式的`命名路由', ???_path, ???_url, 在物理层面上, 对于浏览器其实没有区别.(更多的来自于语义)

这个肯定是不同的,一个是相对路径,一个是绝对路径。

给 Model 编写方法时, 如果可以用实例方法实现, 就不应该用类方法来实现.

这个要看你编写的方法是什么。。

Rails 用不到 ruby-debug, 一个好的调试惯例是

真的用得到啊。。有时候遇到了奇怪的错误,就需要从 controller 开始,单步调试。

<% ... %> 表达式中可以是任意的 Ruby 表达式, <%= ... %>之中的=方法, 行为可以近似的理解为 to_s.

为什么要近似理解呢?其实很简单,对于 <% .. %> 这样的语句,执行结果不会输出,而<%= ... %> 会把执行结果输出。

路由嵌套永远不应该超过两级嵌套

Rails 最多支持 三级嵌套。

对初学者给力啊, 非常感谢分享. 这些都是实实在在的经验, 即便不全面,也能快些上路

先收藏了。支持

#7 楼 @zw963 我现在刚学完 ruby,又回到 rails 开始学习了。感觉还是了解一下本质会更好

谢谢 先收下了

我觉得,有些经验自己不经历,还是无法理解的

#11 楼 @daqing

你说的前两条我理解, 你注意到了吗? 所以, 我会在前两条内都加了其实两个字. 就是这个意思.

有关那个=方法, 貌似就是 to_s, 我只是想告诉大家其中的猫腻而已. 不过不确定. 我没翻过源码.

至于最后那个, 我想说的意思是: 三级嵌套, 你应该分两个来写.

例如:

resources :groups, :shallow => true do
  resources :posts
end

resources :posts, :shallow => true do
  resources :comments
end

中文看起来真吃力…… 盯着"表单 “看了半天才反应过来是 form PS. 感叹号太多了

不论质量,不论武功,单看文章,LZ 的心情激动,愉快,喜欢分享,这点 值得我们学习,楼主也是学习狂,me too.

后部分主要在理解 MVC 模式中三者的关系构成么?

匿名 #21 2012年07月24日

没看懂, 但个人觉得编程语言的学习和使用应该是最简单的部分, 如果不是(C++?)那肯定是什么地方搞错了。

#6 楼 @fredwu 我喜欢这样谦虚的人。要向高级菜鸟学习!

#22 楼 @googya 呵呵,这个也是 “磨练” 出来的我觉得。若干年前刚开始做程序时,觉得自己蛮牛逼的。但做的越久就越发现自己不懂的东西实在太多了。:)

@fredwu 你好像PHP也很牛

善于总结才有进步,不断的自我提问,做出假设.再去验证。楼主的学习方法很正确 ‘

楼主的坑,我都很熟悉。。

  • 在视图中默认总是渲染 partial, 例如: 在 aaa 控制器的视图中,<%= render 'aaa' %>, 等价于 <%= render :partial => 'aaa/bbb' %>

这里是 <%= render 'bbb' %>

  • 在 view 中还可以直接 <%= render @object %>, 等价于: <%= render :partial => objects/object", :locals => { :object => @object} %>

locals 可以省略不写,像这个样子: <%= render 'objects/object', object: @object %>

#28 楼 @ranmocy

第一个是写错了. 谢谢指正, 已经改啦

第二个这种写法我还不知道呢. 刚刚查了下 API, 的确可以这样简写!

声明 has_many 时, 总是应该添加附加的约束

我想理解一下这个。例如添加什么约束条件?

#30 楼 @JeskTop

如果是父子关系, 应该在 has_many 之后声明 :dependent => :destroy 如果是多对一的引用关系, 则应该声明 before_destroy, 确保只要有一个外键还在引用, 就不可以删除主键.

辛苦楼主,不过觉得有几个是不太严谨的地方,想说下:

“发起一个请求, 在 Rails 中一定会有一个对应的 action, 来响应这个请求, 否则就报错啦” 其实是一定有一个路由来匹配请求,真正处理这个请求的 action 倒不一定在 Rails 呢。

“声明 has_many 时, 总是应该添加附加的约束” 这也不一定吧,还是看应用场景。

btw 对于 model association,new() 和 build() 还有有区别滴,见 http://stackoverflow.com/questions/4954313/build-vs-new-in-rails-3

#32 楼 @loddit association 的 new() 和 build() 在 3.1 后没区别了只是 alias 关系,见http://apidock.com/rails/ActiveRecord/Relation/newhttp://apidock.com/rails/v3.2.1/ActiveRecord/Relation/build, 这个改动最近被坑过一次,还是喜欢原来的特性

#32 楼 @loddit

谢谢指正不太严谨之处. 我已经更正了.

#33 楼 @rainchen

果然我猜的没错了. 谢谢.

#35 楼 @zw963 感谢你的分享,我是从 PHP 转过来学 ruby 的,web 开发还是有点体会。能加个 QQ 吗?我的是 229500627

非常感谢分享

刚开始 ruby 收藏下也

据说第一个 rails 版本只有 4000 行代码,不知道现在哪里还有下?我想学习一下

有关 Model hook 方法的一些惯例: before_??? 之类的钩子方法, 返回 true, false 来确定`具体是否被执行'. after_??? 之类的钩子方法, 应该抛出异常 (通过事务回滚的方式), 来回滚到之前的状态.

想问 2 个问题。例如:before_save,我返回 true 了,后面的数值就一定可以 save 成功吗? after 的滚回应该怎么写的?

#40 楼 @JeskTop

你不明白什么是 HOOK 吧? before_hook 是 执行之前被调用, 只有这个 hook 返回为 true, 才会继续, 否则就不继续. 这和你之后是否 save 成功没有关系. after_hook 的回滚自动的. 不用你管, 你只管抛出异常就行了.

哈~ 难得! 我都快四个月没碰 Rails 了, 竟然还记得...

基本的HTML以及CSS一定要会看, 而且会写!!,我已经开始血淋淋了

很好的总结文章,谢谢分享 感觉高手应该多总结一些 rails 的惯例约定,新手对很多约定都不熟悉

自我感觉,不建议没有学过 ruby 的人,直接学习 rails,虽然现在很多人都是,而且现在的 micro framework 很多,上手很快,只有当你觉得重复造的轮子的花费比较大,或者说自己仅仅只是用 ruby 做一个东西,不想过多的了解深入的东西(框架的东西或者说元编程),那样学习 rails 才会很快(前提是你用过其他框架或者学习一段时间 ruby 并开发过网站)

47 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号