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

scriptfans · 发布于 2015年03月20日 · 最后由 msl12 回复于 2017年07月18日 · 18664 次阅读
96
本帖已被设为精华帖!

自打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的问题,这样的话,整个世界就清净了。

(完)

共收到 100 条回复
17424

赞一下!

207

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

17696

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

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

370

楼主文采不错,不过……

第一条太牵强了

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

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

楼主说:

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

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

redirect_to 那条是来凑字数的 😄

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

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

14643

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

1573

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

1

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 请求一样响应,没觉得这是个问题。

14643

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

96

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

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

96

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

1

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

13051

干架的节奏

14713

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

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

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

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

96

#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瞎捣乱的。

96

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

14713

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

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

96

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

1

#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.),这可不止要改变事件绑定的习惯。现在前端开发者可喜欢捣鼓新玩意了,我不担心他们。

14713

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

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

96

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

96

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

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

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

96

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

1

#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()
96

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

8

楼主加油(_)/

96

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

8

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

96

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

1342

过来看楼主填坑实况工程

1553

#28楼 @scriptfans 加油写,

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

@huacnlee 来加精华

207

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

207

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

96

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

9442

英文书记推荐得不错,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
96

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

96

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

1553

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

这质量足够精华了。

14643

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

1342

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

4898

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

17696

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

96

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

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

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

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

9442

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

96

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

4584

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

46楼 已删除
11222

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

96

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

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

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

11222

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

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

11222

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

96

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

1748

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

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

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

207

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

9592

我觉得这都不是坑

A87c18

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

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

theme升级的时候太痛苦了!

96

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

17378

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

332

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

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

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

3210

架构 (其实我个人是喜欢看对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的痛点。

96

#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那种),我只能说呵呵……

4277

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

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

2466

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

96

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
15307

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

3210

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

2116

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

1748

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

9618

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

7659

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

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

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

16469

初学rails。。。表吓我

61

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

14137

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

100

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

959

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

68

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

9484

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

130

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

不明白 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
96

#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填坑啊,哈哈。

61

#52楼 @dfang

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

65

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

11340

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

17915

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

96

#48楼 @scriptfans

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

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

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

18047

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

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

18047

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

18047

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

15628

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

4594

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

947

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

92楼 已删除
93楼 已删除
96

mark一下,回头细细看

15615

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

13229

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

20893

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

96

想加博主qq,有问题请教

96

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

96

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

96

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

Fd55ed

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

60a8f6

收货匪浅!

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