如何正确使用find_by_sql?

时间:2017-02-21 13:30:21

标签: ruby-on-rails

我有以下型号

class Backup < ActiveRecord::Base
  belongs_to :component
  belongs_to :backup_medium

  def self.search(value)
    join_tables = "backups, components, backup_media"
    joins = "backups.backup_medium_id = backup_media.id and components.id = backups.component_id"
    c = Backup.find_by_sql "select * from #{join_tables} where components.name like '%#{value}%' and #{joins}"
    b = Backup.find_by_sql "select * from #{join_tables} where backup_media.name  like '%#{value}%' and #{joins}"
    c.count > 0 ? c : b
  end
end

在pry中,当我运行Backup.all.class时,我得到了

=> Backup::ActiveRecord_Relation

但是当我运行Backup.search('xxx')。class时,我得到了

=> Array

由于搜索应该返回所有的子集,我想我需要返回一个Active Record_Relation。我错过了什么?

3 个答案:

答案 0 :(得分:1)

find_by_sql返回一个对象数组,而不是Relation。如果要返回关系以保持一致性,请尝试重写搜索以使用ActiveRecord api:

  def self.search(value)
    query = Backup.includes(:component, :backup_medium)
    by_component_name = query.where("components.name like ?", "'%#{value}%'")
    by_media_name = query.where("backup_media.name like ?", "'%#{value}%'")
    by_component_name.any? ? by_component_name : by_media_name
  end

或者,如果您仍想使用sql,可以尝试获取记录ID,然后再进行第二次查询:

def self.search(value)
    # ...
    c = Backup.find_by_sql "select id from #{join_tables} where components.name like '%#{value}%' and #{joins}"
    b = Backup.find_by_sql "select id from #{join_tables} where backup_media.name  like '%#{value}%' and #{joins}"
    ids = c.count > 0 ? c : b
    Backup.where(id: ids)
  end

答案 1 :(得分:1)

来自文档:

  

对您的数据库执行自定义SQL查询并返回所有   结果。结果将作为包含列的数组返回   请求封装为您调用此方法的模型的属性   从。如果您调用Product.find_by_sql,那么结果将是   在具有您在中指定的属性的Product对象中返回   SQL查询。

因此,您将获得一组备份实例。

请注意,您可能不应该这样做。在查询中使用字符串插值可以让您轻松获得SQL注入攻击并获得任何收益。此外,使用ActiveRecord示波器可以获得更大的灵活性。

def self.my_includes
  includes(:components, :backup_media)
end

def self.by_component_name(name)
  media_includes.where("components.name like ?", "'%#{name}%'")
end

def self.by_media_name(name)
  media_includes.where("backup_media.name  like ?", "'%#{value}%'")
end

def self.search(name)
  by_component(name).any? ? by_component_name : by_media_name
end

然后你可以打电话

Backup.search(name)

以及

Backup.by_component_name(name)

Backup.by_media_name(name)

答案 2 :(得分:0)

所以我无法获得media_includes的语法,但是受到您的解决方案的启发,我已经成功使用了连接。

我创建了一个小型演示项目,它只显示与搜索相关的代码。你可以看看https://github.com/pamh09/rails-search-demo。如果您想要在解决方案上进行协作,我认为这比尝试粘贴所有代码更有效。也就是说,如果你不打扰,我确实有一个可行的解决方案。但我想看看正确的语法是什么。

以下是型号代码。很可能我只是遇到某种语法不匹配,因为我不太熟悉rails如何实现数据库魔术(显然)。

class Backup < ApplicationRecord
    belongs_to :component
    belongs_to :backup_medium


#---- code below does not work ---
# in pry
# pry(Backup):1> by_media('bak').any?
# (0.0ms) SELECT COUNT(*) FROM "backups" WHERE (backup_media = 'bak')
# ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: backup_media.name:  SELECT COUNT(*) FROM "backups" WHERE (backup_media.name = 'bak')

def self.my_includes
      includes(:component, :backup_medium)
    end

    def self.by_component(name)
      my_includes.where("components.name = ?", name)
    end

    def self.by_media(name)
      my_includes.where("backup_media.name  = ?", name)
    end

    def self.search_by(name)
      by_component(name).any? ? by_component_name : by_media_name
    end

# ----- code below works ... call search('string') -----
# I was unable to get the like query to work without using #{name}

    def self.by_component_like(name)
  # Note: joins (singular).where (plural.column ...)
      joins(:component).where("components.name like '%#{name}%'")
    end

    def self.by_media_like(name)
      joins(:backup_medium).where("backup_media.name like '%#{name}%'")
    end

    def self.search(name)
      by_component_like(name).any? ? by_component_like(name) : by_media_like(name)
    end

end

并且,如代码中所述。我想不通你怎么用?与LIKE一样,查询将像LIKE&#39;%&#39; xxx&#39;%&#39;而不是&#39;%xxx%&#39;。