进行安全的SQL查询

时间:2012-09-10 02:43:24

标签: sql ruby-on-rails

以下代码来自Sinatra应用程序(使用DataMappe),我试图将其转换为Rails 3应用程序。它是Visit类中的一个类方法。

def self.count_by_date_with(identifier,num_of_days)
    visits = repository(:default).adapter.query("SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)")
    dates = (Date.today-num_of_days..Date.today)
    results = {}
    dates.each { |date|
      visits.each { |visit| results[date] = visit.count if visit.date == date }
      results[date] = 0 unless results[date]
    }
    results.sort.reverse    
  end

我的问题在于这部分

 visits = repository(:default).adapter.query("SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)")

Rails(据我所知)没有这个存储库方法,我希望在某种对象上调用查询,例如Visit.find

任何人都可以给我一个暗示如何最好地为Rails应用程序编写这个吗?

我应该

Visit.find_by_sql("SELECT date(created_at) as date, count(*) as count FROM visits where link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)")

2 个答案:

答案 0 :(得分:2)

Model.connection.execute“你的SQL”应该可以帮到你。像

这样的东西
class Visit < Activerecord::Base

   class << self
    def trigger(created_at,identifier,num_of_days) 
    sql =  "SELECT date(created_at) as date, count(*) as count FROM visits where  link_identifier = '#{identifier}' and created_at between CURRENT_DATE-#{num_of_days} and CURRENT_DATE+1 group by date(created_at)"

    connection.execute sql   
    end
   end
  end 

答案 1 :(得分:1)

我知道你已经接受了答案,但是你要求最好的办法来做你在Rails中提出的问题。我提供这个答案是因为Rails不建议将building conditions作为纯查询字符串。

  

将自己的条件构建为纯字符串可能会使您容易受到SQL注入攻击。例如,Client.where("first_name LIKE '%#{params[:first_name]}%'")不安全。

幸运的是,Active Record非常强大,可以构建非常复杂的查询。例如,您可以使用四个方法调用重新创建查询,同时仍然易于阅读和安全。

# will return a hash with the structure
# {"__DATE__" => __COUNT__, ...}
def self.count_by_date_with(identifier, num_of_days)
  where("link_identifier = ?", identifier)
  .where(:created_at => (num_of_days.to_i.days.ago)..(1.day.from_now))
  .group('date(created_at)')
  .count
end

构建Active Record以将Ruby对象转换为有效的SQL选择器和运算符。这么酷的原因是Rails可以将Ruby Range转换为BETWEEN operatorArray转换为IN expression

有关Active Record的详细信息,请查看the guide。它解释了Active Record的功能以及如何使用它。