为什么部署环境中的Model.find行为与开发环境中的行为不同?

时间:2010-02-06 19:18:23

标签: mysql ruby-on-rails ruby sqlite3-ruby

使用Ruby on Rails结合capistrano和git,我遇到了一个烦人的问题..

我有一个控制器“people”,索引操作看起来像这样:

def index
  @people = Person.find( :conditions => params[:search] )
end

Person表中有一个布尔“is_admin”列。假设某些人是管理员而有些人不是,那么打电话给 http://localhost:3000/people?search[is_admin]=true 应该填充 @people 和一些用户......这是真的当我在开发模式下运行应用程序时,我的本地电脑..

但....当我部署到我的服务器帐户(railsplayground)时,对 http://mydomain.com/people?search[is_admin]=true 的调用无法找到任何匹配的项目。 但是,如果我将 ...?search [is_admin] = true 更改为 ...?search [is_admin] = 1 ,则响应会按预期返回管理员用户。 。

回到我的本地电脑,使用“1”代替“true”失败。

底线是

Person.find( :all, :conditions => { :is_admin => 'true' } )

适用于我的开发环境,

Person.find( :all, :conditions => { :is_admin => 1 } )

在我部署的环境中工作。

这是为什么?我该如何解决?

理想情况下,我想放置以下链接:

link_to( "Administrators", {
  :controller => '/people',
  :action => :index,
  :search => { :is_admin => true }
})

并获取管理员列表:)。

可能值得注意的是我的开发数据库是一个sqlite3文件,而production是一个mysql数据库......

编辑:我理解对用户输入的反对意见,但在这种特殊情况下,这是一个非常小的威胁。此外,我当前的代码在我的开发电脑或我的生产帐户中完美运行,但两者都没有。最简单的解决方案似乎改变了sqlite3保存和交叉布尔值的方式,所以我将我的问题改为“我如何改变sqlite保存布尔值的方式”...如果sqlite会完美地模仿mysql的行为,那么它将有助于我的开发需要完美......

3 个答案:

答案 0 :(得分:3)

问题是您将布尔值作为字符串传递,最终行为取决于活动数据库。这是一个更详细的解释。

当您阅读params[:search]变量时,内容是一个字符串,并且没有类型。这是因为查询字符串无法理解是否

params[:search][:is_admin] = "true"

实际上意味着

params[:search][:is_admin] = "true"
params[:search][:is_admin] = true

同样,当你通过1时,你最终会

params[:search][:is_admin] = "1"

不同
params[:search][:is_admin] = 1

将值传递给查询时,由于未传递布尔值,因此数据库适配器不会转换该值。您的最终查询结果类似于

SELECT * FROM `persons` WHERE `persons.is_admin` = 'true'

SQLite3将boolean存储为t / f字符串。 true存储为't'false存储为'f'。我想它也理解真/假,它会自动翻译你的查询。 相反,MySQL只能理解0/1和true / false,但它失败了。

当您切换行为时,传递1而不是true,导致SQLite失败,因为它无法将字符串"1"转换为't'。相反,MySQL可以和它一样。

在这两种情况下,你做错了。您不应该传递字符串而是传递布尔值。此外,您永远不应该信任用户输入。

我的建议是在喂params[:search]前将Person.find规范化。

答案 1 :(得分:2)

我认为使用sqlite和mysql之间的区别是导致问题的原因。

但允许直接从查询字符串传递的活动记录条件对我来说似乎是一个坏主意,并且可能使您的应用程序对sql注入攻击开放。

答案 2 :(得分:0)

@Simone回答解释了为什么会出现这种行为。要在sqlite3和mysql中获得正确的结果,您应该传递:

Person.find( :all, :conditions => { :is_admin => true } )

如果你只有一个级别的参数(你有params[:search][:something]但没有更深),你可以用这样的条件创建正确的哈希:

my_conditions = Hash.new
params[:search].each do |item, value|
  my_conditions[item.to_sym] = value == 'true' ? true : value == 'false' ? false : value
end

它将遍历所有搜索参数,并将所有'true'更改为true,将'false'更改为false。如果有其他价值,它将保持原样。当然,你可以把任何你想要的逻辑放在这里。如果它变得比您使用ifswitch重写它更复杂。

比你可以:

Person.all(:conditions => my_conditions)