新手问题 (解决了问题、原理未搞清楚) 一个无比奇怪的错误,调试了 2 小时,完全不知道咋回事

Catherine · 2016年04月30日 · 最后由 catherine 回复于 2016年06月27日 · 3167 次阅读

项目地址: https://github.com/CoolDrinELiu/graduation.git 花了一晚上加一早晨调试,最终解决了问题,但没有搞清楚到底原因是什么,有兴趣克隆下来看看吧。整个后台就 2 个模块,内容并不多 实在不想继续深究问题的根源了...1 是马上要答辩没多的时间,2 是已经累觉不爱了....

  1. 注释 manage/application_controller.rb的 before_filter :require_login
  2. 登录 root_path/manage
  3. git reset --hard 4508d0bd64fc336dc5ac42115c0d91f419136d1e 回滚到发布这个问题时的版本

出错是在调用 Micropost 对象时,进入_where 方法会 clone 自己,就是克隆自己的时候出错(还有个层完全一样的 user model,运行到这 clone 自己就不会有任何问题)

解决方法是将:_where 方法的 clone 换成 self。 _where 方法的全部内容在 11 楼 - 传送门 -

def _where params
  relation = clone
  ...
  ...
  ...

原因:不知道

先描述下现象: 建了一个空的 microposts_controller.rb,继承 patters_controller,里面定义的是后台可以规整在一起的 index edit update 等方法。patters_controller 继承 application_controller。里面有些常用方法。

在任何一个项目文件中直接按保存,不做任何改动,直接保存(什么也没改动),报错。但这时直接再刷新,一切正常。 完整的报错信息是这样,

ActiveRecord::StatementInvalid in Manage::Microposts#index
Showing /home/ljy/micro/app/views/manage/microposts/index.html.slim where line #2 raised:

SQLite3::SQLException: no such table: : SELECT "".* FROM ""  ORDER BY "microposts"."created_at" DESC, created_at desc
Extracted source (around line #2):
1 = content_for :tbody do
2  - @microposts.each do |entry|
3    tr
4     td = entry.id
5     td = entry.user.email
6

这个表是有的,死活不知道为什么给这个报错。

问题的定位

大概定位了问题出在那个 default 方法上,当我随便保存一下(第一次打开 microposts 的 index 页面肯定会报错)。打在 default 方法里的断点进去后,如果我在终端输入一个 self 后按回车,所有断点直接 exit 退出去,一切正常。也就是说,不知道为什么 default 方法没有被完整执行???实在不知道为什么了。

我在https://ruby-china.org/topics/29674 里也遇到过类似的问题,出错特征都差不多。不知道是哪里关键字冲突,上一次是换了一个 namespace,就没有出现类似问题,我也就略过问题的根源了。这次居然又出现(崩溃)

我整个 manage 域下就 2 个资源,users 页面完全正常,没有任何问题。也没有其他方法、类名、字段啥的叫 manage 求助各位朋友...

路由:

namespace :manage do
  root 'homes#index'
  resources :homes

  resources :users
  resources :microposts
end

view/manage/microposts/index.html.slim 就是报错的地方第 2 行的@microposts

= content_for :tbody do
  - @microposts.each do |entry|
    tr
      td = entry.id
      td = entry.user.email

/controller/patters_controller.rb

class Manage::PatternsController < Manage::ApplicationController #:nodoc:

  def index
    render text: '', layout: true and return if controller_name.match(/application/)
    records = model.default(params)
    instance_variable_set "@#{controller_name}", records
  end

/controller/microposts_controller.rb

class Manage::MicropostsController < Manage::PatternsController

end

.default 方法

def default(params, options = {})
        self
        ._where(params[:where])
        .order(params[:order]||"created_at desc")
end

_where 方法是一个查询,方法有点长,不是它里免出错的,第一次刷新页面 params[:where] 里都是空的,不会有影响。

在调用 default 方法前打断点,执行 model.default,两次结果不一样,第一次报错,也就是页面上看到的错,第二次就正常了,为什么会出现这种情况?

def model
  name = self.class.name
  return if name =~ /ApplicationController$/
  @model ||= name.remove(/^Manage|Controller$/).singularize.constantize
end
 model.default(params)
   Load (1.0ms)  SELECT "".* FROM ""  ORDER BY "microposts"."created_at" DESC, created_at desc
SQLite3::SQLException: no such table: : SELECT "".* FROM ""  ORDER BY "microposts"."created_at" DESC, created_at desc
=> #<#<Class:#<Micropost::ActiveRecord_Relation:0x007f7dccd10050>>:0x3fbee6688028>
[2] pry(#<Manage::MicropostsController>)> model.default(params)
   Load (0.4ms)  SELECT "microposts".* FROM "microposts"  ORDER BY "microposts"."created_at" DESC, created_at desc
=> [#<#<Class:0x007f7dccc85680>:0x007f7dccbfcc68
  id: 11,
  content: "saddas",
  user_id: 18,
  created_at: Sat, 30 Apr 2016 14:27:35 UTC +00:00,
  updated_at: Sat, 30 Apr 2016 14:27:35 UTC +00:00,
  picture: nil>,
 #<#<Class:0x007f7dccc85680>:0x007f7dccbfcb00
  id: 10,
  content: "dasdsa",
  user_id: 18,
  created_at: Sat, 30 Apr 2016 13:55:33 UTC +00:00,
  updated_at: Sat, 30 Apr 2016 13:55:33 UTC +00:00,
  picture: nil>]

注意你第一次出错的时候提示输出的 SQL 语句

SELECT "".* FROM ""  ORDER BY "microposts"."created_at" DESC, created_at desc

from 后面跟的是空字符,也就是没有指定 table。

所以我猜测是你 model 方法的问题。

而且你能解释一下

def default(params, options = {})
        ._where(params[:where])
        .order(params[:order]||"created_at desc")
end

这个方法里面,你直接通过.method 来调用方法,这个执行主体是哪一个呢?我看的代码不多,没有见过这样的用法,能否讲解一下?

def default(params, options = {})
        ._where(params[:where])
        .order(params[:order]||"created_at desc")
end

这段代码,._where这种用法没见过,是否可改成_whereorself._where. 我尝试使用.where在 model 类里面使用,报语法错误,我想可能是不是这个原因呢。

#4 楼 @rikiwong 可以,结果还是一样的,是我昨天调试的时候忘记改回去了,应该是 self._where 这样

#3 楼 @redemption 可能是之前调试的时候改过没注意...执行主体就是 self,是 self._where,我把原文改过来了

@catherine 你在 default 方法里面输出 self。看执行他的是什么。因为出错信息里面已经明确看出来是没有给出表格的名字,所以你先看你执行查询方法的主体是什么。

#7 楼 @redemption 问题就在这,当项目随便保存一个空格(不改代码)在 default 的第一句断点里,调试输入一下 self,程序没有任何报错。但断点里如果不输入 self,那么直接 exit,就会报上面的错。

#7 楼 @redemption 就是两种情况下输出 self,都是 Micropost 对象。唯一区别就是上面提到的

@catherine 能否把实验过程的信息发出来。把 model 的输出结果和 default 里面的输出信息都发出来看一下

#10 楼 @redemption 在报错的时候输出 model,直接说没有这个变量。。项目在这里。

发现问题好像出在这,这是_where 方法,是 default 方法调用的它

def _where params
        relation = clone
        case
          when params.is_a?(String); return relation
          when params.is_a?(Array); return relation
          when params.is_a?(Hash)
            params = params.map do |field, condition|
              condition = case
                when condition.is_a?(Hash); condition
                when condition.is_a?(Range); { '>=' => condition.begin, '<=' => condition.end }
                when condition.is_a?(Array); { 'in' => condition }
                else; { '=' => condition }
              end
              condition.each do |operator, value|
                { %[''] => '', %[""] => '', 'true' => true, 'false' => false, 'nil' => nil, 'null' => nil }.each{|x,y| value = y if value == x }
                operator = operator.to_s.downcase
                operator = { 'eq' => '=', 'lt' => '<', 'gt' => '>', 'gteq' => '>=', 'lteq' => '<=', 'noteq' => '!=' }[operator] || operator
                # operator = { '<' => 'lt', '>' => "gt", '>=' => 'gtep', '<=' => 'lteq', "!=" => 'noteq'}[operator.to_s] || operator

                next if 'active' == field

                operator = { '=' => 'is', '!=' => 'is not' }[operator] if value === nil
                # raise unless field.to_s =~ /^(?:[`'"]?(\w+)[`'"]?\.)?[`'"]?(\w+)[`'"]?$/ && (%w[= > < >= <= != in like is]+['is not']).include?(operator)
                if operator == 'like'
                  relation = relation.where("#{field.to_sym} like ?", "%#{value}%") if value.to_s.present?
                elsif ["=", "<", ">", ">=", "<=", "!="].include?(operator)
                  # relation = relation.where(field.to_sym.send(operator) => value) if value.present?
                  p field
                  relation = relation.where("#{field} #{operator} ?", value) if value.to_s.present?
                else
                  relation = relation.where(field.to_sym => value) if value.present?
                # end
                end
              end
            end
        end
        relation || clone
      end

在 Micropost 里没有办法 clone 自己,所以报错,但在同样的 user 下就没有问题。。

@catherine 既然有方向了,你就慢慢去调试吧,我对你的场景也不了解。你更了解你的代码在做什么。我只是帮你找个方向而已,剩下的就自己努力吧。

#11 楼 @redemption 谢谢了。把 clone 换成 self 就可以了。但还是不清楚为啥 Micropost 里不能 clone 自己而 User 可以..

@catherine 共同学习,我也突然才反应过来,那个 sql 语句提示的是 'order' 这个方法没有成功,应该一早就从 order 这里去开始调试的。可以节省很多时间的。还是不够敏感。

#14 楼 @redemption 不是 order 没有调用成功,是调用 order 的主体,以及它主体的主体没有成功,就是第一句 relation = clone,在 micropost 中无法克隆自己,我也不知道为什么。

我知道,但是从错误提示来看,一开始就可以定义到 order,然后就可以跟踪到调用主体出问题了。你觉得你要继续探究,就去看你 User 执行 _where 和 micropost 执行 _where 具体执行过程中关键输出是什么。我觉得他们执行的逻辑在里面是不一样的,不过这是直观感觉,没有仔细分析代码,你可以试着去看看

#16 楼 @redemption 执行逻辑是一样的,可能有些细小的差别或者什么机制影响的。2 个对象的调用都是通过同一个 patters_controller 的 index 进入 base_model 里的 default 的。不想深究了....一想着 随便加个空格,程序第一次就报错,之后就正常的现象就够恶心了...

好像没有遵循 Convention

Catherine 探秘模块混入 (include Module) 背后的故事 提及了此话题。 06月27日 20:24
需要 登录 后方可回复, 如果你还没有账号请 注册新账号