通用技术 [新秀群作业]从修复 testerhome(rubychina)网站的一个 bug 学习 ruby&rails on ruby

虎卧荒丘 · 2014年10月15日 · 最后由 占婷 回复于 2015年12月16日 · 1763 次阅读
本帖已被设为精华帖!

前言

对于一个差点脱离前沿技术人,想要学习 ruby,就意味着要放弃熟悉的操作系统 windows,熟悉的 ide-eclipse,更重要的是要从 java 这种重量级编译型语言的编程思想强行转换为 ruby 这种轻量级解释执行语言编程思想。这个过程远比我想象的要难,总是抱着以前以 java 编程思想,通吃 vb,C# 的傲慢,虽然 js 还算可以,但 js 的编程思想与 java 也是很像的,经过这次打击让我意识到我掉队了。但仍有一部分经验可以帮助我入手新的技术,这篇文章一方面是警醒自己,另一方面也希望能将这次的学习过程分享给想要学习一门语言的同学。

正文

至于为什么选择 ruby,中间的选择过程也费了一些时日,主要是在 python 和 ruby 之间比较,放弃 python 的重要理由是 python 的语法令我找不到北,还有那每个包下的init.py 文件,请原谅我思想过于老化,无论如何都接受不了。而 ruby,给我感觉就比较优雅易理解,虽然社区和资源不如 python。过程曲折,就不赘述了。

看清现状

要学习一门新技术,首先要知道自己会什么,新技术涉及到了什么。

linux&shell 脚本 ruby 最好还是在 linux 下学习开发吧,我在 windows 下虽然也搭建成功了,但装了例如 devkit 我也是醉了。
开发工具 工欲善事,必先利器。其实我是被 eclipse 惯的,实在不想像原来学习 java 一样,从文本编辑器开始,编译执行。。。我选择了 rubymine,是 jetbrain 出的,基于 IntelliJ IDEA。其强大自不必说了。

对我来说上面这两条满足就够了,至于编程经验什么的,我认为如果没有编程经验,反而还好一些,这样不会被以前的思想干扰。

看清需要什么之后,就去熟悉他们吧,下载安装 blablabla....

学习方法

万事具备之后,就要考虑应该怎么学习 ruby,是去买本书?找网络教学视频找资料?。。。无论哪种方法,要选择合适自己的。对于有编程经验的人,我建议先去看语法,练上几个小例子,深层次的不需要太多考虑。然后就实践,无论是自己编写一个小程序还是去修改 bug 都可以。没有编程经验的人,这里就不好办了,按照通常的做法,需要学习面向对象编程思想、数据结构、网络通信以及其他一些必备的 IT 技能。当然这里不是说要按部就班的去学,如果对自己有信心可以直接从视频教程、ruby 书籍开始学习。

贴一个 ruby 的视频教程:http://edu.51cto.com/lesson/id-10162.html

  1. 我是先去看语法:http://www.w3cschool.cc/ruby/ruby-tutorial.html
    w3cschool 的语法虽然有一些已经过时了,但不影响了解 ruby,看的过程中就跟着例子上手实践了。花了两天时间~

  2. 接下来就要选择是做一个小程序?还是找个一个现成的去修改代码修改 bug。我选择了后者,原因就是因为在 testerhome 里玩的时候发现了一个 bug。这里请没有框架使用经验的新手不要选择后者,自己先做一些像计算器之类的小程序。因为一般的现成的程序中,会使用一些框架,里面蕴含了框架的思想,如果不理解这个思想是找不到北的。

bug 标题:testerhome 论坛,社区帖子列表的分页数量不正确
bug 描述:进入 “社区” 菜单,下方的分页栏,其页数是 100,但点击 100 页后,里面没有数据。这里的分页数量不是根据实际数据生成的,是错误的。
截图:

修复 bug 过程

要想修复这个 bug,首先要有源码,还要知道论坛的架构。混坛子里的肯定都知道了,源码在@lihuazhang群主的 github 中就有,而论坛是使用的 rubychina 的源码,架构是 rails on ruby.

参考:http://www.cnblogs.com/likeyu/archive/2012/02/25/2367379.html

环境搭建什么的就不赘述了,就从 bug 分析开始吧

  1. 这个 bug 的原因不难想象,程序中计算分页的代码有问题。我们要追到这部分代码,就要先去找页面。那接下来就观察一下工程结构 接下来就是我的初步猜测
  • app 论坛业务所在
  • bin 忽略,二进制对业务无影响
  • config 一些配置
  • db 数据库相关
  • doc 里面是 readme,看一眼有没有有用的,忽略
  • faye_server 不知道是什么,实在找不到的时候才去研究它
  • lib 引用的库,忽略
  • log 日志忽略
  • misc 存放了一些资源,忽略
  • public 里面放了 404.html 等公用的错误页、跳转页什么的,忽略
  • script 就放了 rails 文件,忽略
  • spec 不知道什么,rubymine 将其颜色设置为灰色,看来是没什么影响忽略
  1. 有了上面的分析,那么入手点就找到了,我们关注 app 中的内容,那么就展开看看吧

    从这个目录结构中,如果对 web 框架有所熟悉,一定会立刻找到切入点,views 文件夹,因为视图就是表现层,说白了就是组织 web 页面,展开后观察一下[社区列表]这个页面在哪里

    从中不难看出 topics 就是页面所在了,展开 topics 文件夹,找可能页面去观察代码,最终将页面定位到 index.html.erb。

  2. 问题页面定位到了,我们就开始观察代码吧

    <div class="content">
    <div class="box box_gray">
    <div id="node_info">
      <%= render "topics/node_info", node: @node %>
    </div>
    
    <div class="topics">
      <% if @user3 == 1 %>
        <% cache(@suggest_topics) do %>
          <%= render partial: "topics/topic", collection: @suggest_topics, locals: { suggest: true } %>
        <% end %>
      <% end %>
      <%= render partial: "topics/topic", collection: @topics, locals: { suggest: false } %>
    </div>
    <%= will_paginate @topics, inner_window: 2 %>
    


......

注意这一行` <%= will_paginate @topics, inner_window: 2 %>`,说的很清楚,will_paginate分页。好了,找到了逻辑处理部分的代码,本来按照我老旧的思路,去找will_paginate方法,结果发现这个方法并不是业务逻辑处理,只是一个rails通用方法,那么谁才是业务逻辑处理呢,如果使用过web框架的就会想到mvc,rails也是如此。好,这时候还记得目录结构吧,上面app目录下有一个controllers文件夹,业务处理就在这里了,从中很容易猜到topics_controller.rb。来看下代码
```ruby
# coding: utf-8
class TopicsController < ApplicationController
  load_and_authorize_resource only: [:new, :edit, :create, :update, :destroy,
                                     :favorite, :unfavorite, :follow, :unfollow, :suggest, :unsuggest]
  caches_action :feed, :node_feed, expires_in: 1.hours

  def index
    @suggest_topics = Topic.without_hide_nodes.suggest.limit(3)
    suggest_topic_ids = @suggest_topics.map(&:id)

    @topics = Topic.last_actived.without_hide_nodes.where(:_id.nin => suggest_topic_ids) #去数据库查询有多少帖子
    @topics = @topics.fields_for_list.includes(:user) #这部不太明白,但不影响分析
    @topics = @topics.paginate(page: params[:page], per_page: 15, total_entries: 1500)#这个方法就是计算分页了,可以从参数看出每页15个帖子,但total_entries这个参数却给了一个固定值1500,那么也就是说分100页。。。。bug就在这里

    set_seo_meta '', "#{Setting.app_name}#{t("menu.topics")}"
  end
  ... ...#代码多,就看上面这部分吧

上面代码中的注释就是我对代码的分析。我们继续跟到@topics.paginate方法中,

# coding: utf-8
require 'will_paginate'
require 'will_paginate/collection'

module Mongoid
  module WillPaginate
    extend ActiveSupport::Concern

    def paginate(options = {})
      options = base_options options

      ::WillPaginate::Collection.create(options[:page], options[:per_page]) do |pager| #从这里读到意思是根据传入的分页参数创建一个分页集合然后遍历每页,将每页的帖子展示
        items_count = options[:total_entries] || self.count #这块就需要了解ruby的语法了,||类似于三目运算,意思是若`options[:total_entries]!=nil`则`items_count=options[:total_entries]`,若`options[:total_entries]=nil`,则`items_count=self.count',转换为业务说法就是,若传入的总贴数为nil则计算数据库中查询出的帖子数量,这里显然逻辑错误。无论何时都应当使用数据库中查询出的帖子数量
        fill_pager_with self.skip(options[:offset]).limit(options[:per_page]), items_count, pager
      end
    end
  ......
  1. 问题定位清楚了,就开始想怎么改,已经说过了,无论何时都应当使用数据库中查询出的帖子数量。按照这个思路,我们先以调试模式启动 testerhome 这个 web 应用,启动后就可以访问本地调试环境的 testerhome 了 在调试之前,我们需要取得管理员权限,具体的方法可以查看http://www.cnblogs.com/likeyu/archive/2012/02/25/2367379.html这位同学写的。 我们进入http://localhost:3000/cpanel添加分类和节点,这样才能发帖。 接下来我们就要动代码了,首先我们在app/controllers/topics_controller.rb文件中,将代码修改为如下: ```ruby # coding: utf-8 class TopicsController < ApplicationController load_and_authorize_resource only: [:new, :edit, :create, :update, :destroy, :favorite, :unfavorite, :follow, :unfollow, :suggest, :unsuggest] caches_action :feed, :node_feed, expires_in: 1.hours

def index
@suggest_topics = Topic.without_hide_nodes.suggest.limit(3)
suggest_topic_ids = @suggest_topics.map(&:id)

@topics = Topic.last_actived.without_hide_nodes.where(:_id.nin => suggest_topic_ids) # 去数据库查询有多少帖子
@topics = @topics.fields_for_list.includes(:user) # 这部不太明白,但不影响分析
@topics = @topics.paginate(page: params[:page], per_page: 15)# 去掉 total_entries:1500 这个参数,不能写死帖子总数

set_seo_meta '', "#{Setting.app_name}#{t("menu.topics")}"
end
... ...# 代码多,就看上面这部分吧

同时要将`app/models/mongoid/will_paginate.rb`文件中的代码修改如下:
```ruby
# coding: utf-8
require 'will_paginate'
require 'will_paginate/collection'

module Mongoid
  module WillPaginate
    extend ActiveSupport::Concern

    def paginate(options = {})
      options = base_options options

      ::WillPaginate::Collection.create(options[:page], options[:per_page]) do |pager| #从这里读到意思是根据传入的分页参数创建一个分页集合然后遍历每页,将每页的帖子展示
        items_count = self.count #不需要total_entries参数,我们使用从数据库中查询出的帖子数目
        fill_pager_with self.skip(options[:offset]).limit(options[:per_page]), items_count, pager
      end
    end
  ......

代码修改完成,接着就去浏览器中访问 testerhome,进入社区帖子列表,新建 16 个帖子,因为我们要看出效果,需要至少 2 页。造完数据后,我们可以看到如下结果

从中可以看到当前的分页已经正常了,16 条数据,分了 2 页,同时也试试点击第 2 页,看看是否显示正常。

至此这个 bug 就修复了。

最后

之所以想要修复这个 bug,是因为我们是 testerhome,那么看到 bug 就不能放过,同时也为社区贡献点东西,只是改完这个没法真实测试,这个需要@lihuazhang群主帮忙了,话说我这古董技术,到现在对 git 还不熟。另外,在没有看到 rubychina 那位仁兄写的 rubychina 实践之前,不知道管理员怎么进,就去学了 mongodb,好不容易学会了,去数据库里看到 user 里有一个 admin,但密码是加密的,咋办,,,只好自己注册了一个用户,然后将这个用户的加密密码替换了 admin 的密码,结果发现仍然访问不了后台管理,最后才找到那篇文章,豁然开朗。这次解决问题的过程,其中有很多都是靠着经验猜出来的,对与 rails 框架我还不熟悉,通过这次解决问题已经看清一点论轮廓了。希望能帮助到那些想学习开发的新手,同时也欢迎 ruby 大神给点建议!

最后的最后希望,testerhome 能越来越好~各位 tester 事业生活一帆风顺~好不容易有时间写点东西,又有点忙了,请新秀群不要踢我啊 😱,我要学的东西还有很多,以后有时间写东西一定会写~ 😏

共收到 11 条回复 时间 点赞

太精彩了!!!

分享为人为自己,也是自己得一种总结,赞!

娓娓道来,很是精彩!!!👍

其实这个 bug 是 by design 的。。。 不过你的分析很不错啊。。。

field :body
field :body_html
scope :fields_for_list, -> { without(:body,:body_html) }

在 topic model 里面。

#2 楼 @monkey 没能共享点完整东西,只能共享点小的了

#3 楼 @luis 谬赞了,还有很多需要学习

#4 楼 @lihuazhang 恩 对框架一知半解,光靠猜了。所以不能提交代码,跟随你的提示去研究下

匿名 #4 · 2014年10月16日

虽然不是很懂,但是从头到尾看完了!不明觉厉!

Cool! 分析这块涨大姿势了~orz
论坛刚开时就发现了~豆瓣好多页面也有这个问题,想了一下,是为了显得内容更充实而故意做的~

问题分析就开始看不到 ruby 代码了 ;_; 不过楼主写的很好啊!!! 学习这个精神~

最近也在学 Ruby,赞~(≧▽≦)/~

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