【问题标题】:Rails gem rails3-jquery-autocomplete: How do I query multiple fieldsRails gem rails3-jquery-autocomplete:如何查询多个字段
【发布时间】:2011-04-21 10:50:02
【问题描述】:

我正在使用这里的 rails3-jquery-autocomplete gem:http://github.com/crowdint/rails3-jquery-autocomplete

关于如何查询模型的单个属性的说明很清楚,我可以毫无问题地完成这项工作。

我的Person 模型有两个属性,我想组合和查询它们。它们是first_namelast_name。我想将它们组合成一个名为full_name 的伪属性。目前,我收到此错误:

ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: full_name: SELECT     "people".* FROM       "people"  WHERE     (LOWER(full_name) LIKE 'cla%') ORDER BY  full_name ASC LIMIT 10):

Person模型没有full_name属性,虽然我在Person模型文件中有如下方法:

def full_name
  "#{self.first_name} #{self.last_name}"
end

如何修改 Person 模型文件,以便调用 full_name 查询数据库以匹配 first_namelast_name 的组合?

【问题讨论】:

  • 维护的gem是here

标签: ruby-on-rails activerecord model autocomplete


【解决方案1】:

您的伪属性仅适用于已检索到的记录,但与搜索记录无关。可能最简单的解决方案是命名为named scope,例如:

 scope :search_by_name, lambda { |q|
   (q ? where(["first_name LIKE ? or last_name LIKE ? or concat(first_name, ' ', last_name) like ?", '%'+ q + '%', '%'+ q + '%','%'+ q + '%' ])  : {})
 }

因此,这样的调用:

Person.search_by_name(params[:q]) 

将返回适当的结果集。如果没有传递任何参数,它还将返回所有条目(或者更具体地说,此范围不会添加任何额外内容),使其成为索引操作的简单插入。

【讨论】:

  • 谢谢!感谢您的帮助。
  • 谁能指出如何将其与自动完成 gem 集成? (控制器中 autocomplete 调用的参数应该是什么?在我看来,scopes 选项不适用于需要参数的范围。)
  • 这在您考虑时非常有用;感谢您的提示!
  • @JellicleCat 这个范围技巧使自动完成 gem 部分无用。实际上,只有前端 html/css/js 仍然有用。如果你想将它集成到自动完成,你可以做的是手动创建一个路由和一个返回 Person.search_by_name(params[:term]) 的控制器操作但它更棘手,因为你必须正确地重新格式化响应:返回的 JSON 必须看起来像 [{"id":"541ee2d953746106180e0000","label":"your_display_value","value":"your_value","extra_field":"extra_value"}]
  • @JellicleCat,我终于想出了一些很好的集成。看我的回答。
【解决方案2】:

遗憾的是,上面提到的范围方法对我不起作用。我的解决方案是简单地覆盖 get_autocomplete_items 方法(以前称为 get_items)。

值得一提的是,有一个 MySQL 函数(其他 db 也有,但我们目前讨论的是 MySQL)更适合您使用的连接类型:

def get_autocomplete_items(parameters)
  items = Contact.select("DISTINCT CONCAT_WS(' ', first_name, last_name) AS full_name, first_name, last_name").where(["CONCAT_WS(' ', first_name, last_name) LIKE ?", "%#{parameters[:term]}%"])
end

MySQL 的 CONCAT_WS() 旨在通过某种分隔符将字符串连接在一起,是全名的理想选择。

当我们通过串联的名字和姓氏对数据库的联系人记录进行配对时,这段代码基本上表示返回与用户搜索的任何内容匹配的联系人的“first last”格式的名称。我觉得它比上面的 SQL 语句更好,因为它会在一个语句中匹配第一个 AND/OR 姓氏,而不是三个 OR 语句。

使用此“hn sm”将匹配“John Smith”,因为“hm sm”确实与“John Smith”相似。此外,它还具有返回每个联系人连接的名字和姓氏的额外好处。您可能想要完整的记录。如果是这种情况,请从上面的行中删除 select() 查询。我个人需要用户搜索名称并让自动完成字段返回所有可能的匹配项,而不是记录。

我知道这有点晚了,但我希望它可以帮助别人!

【讨论】:

  • 但是按照这种方法,隐藏的 :id 字段的更新不再起作用:-( "real_project_id" %> {:id => '#real_project_id' } %>
【解决方案3】:

前面的建议的替代方法是在包含 first_name 和 last_name 的表上定义一个 SQL 视图。您的 SQL 代码(在 SQLite 中)可能如下所示:

CREATE VIEW Contacts AS
SELECT user.id, ( user.first_name || ' ' || users.last_name ) AS fullname
FROM persons;

然后为表格定义一个模型:

class Contact < ActiveRecord::Base
    set_table_name "contacts"
end

您现在可以“开箱即用”地使用 rails3-jquery-autocomplete。所以在你的控制器中你会写:

autocomplete :contact, :fullname

在你的视图文件中你可以简单地写:

f.autocomplete_field :contact, autocomplete_contact_fullname_path

我将把路线的配置留给读者作为练习:-)。

【讨论】:

    【解决方案4】:

    这是一个 hack,我非常希望将这个函数包含在 gem 中,但正如倾斜所说,覆盖 get_autocomplete_items 就像他写的那样工作,但它只会从模型列返回 first_name 和 last_name。为了恢复 frank blizzard 要求的功能,您还需要返回行的 id。

    items = Contact.select("DISTINCT CONCAT_WS(' ', first_name, last_name) AS full_name, first_name, last_name, id").where(["CONCAT_WS(' ', first_name, last_name) LIKE ?", "%#{parameters[:term]}%"])
    

    我的答案和倾斜的答案之间的区别在于 id 作为 select 方法的最后一个参数。我知道现在已经很晚了,但我希望它对将来的人有所帮助。

    如果您不需要将搜索字符串与连接字符串进行比较的功能,并且尝试只对三个单独的列进行查询,则可以使用 gem 的这个分支:git://github.com/slash4 /rails3-jquery-autocomplete.git。哦,那个叉子也只能与 ActiveRecord 一起工作,这可能是他们没有拉它的原因。

    【讨论】:

    • 有帮助,谢谢。 PostgreSQL 替代方案:看起来 Postgres 9.1 有 CONCAT_WS 但我在 8.4 上。幸运的是,我的字段不可为空,因此我可以使用 Person.select("first_name, last_name, id").where(["LOWER(first_name || ' ' || last_name) LIKE LOWER(?)", "%#{parameters[:term]}%"])。注意添加LOWER 使其不区分大小写。我不需要SELECT 子句中的连接值,因为就像原始问题一样,我的模型中有一个def full_name,一旦找到记录就会进行连接。我在控制器中只需要autocomplete :person, full_name
    【解决方案5】:

    多字段自动完成的完整实现:​​


    根据我的评论,我集成到 jquery-autocomplete 的解决方案是自定义实现“内部”自动完成。

    1.查询数据库

    如果您使用 ActiveRecord,您可以为您的 named_scope 使用 DGM 的解决方案

    如果您使用的是 Mongoid,则可以改用以下语法:

    scope :by_first_name, ->(regex){ 
        where(:first_name => /#{Regexp.escape(regex)}/i)
    }
    scope :by_last_name, ->(regex){
        where(:last_name => /#{Regexp.escape(regex)}/i)
    }
    scope :by_name, ->(regex){ 
        any_of([by_first_name(regex).selector, by_last_name(regex).selector])
    }
    

    EDIT :如果您想要更强大的自动完成功能,可以处理重音、匹配部分文本等;你应该试试 mongoid 文本索引。感谢the original answer there

    index(first_name: 'text', last_name: 'text')
    
    scope :by_name, ->(str) {
      where(:$text => { :$search => str })
    }
    

    添加rake db:mongoid:create_indexes后不要忘记建立索引

    所以你基本上可以做到User.by_name(something)

    2。在控制器中创建自动完成操作

    因为 jquery-autocomplete 提供的...不会做我们想做的事。

    请注意,您必须将结果转换为 JSON,以便在前端 jquery-autocomplete 中使用。为此,我选择使用 gem ActiveModel::Serializer,但如果您愿意,可以随意使用其他东西,并跳过第 3 步

    在您的控制器中:

    def autocomplete
        @users = User.by_name(params[:term])
        render json: @users, root: false, each_serializer: AutocompleteSerializer
    end
    

    3。重新格式化响应

    您使用 gem activemodel 的序列化程序:

    我提供了 0.9 版本的链接,因为 master 主页不包含完整的文档。

    class AutocompleteSerializer < ActiveModel::Serializer
      attributes :id, :label, :value
    
      def label
        object.name
        # Bonus : if you want a different name instead, you can define an 'autocomplete_name' method here or in your user model and use this implementation
        # object.respond_to?('autocomplete_name') ? object.autocomplete_name : object.name
      end
    
      def value
        object.name
      end
    

    4.为您的自动完成创建路线

    在您的路线中:

    get '/users/autocomplete', to: 'users#autocomplete', as: 'autocomplete_user'
    

    5.在你的观点中玩得开心

    最后在你的视图中你可以使用jquery-rails的默认语法,但是记得改变路径!

    <%= form_tag '' do
      autocomplete_field_tag 'Name', '', autocomplete_user_path, :id_element => "#{your_id}", class: "form-control"
    end %>
    

    RQ :我使用了一些 2 级深度嵌套形式,因此获得正确的 id 元素 your_id 有点棘手。就我而言,我不得不做一些复杂的事情,但很可能对你来说很简单。您可以随时查看生成的 DOM 以检索字段 ID

    【讨论】:

    • 这看起来很漂亮。 :)
    • Slick,@CyrilDD,但正如我的问题所示,我使用的是 ActiveRecord,而不是 Mongoid,所以我需要 DGM 对粘性检票口的回答,即模型实现。
    • 是的,我在第一步“查询数据库”中提到了这一点。其余的,不管你使用 ActiveRecord 还是 Mongoid。我只是为像我这样想知道为什么 DGM 语法在 mongoid 上不起作用的人写的 :)
    • 太好了,我找这个已经好几个月了,因为我想完全自定义我的搜索,例如,如果我想自动完成同一模型上的不同属性
    • 我进行了编辑以展示如何使用 Mongoid 文本索引来使用不区分大小写/重音的自动完成功能。
    【解决方案6】:

    为了使用@Cyril 对rails4-autocomplete 的方法执行不区分大小写的搜索,我对@DGM 提供的命名范围进行了轻微修改

     scope :search_by_name, lambda { |q|
       q.downcase!
       (q ? where(["lower(first_name) LIKE ? or lower(last_name) LIKE ? or concat(lower(first_name), ' ', lower(last_name)) like ?", '%'+ q + '%', '%'+ q + '%','%'+ q + '%' ])  : {})
     }
    

    这会将记录的条目转换为小写,并且在进行比较时还将搜索字符串转换为小写

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-25
      • 1970-01-01
      • 2016-02-04
      • 2012-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-17
      相关资源
      最近更新 更多