分享 Rails 开发:那些年,我们一起踩过的坑 (剧终)

scriptfans · 2015年03月20日 · 最后由 wuyuedefeng-github 回复于 2018年04月02日 · 29789 次阅读
本帖已被管理员设置为精华贴

自打 06 年接触 Ruby 开始直到现在,一直从事基于 Rails 的 Web 开发,不知不觉九年过去了……从最开始的惊艳,到喜欢,再到热爱,随着开发经验的不断积累,自己对 Rails 这个框架的看法也有了很大的变化(干了十年的开发,或许是成熟了?),正所谓爱之深,恨之切,大概也就是这个意思吧。今天在这里开个头,让我们抛开 Rails 的优点,只说不好的地方,当然其中本不是框架自身的问题,但是既然遇到了,就写出来,欢迎大家继续补充或拍砖:)

架构

Rails 一向以开发效率高而著称,这从当初那个“十分钟构建 blog”的操作视频就能看出来,这也是吸引我关注它的一大原因(那会儿还不知道 Ruby 是啥,更不要提语法优美了)。MVC、ActiveRecord、脚手架、惯例优于配置、DRY、Database migration……这些新东西组合起来,在当初那个时代算得上是超前了,但是就如 DHH 的固执一般,基于 Rails 来做 Web 开发也意味着你必须遵守他的这些潜规则才能用得舒服:Autoload?恩,你不必显式的 require 依赖,直接在代码里面用就好了,但前提是目录结构、文件命名都要按照约定的来;RESTful 默认定好了七个 action,想要改别的名字可以,自己改 route 定义去吧……

上面这些都还好,其实最大的问题来自于 Rails 对 MVC 的理解,模型层是 Active Record 模式的实现,控制器获取请求数据,执行一系列的操作,并最终决定渲染什么样的视图返回给客户端,看上去简单直观,不是么?Ruby 社区一向对 Java 这种大怪物不屑一顾,JavaEE 那些层次划分与设计模式从来都不被待见,但为什么会发展到那种地步,肯定是有其合理性的,就拿“服务层”来说,Java Web 框架在集成 ORM 的时候,一般都不会采取 Active Record 这种充血模型,而是将 Service Object 与 DAO 分为两层,专门使用服务层来封装复杂的业务逻辑,ORM 只负责数据的存取而已。为什么会这样设计?其实一开始我也觉得是把简单的问题复杂化了,ActiveRecord 多好用,模型类就是表,属性就是字段,再加上业务算法,多省事儿!恩,简单的场景下这种方式确实简洁明了,但是随着系统的业务需求不断增加和变化,还采用这种模式的话,就要求你不断的扩充模型类,往里面加代码,”Duang,Duang,在 PM 要求加功能的时候,其实我是拒绝的……”,最后的事情大家都知道的,恩,说的就是你,抓个典型 User.rb,打开看看我敢保证你一眼是看不到底的,更不要说理解里面的逻辑都是干嘛的了。什么,你把逻辑都写控制器,模型一点都不复杂?好吧,你牛你先走。

其实 Rails 也不是不知道这个问题,违反 SRP 的事情谁都不好意思当优点讲,所以 4.0 开始加入了 Concerns,寄希望于给开发者多一种组织代码的方式,但是别忘了,这仅仅是利用了 mixin,将代码物理上分开而已!就好比你嫌屋子脏,拿起扫把将垃圾清扫到四个墙角,然后拍拍手认为打扫干净了。Jim Gay 在他的《Clean Ruby》中说,你不要看表象,最好打开一个 rails c,然后看看 puts user.methods,真相就是这样……所以 Rails 社区就服务层,或者更准确的说,如何避免过渡使用充血模型这个主题也是争论不断,有人赞成 Java 社区的做法,也有人另辟蹊径,想采用装饰器、DCI 等更新颖的方式来实现瘦模型,但问题的根源在于,这些方案都不是官方的,可不符合 Rails 的“哲学”,不是么?

总之,就架构层面的问题,我个人主观、固执、不负责任的认为,Rails 的架构就是: 基本没有架构 ……

使用中可能遇到的坑

前面关于架构的牢骚看起来既空洞又无趣对吧,恩,那接下来写的内容应该会更贴近实际,你们拍砖轻点,哈哈。

Turbolinks

这货也算是 DHH 得意之作了吧,丫就喜欢在某个版本里面突然就给你搞出个新概念来。来看看 Rails guide 官方是怎么说的:

Turbolinks 为页面中所有的 a 元素添加了一个点击事件处理程序。如果浏览器支持 PushState,Turbolinks 会发起 Ajax 请求,处理响应,然后使用响应主体替换原始页面的整个 body 元素。最后,使用 PushState 技术更改页面的 URL,让新页面可刷新和收藏,拥有美观的 URL。

瞧见没,好处大大的对吧,但是……实际开发中这货不是那么美好,首先,因为页面加载流程被改变了,你不能再使用之前的 dom ready 来监听页面加载完毕与否了,必须使用 page:change、page:load 等自定义事件才行,这都不是什么问题,改改遗留代码就好(其实也挺费事的,不是么);最坑爹的是,Turbolinks 会缓存加载过的页面,默认是 10 个(具体可以参见其 github 项目 readme 页面,或者查阅 the rails way,官方指南可不会告诉你!),这就很蛋疼了,要求你的 js 脚本对 dom 的操作必须是“幂等”的,什么意思,就是说同一段代码会在同样的 dom 节点上执行多次,你,作为开发者,code monkey,必须自己来保证不会出问题!(我擦)没明白什么意思?恩,手把手来个例子好了,比方说,你监听了 page:change 事件,并在其中给某个按钮添加了点击事件,弹出个 bootstrap modal,比如电商网站经常放导航条上的购物车按钮,很常见对吧?访问商品列表第一次加载没问题,用户点击某个商品查看详情页面,也没什么问题,但是,如果这会儿他点击了浏览器的“返回”按钮,那么问题就来了,因为 Turbolinks 是会缓存之前访问的页面的,默认十个哟,第一次访问列表页的时候点击事件已经监听好了,这下又执行了一次,然后用户点击购物车按钮,Duang,Duang……弹出两个 Modal,真酸爽。

怪不得别人,只能怪自己不去翻项目的 readme,不好好看《the rails way》,解决办法其实也挺简单的,把这句代码加入你的 js 文件吧:Turbolinks.pagesCached(0),关掉缓存页面的功能就好。另外如果你要基于 Backbone 之类的前端 JS 框架来开发界面的话,最好直接去掉 Trubolinks,要不然,有你好受的,真的。

其实 Trubolinks 也是个替罪羊,那罪魁祸首是谁呢?恩,说的就是你——Assets Pipeline!鉴于这货的罪行,加上现在已经夜深了,我还是先不讨伐它,留作后续吧,先把能长话短说的坑写了。

redirect_to

恩,如果说前面的是框架的原罪,那么这个确实不应该算在 Rails 的头上。具体说来就是,某些客户端(包括 Chrome 的 Ajax 实现、Rested、Paw 等)在发起的 delete、put 等非 get 请求时,如果被 redirect 了,后续的请求依然会采取同样的方法而非 HTTP 规范里要求的 get 方法,这会造成死循环,或者导致服务器返回错误信息(很显然嘛,请求方法都不对,除非你正好有路由规则能碰上)。解决方法说起来也很简单——设置 303 status code 响应码:

redirect_to posts_url, status: :see_other redirect_to action: 'index', status: 303

更多信息可以参考 redirect_to 方法的 API 文档:

If you are using XHR requests other than GET or POST and redirecting after the request then some browsers will follow the redirect using the original request method. This may lead to undesirable behavior such as a double DELETE. To work around this you can return a 303 See Other status code which will be followed using a GET request.

这不是 Rails 的错,真不是,但是总有一天你会遇到的,比方说我就很喜欢在异步删除某个资源之后,直接 redirect 到 index 列表,如果不注意,那么就会执行delete /resources……


继续填坑……

先回复下前几条评论的大大:

@blacktulip 不到万不得已,还是别 disable 这个特性,毕竟是 Rails 的新功能,好处还是有的。其实最主要的原因是 Assets Pipeline 的坑需要用 Turbolinks 来弥补,可以说就是帮它擦屁股的!因为采用 Assets Pipeline 的方式来 serve 静态资源的话,会把 javascript、stypesheet 都合并成单个文件,以减少浏览器需要发起的请求数量,但是这样一来就会导致文件本身比较大,每次全页刷新的时候加载速度会有一定的影响,所以 DHH 才会用 Trubolinks 来做异步抓取并判断资源文件是否有改动,没有的话仅仅替换 body 的内容,这样就不会白屏了。如果你采用了 Assets Pipeline(我想绝大多数 Rails 应用都是这样,主动去关闭的应该很少吧),最好还是留着 Turbolinks,以便减轻前者带来的性能问题。

@kgen 这篇文章的主题是吐槽,Rails 的好处自然不必多说啦,抛砖引玉就是想让大家都来发牢骚:)当然我知道如果仅仅是抛出问题又不给解决方案,那肯定会被大家认为是脑残黑,所以这里就第一点再扩充讨论下:

首先,我们要承认 Rails 是一个非常固执的框架,DHH 的性格不可能让 Rails 变得迎合大众的口味,还记得他那篇有名的 blog 么? Rails is omakase Rails 最大的特点也就是为开发者提供了一套简单的解决方案,惯例优于配置(COC)、DRY 和最小惊讶都是大家耳熟能详的原则,初学者通过简单的学习就能掌握构成 Rails MVC 的几大组件:ActiveRecord,ActionPack,Route,然后就可以快速开发 web 程序了。在社区里最常见的一个词就是 rails way(这里不是说的同名书 The Rails Way,你懂的),写代码、思考问题的时候都会尽量的靠着这个目标来,但是,the rails way 并不一定等同于 the right way,我想这点没人能反驳吧,类似哲学的问题,嘿嘿。要不然为啥那本同名的 rails 圣经(The Rails Way)最新版在编写的时候,宁愿冒着被读者骂的风险抛弃默认栈的组件,改为使用 haml 编写 view,rspec 做 tdd,以及使用decent exposure做控制器 - 视图的数据共享?

初学者在接触 Rails 的时候,肯定首先考虑官方的默认设计,并严格贯彻这一套思想去指导开发,久而久之,也就养成了习惯,姑且不说这些习惯好与不好,对今后的影响肯定是比较大的。我觉得 Rails 最大的问题就是,默认给你个简单的,第一眼看起来很不错的东西,但是又不是完备,非得等开发者自己去摸爬打滚,通过长时间的填坑慢慢积累经验。默认的 MVC 实现很好的分离了模型、视图、控制器(其实后两者还是纠缠不清,都放到 ActionPack),简单的 CRUD 只要遵循原则写起来确实快速方便,但问题是业务复杂之后,很多逻辑明显不能放 Controller 对吧,于是按照 Active Record 充血模型的原则,很自然就让人觉得该放到 model 层,thin controller,fat model 应该是首先被社区认可的手法,但这样真的是最佳实践吗?反正基于 Rails 潜移默化的引导,随着开发经验的增长以及业务逻辑的日趋复杂化,羁绊的问题会逐渐凸显出来。这里举个简单的例子,很多 Rails 开发者都认为,一个类如果不继承 ActiveRecord::Base 的话,就不算是 Model(比方说电商网站的购物车 ShoppingCart),那这些代码放哪里呢?框架指导性的目录结构只有 models/controllers/views……恩,有个 lib,放这里吧!但其实它真真切切就是个 domain object 啊,放 models 很合适不是么?

缺少服务层的抽象,提倡充血模型,将业务逻辑放到 model 层,这是 Rails 最大的问题,这样的潜移默化会让开发者在后期尝到苦果……

解决方案?我们先来看看国外的开发者们都是如何应对的吧,相比他们的槽点和做法,我这种小白真心只是稍有微词,呵呵:

首先有请 Avdi 大神隆重登场,人物信息我就不用废话了,大家 google 一番自然就能知道谁谁谁。我们来看看他的这部《Object on Rails》,在线版是免费的,推荐各位感兴趣的 Rubyist 有时间都看看。在 Avdi 看来,Rails 风(下文均以此指代 Rails Way)根本就算不上面向对象!他觉得 AR 层实在是肩负了太多的责任,应该减负,所以自己写了个叫做 FigLeaf 的 gem 来限制模型类的接口,只将 ActiveRecord 作为 DAO 工具来使用,除了简单的 CRDU,其方法一律被自动设置为 private 的,够疯狂吧?通过这种方式强制让你思考,业务逻辑到底应该放在那里(Rails 君含泪飘过,我可不造,你造吗?)。另一方面,Avdi 还提出了一个新名词:Exhibits,说白了其实就是 Presenter 的一种变体(虽然他自己觉得区别很大),只不过加上了 chainable 功能的 decorator,通过层层装饰,让模型对象在不同的上下文环境具备不同的功能,比方说在 view 里面可以附加上展现相关的逻辑,这些逻辑原本是不应该放到模型对象的类定义里面的。

然后,Andrzej Krzywda 在他的《Rails refactoring》里面,也含泪控诉了多条 Rails 的不是,焦点都集中在 Controller 上,他认为 callback(比如 before_action 这些)、cv 通过实例变量来传递数据等等一系列 Rails 开发者习以为常的做法都是错误(或者说,是不好)的,建议引入 service object 来处理复杂的业务逻辑,让 controller 只处理跟 http 有关的事情(解析请求,发送响应)。

再然后,更激进一点的,Jim Gay 写了本《Clean Ruby》,想从源头上解决复杂逻辑的组织问题,这次可不仅仅是黑 Rails,连 Ruby 都黑了,他认为,我们之前写代码的方式,都称不上 Object-Oriented,只能算作 Class-Oriented,正因为不是真的 OO,才会导致违反 SRP,增加开发人员对代码的理解难度。他的方案通过将 User Story 映射到场景,然后把代码按照“是什么”以及“做什么”的类别分开,基于 Data(数据)、Context(上下文)、Interaction(交互)来编写代码,以便更适合心智模型。


吃完饭了,继续填坑。看了下评论,可能某些大大有点误会了,我发这贴的初衷,并不是为了说 Rails 有多烂,而是想开个头,让大家能互相吸取经验,避免在同一个坑里面摔倒。我用过很多 web 框架,包括 java 系的 ssh、grails,以及 python 社的 django,还有 node 的 express,直到现在还是觉得 Rails 比他们先进、好用,但还谈不上完美,真的是爱之深恨之切:)

接着说老外的解决方案。

上面那些都是比较偏门的,而且不成体系,那么好戏来了,请看这个:

《Trailblazer》

就像它的名字,很新奇,在一堆 leanpub 的书里面,你绝对看不出是讲 Rails 的,我敢肯定,当然副标题 A New Architecture For Rails 深深的出卖了它:)作者的信息我也不在这里废话了,请自行 google。在这本书里面,Nick Sutterer 提出了一套完整的架构风格,从前到后,展示了完整的、他认为比较合理的全栈模式,当然我觉得最好的一点是,他遵循了一个标准,那就是尽量不或者很少的打破 rails 风,这点很好,为什么这么说,等待会儿讲另一种方案的时候对比一下就知道了。Nick 给出了实际可用的东西,不得不令人钦佩,具体内容我还是不透露了,大家都去买一本支持他吧,虽然还没写完,这里转帖下他自己的大纲说明:

Logicless models solely focusing on persistence. Skinny controllers working as endpoints, only. Domain objects for your business logic, known as operations. Form objects for deserialisation and validation. View models to introduce a widget architecture. Representers that parse and render documents for APIs. Twin objects for decorating models.

是不是很眼熟?没错,这些手法都是为了解决正统 rails 风没解决的问题,也就是我之所以主观认为的,rails 没有架构,需要更高层面的方法来指导,呵呵。

还有一种从不同视角去组织业务逻辑的方式,那就是 Modular,也叫做 Component Based 架构,这在我之前那篇《Spree 源码导读》中有过介绍。具体说来,就是采用 Rails engins 的方式,将整个系统的功能点划分为不同的模块,打包成 gem,然后在需要的时候可以随意组合,这在很大程度上解决了上面都没提到的 Rails 的一个问题:倾向于促使开发者实现“孤岛”应用,也就是无法很好的拆分组合、巨无霸的应用。这种方式目前在国外 Rails 社区也是比较热门的话题,相关研究也比较多了,比如:

《Component-Based Rails Applications》 Stephan Hagemann 的新书,目前也还在编写阶段,但是非常值得期待,他在 RailsConf 2013 上有过讲座,视频就在书籍介绍页面,不容错过。

《Modular Rails》 作者 Thibault Denizet,现居曼谷,这本书现在已经完成了,也是我目前主要的学习资料。

看起来很美好的架构,但是的但是,经过我个人的实操发现,其中最蛋疼的是,完全打破传统的 Rails 风,这从 Modular Rails 的前面章节就能看得出来,要改多少东西,包括目录结构、文件名、类名……得出的结论是,初学勿入,在复杂性没到一定程度的阶段,折腾大于实际带来的好处。当然,或许是因为个人天资不行,摸爬打滚了九年还是这模样,你,大概可以哟。

好了,问题抛出来,解决方案也有了,但现在我又有了更大的疑惑:

问题的问题是,这么多条路,都不是官方扶持的,我应该选哪个?真是个问题,你觉得呢?


关于前后端代码重复的问题,这里还想解释下,这里的“重复”是怎么定义的。我的想法是,如果后端 view 可以简单实现,那就大可不必非要把构建逻辑移到前端 javascript 身上,除非你的团队有明确的分工,前后端可以有清晰的职责分离,要不然对于一个前后台都要写的程序员来说,其实挺精分的。还以 Ajax 购物车为例吧,你有一个按钮,点击之后异步请求服务器,成功之后在当前页面上弹出一个 bootstrap modal,因为这个功能全站都在用,也不会提供其他的展现方式,那么你完全可以就用全局的 CartItemsController 来提供接口就好:

class CartItemsController < ApplicationController
  layout false #我只需要返回html片段,无需layout
  def index
    @cart_items = shopping_cart.cart_items
    #这里我选择render默认的view,一个table之类的,因为不想把逻辑交给js,毕竟整个网站就一个购物车弹出,无需响应json
  end

  def create
    shopping_cart.add cart_item_params
    #新加商品到购物车,之后,怎么来得简单?我懒,所以干脆直接重定向到index呗,都不需要关心总价怎么更新
    redirect_to :index, status: 303 #恩恩,你不加303?好吧……而且这真不是什么炫耀,只是个善意的提醒
  end

  #其他的修改购物车商品数量啊,删除购物车项啊,清空购物车啊啥的,都可以同样的逻辑,操作完直接重定向好了

  private

  def shopping_cart
    # 这里抽象的购物车是一个model,但是不必是AR,传self你可以获得用户是否登录来执行不同的操作,比如游客的话就操作cookies……
    ShoppingCart.for self
  end
end

为什么说这样的偷懒做法是可行的?因为简单。你可以继续用 Rails 的 view helper,比如 link_to、form_for、url_for、xx_path…… 当然你也可以设计成返回 JSON 数据,但是那样的话,你就把构建前端展现的责任推给前端开发人员了,在明确分工的团队,这是合理的,但是对于一个从头干到尾的苦逼程序员来说,为了组织代码,你得做前端 MVC,比如用 backbone,然后用 eco 或者 ejs 来做 template,将服务器返回的 JSON 数据 eval 到 template 中,最后再更新到 dom 树,这又是何苦呢?其实 Ajax 的那个 x,一开始强调的是 xml,html 作为一种通用表述,直接返回片段标记也是非常可行的,装逼点说就是 Ajah,《the rails way》在讲解 Ajax 的章节也有比较具体的实例场景,以告诉读者,某些时候这种方案会比 JSON 纯数据接口更加合适。如果你真喜欢严格分离的方式,有些问题必须记住:

  1. 你要自行构建界面元素,无法再利用 rails 的便利,比如 form helper,千万记住自己解决 CSRF;
  2. 同样是因为第一点,不能使用 url helper,所以千万别写死 url(你不会想每次修改 routes.rb 之后再去搜索那些 js、eco 模板,对应的一个个改的。这个时候我要强烈推荐这个居家旅行杀人越货必备的 gem:js-routes

至于响应*.js.erb 的做法,我个人是非常反感的,这个方案会导致前后端的耦合度大大增加,你的服务器端代码、前端 js 都得了解具体的 html 结构,这是很蛋疼的,ajah 的方式还好,嘿嘿……

然后,那个改成桌面 GUI 的场景,我换个方式好了,BOSS 说:Rails 好难用,讨厌得很,明天给我改成 sinatra 或者 ooxx 框架!什么,服务层是基于 rails 特定的 controller,没法直接用?我不管,你加班……

继续挖坑,闲着在控诉 Assets Pipeline 的罪行,DHH 挖的一个大坑,然后从旁边挖了点土来填,于是 Turbolinks 就粗线鸟,哈哈哈。剧透下,接某位大大那句话,IE 6 的兼容性问题要不要算到 Rails 头上?这个我表示中立,但是 Assets Pipeline 的问题确实有一部分跟 IE 有关,而且正是 Rails 导致了这个问题发生的几率大大增加,可以说,项目大了必然会遇到!


来最后补一刀,在我看来,Assets Pipeline 不说有七宗罪,至少也有两宗罪:

第一宗罪:增加集成客户端组件的复杂度

大家都知道,要做出美观漂亮简洁大方的用户界面,其实是挺麻烦的一件事,特别是对我这种不懂设计、前后端都得一个人来扛的野生程序员来说,更是难上加难。只有借助诸如 bootstrap 之类的 css 框架,以及国外设计师设计好的商业模板,才能在最短的时间完成项目,尽早上线。比方说,http://themeforest.net/就是一个巨大的宝藏,不会设计也没关系,好好选个适合手头项目的 theme 其实不是难事,花费也不会太高(比专门请设计师做划算多了),这里随便选个来做例子,就 Flatlab 吧,蛮漂亮的扁平化风格 Admin Template:

介绍页: FlatLab - Bootstrap 3 Responsive Admin Template 在线演示: http://thevectorlab.net/theme/?theme=Flat%20Lab

一般来说,这些模板在购买后,你会得到一个源码包,里面包含了完整的 html、css、javascriot 以及相关图片,类似这样的目录结构:

在早期的 Rails 项目中使用这些设计模板是非常简单的事情,你只需要把必须的资源放到 public 目录下就可以了。但是现在,由于 Assets Pipeline 的机制,要正确集成却成了一件非常蛋疼的事情,这是因为:

Rails 项目以 production 模式启动的话,默认是不会动态实时编译 assets 文件的。这就要求你在启动之前先自行编译,Rails 会按照 Manifile 声明好的依赖关系和顺序,去压缩串接资源文件,比方说那个 application.js 以及 application.css,让然如果你在配置文件(/config/initializes/assets.rb)设定了其他的预编译文件,他们也会被处理好,最后放到 public/assets 文件夹下(具体的细节请参见官方指南 Assets Pipeline 的相关章节)。要成功编译,并保证运行的时候所有需要的依赖都正确,其实是件很蛋疼的事儿,因为这些 css 或者 js 代码中,如果涉及到字体,或者图片的话,作者原来都是基于相对路径来编写的,一点问题都没有对吧?这里节选一段(你可以随便打开模板源码中的某个.css 文件,搜索下“url(”这个字符串,一般都是用来设置背景图片的):

/* vendor/assets/themes/flatlab/css/style.css 的片段代码 */
.active .dcjq-icon {
    background: url(../img/nav-expand.png) no-repeat bottom;  /*注意这里的图片路径,真实地址是vendor/assets/themes/flatlab/img/nav-expand.png */
    border-radius:3px;
    -moz-border-radius:3px;
    -webkit-border-radius:3px;
}

这样写在本地文件系统直接打开相关页面是没有问题的,你放到服务器上按理说也是没问题的,但是如果启用了 Assets Pipeline,那么问题就来了:Rails 认为编译之后的资源都放在 public/assets 文件夹下,url 访问路径就是/assets/,如果不做修改,直接把这些 Theme 的源码 require 到 manifile,rails 可不会智能到帮你自动修复相对路径,比方说上面的代码,最终会导致浏览器访问: http://demo.com/img/nav-expand.png 结果你懂的,404,找不到对象……恩,谁都不希望找不到对象是不是,嘿嘿…… 解决办法是有的,那就是你只能采用 Rails 提供的 Assets helper 来动态生成这些资源路径,但是前提是,只能在 erb 或者 scss 中使用,比方说采用 erb,然后我把整个 theme 源码放到 rails 工程的 vendor/assets/themes/flatlab 下(为什么会是这个目录,下文会解释),接下来需要将原始的 css 文件改名添加.erb 后缀(这样你才能享受 Assets Pipeline 的便利哟),最后去把 所有的 路径改为类似这样的: background: url(<%= asset_url flatlab/img/nav-expand.png %>) no-repeat bottom; …… …… …… 处理完所有的 css 文件 …… …… …… 加油少年,继续处理所有的 js 文件 …… …… …… 然后的然后,这还不算完,因为,你还得去/config/initializes/assets.rb 中把所有非 css 和 js 的资源文件(不仅是各种图片,还有各种字体哟)加入预编译路径,类似这样的:

Rails.application.config.assets.precompile += %w(
  flatlab/fonts/*.*
  flatlab/img/**/*.*
)

因为 theme 作者经常会使用熬很多三方开源的东西,我们看看 flatlab 的目录结构就知道了,它把所有的三方 plugin 都放到 assets 子目录,如下图:

看到了没,这带来的后果是什么你造吗?意味着,你还得递归去处理每一个三方 plugin 的资源路径!改文件名,改源码,改改改……

然后为啥我建议将整个 theme 放到"vendor/assets/themes/模板名/"这个目录?其实也是为了减轻集成的工作量,因为 Rails 在搜索资源的时候,其实是不会关注目录名的,只要在 app/assets、lib/assets、vendor/assets 下的任意一个子文件夹下都可以,我们这里搞个 themes 来存放所有的模板,以后也好做切换皮肤的功能,另一个原因就是,可以利用资源 index 的功能,也就是在某个目录下如果有 index.js 和 index.css 这两个 manifile 的话,你在主 manifile 中就可以简单的引用到整个目录,就好像一个独立的文件一样(同样的原理可以应用到子目录上,比方说其他三方插件),然后在 application.css 中:

/*
*= require flatlab
*/

恩,让我歇口气,解释步骤都用了半天,我敢说作为看官的你,都觉得太折腾的对不对?完全同意!但有什么办法,要用 Assets Pipeline 就得这样……你不用 Theme 的?那我想问,你用不用三方 UI plugin,比如 js tree 之类的?如果用的话,正巧那个 plugin 又没有人提供现成的 assets gem 包,那么恭喜你,还是难逃一劫,嘿嘿。我在想如果 bootstrap 不提供 gem 包的话,这个问题会有更多的人感同身受吧,那些 icon font……所以真的要感谢下那些做 assets gem 的人,挺不容易的。

这还不算完,某一天某个 plugin 告诉你升级了,加入了多少多少新功能,解决了 N 多的 bug,请问你打算怎么办?唯一能做的,就是下载源码包,然后再来一遍上面的功夫,爽不爽?如果想要亲自尝试下,我强烈推荐各位集成下这个:

百度编辑器

绝逼是个典型,为啥到现在都么有 ruby gem?恩,我来告诉你,因为源码里面到处充斥了 hard code 的资源地址,包括 js 里面,有你好受的,嘿嘿嘿……

这是就是 Assets Pipeline 的第一宗罪: 徒增集成前端组件的难度,以及后期维护的工作量


回来了,继续最后的控诉。

第二宗罪:诱发 IE 解析 css 的 bug

实在无力吐槽了,码子挺累的,各位先自己 google 下这个关键字:IE css 4096,或者直接点这里:

Internet Explorer 9 and below have a max of 4,096 selectors per stylesheet.

光看标题就知道了吧,非要翻译的话,那就是:IE10 以下的所有版本,针对同一个 stylesheet link 的样式文件,最多只能解析 4096 个选择符(selector)。其实真相是:

A sheet may contain up to 4095 rules A sheet may @import up to 31 sheets @import nesting supports up to 4 levels deep

兄弟姐妹们,知道为啥我想骂街了吧?不知道?你好懒啊,偶继续翻译:

单个样式文件最多只能包含 4095 个规则 单个样式文件中,采用@import声明最多只能导入 31 个子文件 @import的嵌套最多支持到 4 层

你非要问如果超过了限制会怎么样,那我来告诉你,多出来的样式声明将会不起作用……

还是不明白这意味着什么?I 服了 U,奉上白话版——

在 Rails 没有推 Assets Pipeline 之前,如果要诱发 IE 的样式 limit bug,其实挺不容易的对吧?要么单个样式文件超级长,超过了 4096 条 rule,要么你在一个样式里面@import了超多的子样式,最后一条貌似很难发生吧,谁蛋疼到嵌套 N 层的@import

现在好了,Assets Pipeline 横空出世,大声告诉世人,我们提供了一套非常完美的解决方案,能帮您实现静态资源的源码结构化管理,以及自动压缩、合并……

然后的然后,你们懂的,application.css 作为集大成者,串联了 bootstrap、font-awesome、ooxx-awesome 之后,何止 4095,40950 都是小菜一碟,不是么?

那么问题来了,您的最终用户,全都是 geek 吗?他们不用 IE 来着?或许都是有钱人,统统升级到了 IE10?好,当我什么也没说,关掉浏览器享受生活去吧。不是的话,你就惨了,跟我之前一样,面对这个神奇的 bug,只能毫无头绪,为啥?因为别的浏览器都是好的哇,还有哇,我的开发机上跑起来也是完全正常哇,还有哇,IE 我要如何调试哇???DHH,我能讲脏话吗?

其实如果知道症结所在,只要 google 下上面的关键字就能找到解决方案,毕竟已经有人掉过坑了,不过很不解的是,为啥都是老外?各位国内的同胞们,可都是见过大世面做过大项目的,为啥从来没有发现过?难道就因为个人天资愚昧,花了九年才有缘碰到吗?真是缘分呐。

如何解决?请参考这个 gem:

css_splitter

说多了都是泪,这事儿完全应该交给 sprockets-rails 这个核心 gem 来完成,在编译的时候统计下 rule 的数量,自动切分 css 文件,然后在 link 到页面上的时候自动处理 IE 的问题,这样的话,整个世界就清净了。

(完)

嗯,turbolinks 这东西确实,我现在一般直接关掉

06 年接触 ruby,在国内算元老了吧?

10 分钟 blog 用的现成模块吧?

楼主文采不错,不过……

第一条太牵强了

其实,Rails 已经提供了最佳实践:

  1. 在你业务简单的时候,让你简简单单用 ActiveRecord 模型。
  2. 复杂的时候,你可以用官方推荐的 Concerns。
  3. 更复杂的时候,可以通过 gem 和 API 来拆分。
  4. 极端复杂的时候,由于 Rails 3/4 两轮更新,模块化地更好了,你可以设计自己的架构,按需使用 Rails 提供的工具。

楼主说:

Rails 的架构就是:基本没有架构……

不知道这个结论是如何得出的 😪

redirect_to 那条是来凑字数的 😄

这是浏览器的行为,无论你用什么语言开发,什么框架开发,都会遇到这样的问题。

如果这个算 Rails 问题,那 IE6 兼容性问题一定要一起带上,算给 Rails 的坑,哈哈~~

#4 楼 @kgen 我感觉逻辑复杂了,rails 的 mvc 还是太过于简单了。你知道你是咋认为的?

#5 楼 @zhaozijie 当架构变得极其复杂的时候,为什么还要坚持全部使用 rails?我一贯认为 rails 最适合在于产品的前期和发展期,中后期若是变复杂了,就可以做架构分离了。这已经不是 rails 一个人的问题,任何大型产品都不太可能从头到尾用一个框架搞定。我觉得这不是 rails 的“问题”,而是软件工程一贯的挑战。

1. 如果你需要可以 mkdir app/services 新建一个 service 层。

DHH 认为 Controller 就是 service 层,据我了解 DHH 从未建议把所有业务逻辑都写到 Model,所谓充血模型是一些文章一厢情愿。

要了解更多可以看这篇文章:Keep email views out of the models http://david.heinemeierhansson.com/2012/emails-are-views.html

2. 我觉得很奇怪,很多人愿意用一个前端框架重写整个应用,却不愿意了解 Turbolinks 的原理。(也许这是两个人群)

3. 从未在 js respond 写 redirect_to,因为知道这不会跟 http 请求一样响应,没觉得这是个问题。

#6 楼 @nightire 很同意的。但是我觉得后期 rails 使用上装饰器 server 等东西后,复杂度可以降低的。

#7 楼 @Rei Controller 就是 service 层???OMG……

service 层的存在,首先是要解决逻辑归宿的问题,另一个巨大的优点就是,通过抽象出这个层面,你可以极大的提升代码可重用性!比方说,我现在的系统有 web 界面,好,我用 rails,然后抽象出了服务层。某一天,我的 boss 蛋疼说要做桌面版 GUI,在前面的基础上,我可以直接把 service copy 过来用,如果你把 controller 层当服务层,因为纠缠了 http 的东西,别做梦了……

#7 楼 @Rei 至于 ajax 请求为啥会 redirect,那是因为很多事情,json response 并不是最佳方案,详情可以看 the rails way 的 ajax 章节,你不会想前后端重复某些业务逻辑的。对我来说,响应 html 片段代码能直接用 form helper,还能利用 ujs 来处理后续的请求,为何还要响应 json,然后让客户端 js 来组织界面,还要注意 CSRF 的问题。比方说购物车,我界面上修改了数量,ajax 发给服务器,处理好之后直接把这个购物车的 html 再返回来就好,不是么?这个时候你就会遇到,修改了资源,然后需要 redirect 成 get 的情况了。万事皆有可能,只不过某些人先掉坑里,然后跑出来告诉别人小心点,仅此而已。

#9 楼 @scriptfans

某一天,我的 boss 蛋疼说要做桌面版 GUI,在前面的基础上,我可以直接把 service copy 过来用

#10 楼 @scriptfans

你不会想前后端重复某些业务逻辑的。

这不是自相矛盾了么。

我没有阻止你加 Services 层,mkdir app/services 一条简单命令就实现了,但是在需求没出现的时候就加那么多层只会把问题搞得更复杂。service object 只是 Ruby object,Rails 框架不会也不能阻止你用。

我 7 楼说的是 js respond,不是 json。不过 json 也一样的,我不会在 html 以外的 respond 写 redirect_to。为什么?因为我测试过,每个 respond format 返回各种内容,例如 js 请求返回 3xx 响应会不会执行,json 请求返回 5xx 响应会不会解析,这在我学习 Rails 的很早阶段、刚知道 respond_to 这个方法的时候就测试过了。干了九年 Rails 开发才总结出这些问题我觉得不是值得炫耀的事。

顺便说说 js 请求重定向的方法:

// destroy.js.erb

// with Turbolinks
Turbolinks.visit('<%= resources_path %>');

// without Turbolinks
document.location.href = '<%= resources_path %>';

单单为重定向写个模版有点浪费,所以 Turbolinks 3.0 开始给 redirect_to / render 打了补丁,可以直接在 Controller 用 redirect_to / render https://github.com/rails/turbolinks/blob/4e9acc30ded75bb4fd88597b90eadcbf9c809545/lib/turbolinks/redirection.rb

如果充分理解 Rails 框架,自然可以将它扩展到各个场合,但如果大部分时间是在跟框架对抗,那么应该换框架了。我好奇楼主觉得现在哪个框架最先进?我有空参考一下学习它的长处。

干架的节奏

个人觉得 Rails 框架最大的好处恰恰就是精简和逻辑清晰,没有包含很多不必要的东西。当然开始的时候,Rails 的确就只是一个适用于快速开发的框架,框架内部的耦合性也是很高的。但是发展到现在,已经变成了一个非常灵活的、各个组件耦合低随时可拆卸更换的架构了。

楼主在文中提到的一些问题,特别是复杂系统如何解耦的问题,其实并不是 Rails 框架所应该去解决的,而是应该根据自己业务的特点去自己进行进一步抽象重构才好解决的问题。如果 Rails 也考虑这么多复杂的情况,到时候又会有人抱怨 Rails 框架臃肿难学了。

至于楼主提到的 Turbolink 和 redirect_to 的问题,我个人感觉是楼主的用法不是很好。Turbolink 环境下,绑定按钮事件的确应该在 page 加载的时候绑定,为什么要在 page:change 绑定呢?如果只是一个片段,那么可以在这个片段加载进来的时候绑定,或者直接用 delegate 的方式来绑定。而楼主的解决办法就是简单粗暴的把缓存去掉,这个似乎不是最佳的方案。Ajax 请求后重定向,可以用 SJR 的方式返回一个 js 代码来在页面中执行重定向,这样就不会有歧义了。

看来是否充分理解一个框架并能够科学合理的运用它,跟使用这个框架的时间长短没有很强的相关性。

#13 楼 @cuterxy 因为 rails tut 上说了,你以前的 ready 事件现在不能用了,改成 page:change。如果你不用这个事件,那么只能这样:同时监听 ready 跟 page:load,因为第一次显示页面的时候,只会触发 ready,然后接下来由于 Turbolinks 的原理,真正换页面了会触发 load,所以方案二是这样的:

$(document).on 'ready, page:load' ->
  #你的初始化代码

但是真的问题在哪里?作为前端开发者,我 tm 为什么要去管某个 attach even 或者别的什么 dom 操作是不是幂等的?以前没有 Turbolinks 的时候,可不需要闲操心……

至于 delegate 方式,不是无懈可击的,你知道的 ujs 就是通过取消冒泡来阻止 Trubolinks 瞎捣乱的。

#11 楼 @Rei 请看主贴,我会不断的扩充这个帖子,欢迎继续热烈讨论。

#14 楼 @scriptfans 你说“作为前端开发者,我tm为什么要去管某个attach even或者别的什么dom操作是不是幂等的?以前没有Turbolinks的时候,可不需要闲操心……”,那么说到底是人的问题,这就有点复杂了。比如说,前端开发者是董事长的亲戚什么的得罪不了,那这个另说了。我这里只是讨论技术上的最佳解决方案而已。

我又没说 delegate 方式就是无懈可击,只是其中一种解决方案而已,用的时候总是要具体问题具体分析的。

#16 楼 @cuterxy 请为维护老系统的程序员考虑,某些应用在 rails 3 时代就开发了,现在升级到新版,很蛋疼的。还有就是,作为开发人员,你能保证自己的代码按照 rails 的要求来,但是三方插件呢,特别是那些 ui 库,如果没有人做 assets pipeline 兼容的 gem 包,要集成进来这的好累,我后续会就这个问题讨伐 Assets Pipeline!

#14 楼 @scriptfans

提供几个模式:

1. 整站共享的逻辑,例如购物车:

$(document).on 'click', '.cart', ->
  # code here

2. 特定元素存在的时候触发的逻辑,例如 editor:

$(document).on 'page:change', ->
  $('textarea[data-behaviors="editor"]').editor() # a jquery plugin

3. 特定元素由用户触发的逻辑,例如 modal:

$(document).on 'click', '[data-toggle="modal"]', ->
  # toggle modal

4. 某个页面特有的逻辑:

写到页面末尾,用内嵌形式。

<script>
  // code here
</script>

现在不用操心 ready 了,page ready 是 jQuery 带来的思维方式,在 Turbolinks 环境需要另一种思维方式。无论最终是否用 Turbolnks,理解它,就会发现自己对浏览器如何执行 JavaScript 有更深的理解。

如果是前端开发者,应该比我更熟悉 Ember.js,Angular.js 这些框架,在这些框架生成的页面元素上要怎么操作 DOM 呢?Angular.js 的 FAQ 里面说,不要自己操作 DOM(Stop trying to use jQuery to modify the DOM in controllers.),为了打破习惯,还建议移除 jQuery(If you're struggling to break the habit, consider removing jQuery from your app.),这可不止要改变事件绑定的习惯。现在前端开发者可喜欢捣鼓新玩意了,我不担心他们。

#17 楼 @scriptfans 升级系统肯定是会有阵痛的,所以升级之前做好技术调研,估计一下实施的难度和具体都需要做哪些工作还是很有必要的。最起码会有个心理准备,这样做的时候压力就不至于太大,头脑也会比较清晰,减少去做狗急跳墙的事情的几率。

Assets Pipeline 好像也没有阻止你用第三方 ui 库吧?如果发觉不兼容,完全可以将第三方的 ui 库独立使用啊,配置文件修改一下就好了。

#18 楼 @Rei 确实,操作 dom 是上一个时代的思维,这方面我比较关注 web component,以及 react.js

#18 楼 @Rei debug 一下,其实这段代码不符合 dom 幂等操作:

$(document).on 'page:change', ->
  $('textarea[data-behaviors="editor"]').editor() # a jquery plugin

如果这个 plugin 在设计的时候没考虑到避免多次初始化,那么,同样会遇到我说的问题(当然很多 jq 插件是没这个问题的,但情况很多,谁也无法保证别人的代码万无一失),这就是为啥要吐槽的原因,Rails 崇尚最小惊讶,在这点上却非常让人惊讶,“以前的代码完全没问题,现在怎么这样了?”呵呵。

#19 楼 @cuterxy 问题就在于需要开发者做很多额外工作才能集成,这会带来一系列的问题,比如插件版本升级,我后面会对这一点做详细说明的。

#21 楼 @scriptfans 我自己写的 plugin 是幂等的,如果原插件不幂等就加个 wrapper:

$(document).on 'page:change', ->
  $('textarea[data-behaviors="editor"]').each ->
    element = $(this)
    if not element.data('editor')
      element.data 'editor', element.editor()

@zhaozijie @blacktulip @lips @kgen @nightire @Rei @cuterxy 主贴有更新,但是因为急事又没填完,sorry 啊,待会儿继续,哈哈哈……

楼主加油(^_^)/

#25 楼 @hooooopo 哈哈,多谢捧场,接孩子回来了,继续填坑…… 不过我很好奇 ruby china 上首页的推荐算法,为啥俺码这么多字,都上不去,难道就因为黑了 Rails?

#26 楼 @scriptfans 不不不,有可能是缓存

#27 楼 @hooooopo 我感觉是人为干涉的,人工置顶而已

过来看楼主填坑实况工程

#28 楼 @scriptfans 加油写,

第一宗罪 完全赞同,开始还想用模板,后来还是算了,自己写吧,比用模板还快。。。

@huacnlee 来加精华

😄😄😄,填模板确实是感同身受,我填过不止一次,每次都是 改一点 -> 用浏览器打开看看 JS 报不报错 -> 再改一点-> 再打开……

#30 楼 @peter 问题是,自己写不出别人那么好看少错的前端模板来……

@zhaozijie @davidzhu001 @lips @kgen @nightire @Rei @cuterxy @hooooopo @ywjno @peter @blacktulip 坑已填完,多谢捧场。让评论来得更猛烈些吧。 btw,如果按照中学论文的标准,俺这个论点、论据、论证都算不错吧,既提出了问题,也解决了问题,是不是可以在讲台上当做范文来念?嘿嘿,至于精华不精华,班组你自己看着办……

英文书记推荐得不错,Assets Pipeline 我也是很醉的,同 lz 一样买主题。 所以新项目一律前后端分离,rails 只做 api

rm -rf app/views
rm -rf app/assets
rm -rf verdor

# remove gem in Gemfile, 只剩下了
gem 'rails', '4.2.0'
gem 'mysql2'

# application.rb
# require 'rails/all'
# gem which rails/all
require 'active_record/railtie'
require 'action_controller/railtie'
# require 'action_view/railtie'
require 'action_mailer/railtie'
# require 'active_job/railtie'
require 'rails/test_unit/railtie'
# require 'sprockets/railtie'

# remove none useful middleware
config.middleware.delete ActionDispatch::Flash
config.middleware.delete Rack::Sendfile
config.middleware.delete ActionDispatch::Cookies
config.middleware.delete ActionDispatch::Session::CookieStore

    config.generators do |g|
      g.orm             :active_record
      g.template_engine :erb
      # g.test_framework  :test_unit, fixture: false
      # g.test_framework  nil
      g.stylesheets     false
      g.javascripts     false
      g.jbuilder        false
      g.helper          false
    end

#30 楼 @peter 精华恐怕有点难,高级黑嘛

#31 楼 @blacktulip 其实基本的处理流程我已经写清楚了,只要一步一步的来就好。这活儿确实很蛋疼,关键是,你要全盘掌握模板的构造,最郁闷的,就是碰到模板升级的情况,你说是升级还是不升级?慢慢纠结吧……

#35 楼 @scriptfans 黑什么啊,不是精华不上首页。

这质量足够精华了。

#27 楼 @hooooopo 可能是 缓存。我读书少,你别骗我。

@scriptfans 重头再初看一眼发现原来写的内容貌似有更新,我表示再仔细研读一遍学习学习(包括写作风格)(我也是在 06 年左右买了滑板书接触到 rails 的)

:plus1: @scriptfans 楼主可否将在 Rails 中使用第三方的 theme 的经验部分单独成文?我想收藏并学习下。

#34 楼 @flowerwrong 前后端分离,具体解释下? @scriptfans 里面好多书都价格不菲,楼主都买了?

#30 楼 @peter Assets Pipeline 集成组件的问题,并不仅限于使用 theme 才会遇到,只要你用到三方带有资源的前端 plugin,然后它又没有现成的 gem 包,都逃不开。

#31 楼 @blacktulip 不必盲人摸象,只要找到症结所在,其实问题是可以解决的,只是比较 dirty 罢了,就像我说的,怕的是以后升级又要再来一次,而版本升级肯定是注定的。

#40 楼 @springwq 可以,改天另写一篇专门介绍这个,大体的流程其实就是这样了,说实话挺累的,工作量跟作者使用的 plugin 数成正比,另外还跟开源组件的代码质量有关系,如果到处都是 hard code 的资源引用,那就太悲剧了(说的就是你,百度编辑器,大厂出品?呵呵……),其实我最不爽的是,升级维护,或许可以考虑写个 gem 来处理这个事情。

#41 楼 @lips 这么说吧,市面上目前你知道和不知道的 rails 书,我都看过了,除了入门级别的……

DHH 说------我从来不用 windows

#43 楼 @flowerwrong 这跟 windows 有毛关系,总不能让甲方也买 mac 吧

看到长文,果断先赞一下再看

46 楼 已删除

没有第三方资源的 gem 包自己写一个不就行了,值得费那么大劲喷么。开源的就放到 github 上,私有的就放到私有库里面。gem 核心也就是几行代码而已,其余都是 copy paste。

#47 楼 @billy 像 fontawesome 这种简单的当然很容易,搞不搞 gem 真的无所谓。很多时候,现实是残酷的,不说集成 themeforest 的模板(注意看主贴给的例子,第二张截图,assets 目录有多少三方组件,这还算是少的了),请尝试下给百度编辑器做 gem,这个我在项目里面是搞好了,但最大的问题是维护版本,只要人家一有改动,你的 gem 包也要同步的话,是非常悲剧的,特别是这种玩 hard code 资源路径的玩意儿……

脏一次手无所谓,怕的是要脏一辈子。当然如果对干这事儿情有独钟的话,我也无话可说,真爱。

其实如果 Assets Pipeline 在预编译的时候,考虑周全点的话,完全可以处理下资源的路径,自动将相对路径替换为对应的绝对路径(毕竟是可以根据源文件所在的目录来确定的,至少 css 文件可以,js 就有点蛋疼了,如果是字符串拼接,神仙也帮不了),世界会多么美好……

@scriptfans 第一,我不认为第三方升级了你就一定要升级,你的网站又没有出问题,用得好好的,为什么非要赶趟?很多时候出问题就出在升级上面。第二,一定要升级的话,就算维护 gem 再难,也比在应用里面升级各个文件要容易得多。

另外,我是非常反对把第三方的东西直接放到应用里面的,弄得 Git 历史一团糟。

我是没有用过百度编辑器,不知道具体。但你既然觉得这个又难又好用,难道你只希望在这一个应用里面使用?下一个应用怎么办?难道又要重复这个过程?宝贵的时间都花在了处理这些价值很低的事情上面。为什么就不能稍微多费一点时间做一个 gem, 就算是私有的都好过没有。

#50 楼 @billy 不要把宝贵的时间都处理在这种价值很低的事情上,非常同意!所以你知道为啥我要吐槽 Assets Pipeline 了吧。不管你是 dirty hack 也好,做 gem 也好(这只是在 dirty hack 之上多费点包装的功夫,搞个 engins 而已),同样的过程都要来一遍,问题的根源不在于做不做 gem……

assets pipeline 的确是不完美的,但是还挺方便的,用过 gulp 或 grunt 配置 assets pipeline 做的这些事也需要不少代码啊

关于这两坑,我是这么做的:

第一坑 主题的 assets 扔到/public 下去,需要改动的就少了很多 第二坑 如果不知道情况的话,确实很坑,会导致你修好一个地方的样式,另个地方的就坏了,因为它只认前面 4096 个选择器的,当初也是毫无头绪,误打误撞搜到 stackoverflow 上一个帖子才解决的,所以知道这个问题就好办了,自己 precompile 时手动分割下就好了

@dfang 扔 public 当然是爽了,assets pipeline 也失去意义了

我觉得这都不是坑

Rails 里的 concern 也只是一种多重继承的 mixin 的实现方式,自己也可以实现,更多的只是为了代码的复用,并没有实现“加一层”的概念。不过如果项目真的复杂,构建一个 service 层也不是很难。

第一宗罪:增加集成客户端组件的复杂度

theme 升级的时候太痛苦了!

#55 楼 @rubyu2 握爪,升级的时候真的想骂人,要是没有 Kaleidoscope 这种 diff 神器,想死的心都有…… 别跟我说什么你不升级就好,劳资花钱买了 Theme,作者不断的升级,增加新功能新组件,不用简直就是脑袋进水了。 至于 services 层,肯定要自己来 DIY 了,rails 没有内置指导的,这也是对新手非常不好的地方。

作为刚买 ruby 的书看的新手,看了这篇文章都不敢下 rails 的海了。。。。看了底下的讨论。。。。感觉额咬咬牙撑撑就过去了

楼主应该是对 rails 爱恨交加吧,对第一条:rails 没有架构,个人持保留意见。

偶也是从 java 转到 ruby 的,相比于 java 很多架构起步就到引入庞大组件的做法,rails 是化架构于无形,让人可以十分简便高效的建立产品原型,完成早期项目开发。如果项目不能成长起来,早死早超生,这种做法最经济不过。如果项目能够成长起来(理性的看,这是凤毛麟角),为了处理各种复杂性,再引入各种处理复杂问题的方法不迟,比如剥离 service 层,比如微服务化等等。

不过还是觉得文章很赞,让人深入思考 rails 的一些利弊,里面提到很多东西也让人眼界大开,/强

架构 (其实我个人是喜欢看对 rails 的吐槽的,不过很多地方楼主没有找准槽点。) 第一个例子就并不十分准确,准确的说 rails 应该是充分利用了ruby的特性来完成工作,而不是靠引入更多的概念,不过这个谁是谁非很难讲,所以可能会显得在架构上略弱于 java(不过反过来说就是 java 不得不用很多框架来实现语言没有实现的功能)。 充血模型这个梗真的是翻来覆去的说了,解决办法也提出过很多次了,rails 的特点就是很多问题不止一个选择,很难说哪个是best practice,所以其实从简单逻辑->充血,复杂一点->mixin,再复杂一点->services/interactor 这些都是可选的啊,也就是说你如果在 java 里也用充血模型也没关系啊,但是你不能把这个锅推给 rails/java spring 之类的东西。 《迦勒比海盗》里面有一句话:“法典,更像是指南,而不是准则……” 所以说,srp 这种东西只是一种指导性的指南,(我又想起了数据库的范式)。 是否要遵循,完全取决于需求,就像我们很多时候选择 rails 是一种速度/性能的权衡一样。

pipeline (我不用这玩意,真的没办法评价,不过对其中一个例子提点建议) 那个购物车,你说返回 erb 不好处理,返回 json 就是前台的工作,可是事实上,在哪个框架里你都要面临这种场景啊。

说实话,我真觉得楼主的吐槽没有找准槽点啊,很多都是选择怎么做的问题,并没有哪种框架可以把人的选择和工作替代了,只能尽可能的简化。

要说对 rails 吐槽的话,

https://news.ycombinator.com/item?id=9123683

Don't switch stacks. He's expressing an opinion about code from Rails developers which is about as broad as opinions go this side of saying "black people are like X" or "Asians are like Y." On the bright side he does leave the door open that Rails developers could write solid code though most don't in his opinion. The best thing you could do is continue working on Rails but read a variety of opinions on good code in different languages, read Coders at Work and similar texts on coding, and remember not to blindly follow coding trends. Generally keep your code simple and work to find abstractions to fit your domain.
I'm not saying anyone here is incorrect in their generalizations of Rails developers. I've obviously never seen the code they are criticizing. But they also aren't providing any useful information. Keep that in mind above all else. There will always be politics and disagreements. You're never going to make everyone happy and it would be a disaster to try to.

这个评论才是真正找到了 rails 的痛点。

#59 楼 @taojay315 那个例子是针对评论里有大大断言 Ajax 请求不可能也不应该发生 redirect to,而给出的一个简化版场景。在面临 Aja*(html,json,js) 问题的时候,根据不同的情况做出合适的选择才是明智的,比方说这个例子,采用 Ajah 可以少操许多心,比方说,修改购物车商品数量其实是连锁反应:你要更新单项小计,总价,甚至还需要应用某些促销算法(比如商品 A 买满 3 件就送 B 之类的),这种情况是不是直接重定向到 index action,充分利用服务端的能力会更好?为什么非要 render json 给客户端然后将构建 UI 的逻辑在 js 里重复一遍?退一万步,就算真这样,数据来源也是 index 提供的,那么还能觉得 redirect 是不合理的?至于返回 js 片段在里面写每个 dom 的更新操作(也就是 js.erb 那种),我只能说呵呵……

这是一个憋了九年的大槽,鉴定完毕 😄

Rails 这种八面玲珑的大杀器,哪都有他一腿必然也是瑕疵不断。不过从他开始,逐渐摸索到后端前端数据库缓存网络等等方面的设计方法以及积累很多经验后,对这套东西更是敬畏有加。无论菜鸟还是老鸟,试问挖掘机哪家强,必然会想到他

软件架构本质就是把简单事情复杂化。 C 语言纯过程式,不照样能把 os,compiler 这种级别的软件做得好好。软件开发最根本的重点只有一个,就是谁在写代码。

Assets Pipeline 引入第三方组件,连同版本更新都已经能全自动化了好么,一个个文件改路径这种事情显然可以自动化,而且已经有人这么做了……rails-assets.org 现在已经相当成熟了,我某项目用了 40 多个种 gem,只要是能用 bower install 的基本都是引入后不用管的

source 'https://rails-assets.org' do
  gem 'rails-assets-angular'
  gem 'rails-assets-angular-animate'
  gem 'rails-assets-angular-bindonce'
  gem 'rails-assets-angular-cookies'
  gem 'rails-assets-angular-i18n'
  gem 'rails-assets-angular-loading-bar'
  gem 'rails-assets-angular-mocks'
  gem 'rails-assets-angular-resource'
  gem 'rails-assets-angular-route'
  gem 'rails-assets-angular-sanitize'
  gem 'rails-assets-angular-touch'
  gem 'rails-assets-foundation'
  gem 'rails-assets-html5shiv'
 ... balabala
end

顺便发个让 rails-assets.org 重新索引、更新最新版本相关 gem 的脚本

require 'json'
require 'rest_client'
patten           = /gem ['"]rails-assets-([^'"]+)['"]/
rails_assets_api = 'https://rails-assets.org/components.json'
gemfile          = ENV['BUNDLE_GEMFILE'] || 'Gemfile'
assets = File.readlines(gemfile).map! { |l| l.match(patten) }.compact.map! {|l| l[1] }

assets.each do |asset|
  RestClient.post rails_assets_api, component: { name: asset, version: nil }
  sleep 5
end

这么长的文章看完不易,写完更不易!赞了

#60 楼 @scriptfans 我的意思是说 你这个例子在任何语言框架里都会遇到,跟 rails 无关。

Assets Pipeline 是否可以使用 webpack 来完全替代?webpack 有一些特性很棒,譬如智能分析各页面用到的 asset,然后将其共有的部分单独打包。

#53 楼 @blacktulip 整合 theme 无非是为了快速把项目跑起来呗,后来有时间慢慢整理到 pipeline 呗,真看不下去,可以用 gulp 那一套自己压缩合并下 ....

坑好多,先收藏之后再慢慢看。

更喜欢这样来处理图片预编译😄

config.assets.precompile += %w( *.png *.jpg *.jpeg *.gif */*.png */*.jpg */*.jpeg */*.gif)

rais 4.2 中,如果所有脚本都在 header 里,就不存在多次加载对话框的问题了吧?

初学 rails。。。表吓我

作为一个用 rails 来做企业管理系统的开发者,作为一个需要整合各种 theme 到 rails 项目的开发者,我对作者的各种控诉,表示深深的感同身受,一生好基友啊,一把泪,说不清

作为一个菜鸟,看到这种文章很兴奋,因为大多数还没有接触过,只看到吐槽 AR,不过还是认真看完了

楼主好文章,对架构的分析很赞同。实际项目中多次看到自发形成的 service, presenter 之类,但正如楼主说的,这么多路,没一个是官方扶持的 (或者说是主流的),也许才是最大的问题。不是所有项目都能遇到这一个坑,遇到坑自行绕路的又各自在踩坑 :)

#51 楼 @scriptfans 我也不太喜欢 Assets Pipeline 这个赞同。rails 还有一个问题就是部署比较复杂。

#75 楼 @foyoto 建议自己写好一个通用的 bash 脚本

楼主最后分享一条我是感同身受,自己一个小项目,在开发环境一点问题没有,可是,到了生产环境,所有样式以及 js 都不起作用,网页就跟 json 格式一样,蛋都碎了一地,睡觉都睡不着,后来填坑一天,当然了,这些都不重要,广告来了,不管好不好,欢迎大家能去我的小网站光顾 http://dadouya.coding.io

挺多心得的,赞个,有很多问题我也还在思考,楼主给我了很多启发,谢谢!

不明白 redirect_to 为什么会被吐槽,json format 时不能 redirect_to 难道不是任何 web 开发人员都需要遵从的准则吗?应该和 rails 没什么关系。

我在 controllers 里面是这样用的:

# file: app/controllers/share_dirs_controller.rb

# POST /share-dirs
def create
  create_share_dir!

  respond_to do |format|
    format.html { redirect_to files_path }
    format.json { render 'show' }
  end
rescue SubDirAlreadyShared
  render_json code: -1, msg: "..."
rescue DirLimitReached
  render_json code: -1, msg: "..."
rescue DirLimitFileSize
  render_json code: -1, msg: "..."
end

即便是用户认证失败,json 格式也只是 render 一个失败的 json,不会 redirect:

# file: app/controllers/application_controller.rb

# 需要认证登录用户的 action 加上这个 filter
# Usage:
#    before_action :require_user
def require_user
  return true if logined?

  if request.format.json?
    render_json({ code: 10301, msg: "用户登录,或登录信息已过期。" })
  else
    redirect_to signin_url(protocol: "https")
  end
  false
end

#78 楼 @qhwa 多谢捧场:) 为什么我会让 Ajax 请求 redirect?请看 60 楼,有针对这个问题的解释,同时也可以参考《The Rails 4 Way》一书有关 Ajax 的章节。

#79 楼 @leopku 后续我也打算就这个话题继续深挖,但是目前真没时间,忙成狗……期待您的分享:)

#70 楼 @yan32768 根本问题不是没把脚本放 head,而是因为 Turbolinks 的机制要求你的 DOM 操作必须“幂等”。自己写脚本好办,别人(三方组件)写的就蛋疼了,所以现在要集成前端 Theme 的话,不出意外我都会被迫关闭 Turbolinks,实在是折腾不起。

#72 楼 @pzgz 握爪,抱头一起哭吧,哈哈。

#63 楼 @aptx4869 rails-assets 这条路不错,希望能逐渐完善起来,这完全是在帮 Assets Pipeline 填坑啊,哈哈。

#52 楼 @dfang

如果把 theme 扔到 public 中,的确可以省去很多时间,当时很明显的,就用不到 asset pipeline 便利的地方,pipeline 的一个好处就是合并请求,这个在实际使用中,还的确是可以看到效果的,这种情况下,我们还有没有其他的选择呢?不知道 gulp 或者 grunt 是否能够做同样的事情?

楼主写的很实在,支持 @liyijie

好文。 个人感觉 rails 开发确实忽略了很多 OOP, SRP 的原则,编着编着,就觉得很混乱,也会形成惯性,把逻辑都写在 model 里面,也不去思考 model 是否该负责这些功能功能。 没怎么踩过 AP 的坑,基本都是 API 开发,用 rails-api 做开发,AP 去掉,cookie 去掉,爽! 文里提到的书籍都很不错,很想看 modular rails, 可是好贵。。

#80 楼 @scriptfans 你邮箱多少?想请你帮忙,谢谢

#48 楼 @scriptfans

AssetsPipeline 方面给楼主一个建议:对于 hard code 很严重又实在没办法一定要用的东西扔到 public 目录下去。

redirect_to 和 turbolinks: 我们现在用 redirect_via_turbolinks 来做提交完后的重定向,很好很方便

幂等的 js 调用:我觉得这是个好习惯,许多高质量的插件都是幂等的,以后处理页面局部替换也会方便很多(直接全部执行一遍即可)

也试过用 themeforest 的主题,加载大量的 javascript 插件, 但没有改动那些插件,都原样扔到 vendor/assets 里, 部署时用一个 capistrano task 自行复制到 public/assets 里, 以保证http://example.com/img/nav-expand.png对应存在即可。

毕竟,javascript 组件里的图片资源很少会更新。

百度编辑器我也曾用过,也没遇到资源地址的问题, 倒是因为没有上传文件等后端支持,需要一一另写,也挺麻烦的. 后来都用 ckeditor 了。

以百度编辑器为例,把文件放到 vendor/assets/javascripts/ueditor, app/assets里加载一个ueditor.config.js,设置URL='/assets/ueditor', 部署到 production 时复制 vendor/assets/javascripts/ueditor 到 public/assets/ueditor,即可 有新版本随便更新,照此处理,也不算很麻烦。

Pipeline 确实恶心,不过现在 nodejs 里也有这些东西。

turbolinks 确实不够实用,而且会引入一些奇怪的 bug,我们的项目后来逐步都用 pjax 自己实现了一下。

首先必须要赞一个,知识点很全面。然后吐槽一下这个吐槽:其实吐槽点还有很多的,比如你可以说 migration 太麻烦了,直接操作数据库多爽,下面就会有人点赞说“是啊,是啊,贼折腾,果断抛弃好久了”,还可以说路由写起来太麻烦,也会有人跟风来一个“我都全局适配的“,然后贴一个巨牛逼的适配代码。总的来讲,我认为本文就是巨牛逼一个知识贴,外加钓鱼贴

92 楼 已删除
93 楼 已删除

mark 一下,回头细细看

料越来越多,留着慢慢体会。

#66 楼 @afly 最近也在摸索 webpack 配 rails

槽的好彻底,有思考,有希望

想加博主 qq,有问题请教

第二次回来看这个帖子,随着学习的深入,跟第一次读帖的感觉似乎不一样;mark 一下,将来还会回来再读。

好长的帖子。。。看了蛮久,对于刚接触 Rails 的老菜鸟,太多不懂的地方。关注下慢慢看。

新手,想写一个网站,是自己写前端好,还是下载模板往 rails 里塞?表示不会熟练使用 Asset Pipeline😂

相当的深奥,看了好长时间,对于我这个新手来说,需要学习的太多了。

收货匪浅!

vue/react + rails api 走起 😆

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