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

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

经过断断续续几个月的学习,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 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号