如何在 Rails 中为现有模型添加自动完成标记?

Posted

技术标签:

【中文标题】如何在 Rails 中为现有模型添加自动完成标记?【英文标题】:How to add tagging with autocomplete to an existing model in Rails? 【发布时间】:2011-06-23 15:47:31 【问题描述】:

我正在尝试向 Rails 3 应用程序中的 Article 模型添加“标签”。

我想知道是否有一个 gem 或插件在模型中添加了“标记”功能以及视图的自动完成助手。

我找到了acts_as_taggable,但我不确定这是否是我应该使用的。有没有更新的东西?当我 googleacts_as_taggable 时,我得到了 2007 年的结果

【问题讨论】:

我实施并测试了我的答案,效果很好!请参阅我的更新答案以及完整的说明和修复。 【参考方案1】:

acts_as_taggable_on 和 rails3-jquery-autocomplete 可以很好地协同工作,形成一个类似 SO 的标记系统,请参见下面的示例。我认为目前还没有适合 Rails 的多合一选项。

按照以下步骤安装:

1 .备份您的 Rails 应用程序! 2.安装jquery-rails

注意:您可以使用jquery-rails 安装 jQuery UI,但我选择不安装。

3 .下载安装jQuery UI

选择一个与您的网页设计相得益彰的主题(请务必使用您选择的主题测试自动完成演示,默认主题不适合我)。下载自定义 zip 并将 [zipfile]/js/jquery-ui-#.#.#.custom.min.js 文件放入应用程序的 /public/javascripts/ 文件夹中。将[zipfile]/css/custom-theme/ 文件夹和所有文件放入您应用的public/stylesheets/custom-theme/ 文件夹中。

4 .将以下内容添加到您的 Gemfile 中,然后运行“bundle install”

gem '充当可标记' gem 'rails3-jquery-autocomplete'

5 .从控制台运行以下命令:

rails 生成acts_as_taggable_on:migration 耙分贝:迁移 rails 生成自动完成:安装

在您的应用中进行这些更改

在您的应用程序布局中包含必要的 javascript 和 css 文件:

<%= stylesheet_link_tag "application", "custom-theme/jquery-ui-1.8.9.custom" %>  
<%= javascript_include_tag :defaults, "jquery-ui-#.#.#.custom.min", "autocomplete-rails" %>

控制器示例

编辑:根据 Seth Pellegrino 的 cmets 进行了更改。

class ArticlesController < Admin::BaseController  
  #autocomplete :tag, :name  <- Old   
  autocomplete :tag, :name, :class_name => 'ActsAsTaggableOn::Tag' # <- New
end

模型示例

class Article < ActiveRecord::Base
   acts_as_taggable_on :tags
end

Route.rb

resources :articles do
  get :autocomplete_tag_name, :on => :collection    
end

查看示例

<%= form_for(@article) do |f| %>
  <%= f.autocomplete_field :tag_list, autocomplete_tag_name_articles_path, :"data-delimiter" => ', ' %> 
  # note tag_list above is a virtual column created by acts_as_taggable_on
<% end %> 

注意:此示例假设您只在整个应用程序中标记一个模型,并且您只使用默认标记类型 :tags。基本上,上面的代码将搜索所有标签,而不是将它们限制为“文章”标签。

【讨论】:

@deb - 通过查看源 html 确认包含 jquery 和 jquery ui、autocomplete-rails js 文件。此外,请确保还包含 ui css 文件。你是用 Firebug 来调试的吗? @deb - 太好了!我认为这个解决方案更复杂,任何一个使用者都会喜欢。但是我认为这两个插件都已经很成熟并且本身就很好。如果我将来找到更优雅的解决方案,我会尝试在这里发布。 @TimSanteford 当acts_as_taggable_on 为namespaced 时,上面的解决方案似乎失效了。更改自动完成行以显式指定标记类(例如autocomplete :tag, :name, :class_name =&gt; 'ActsAsTaggableOn::Tag')使所有内容重新启动并运行。 为什么第一项是“备份您的 rails 应用程序”?当然,您的 rails 应用程序已经在版本控制下,因此您不需要备份它,并且您已经对所有更改进行了版本控制? 我知道这个答案有点老了,但知道 rails3-jquery-autocomplete gem 是否可以与 jquery-ui-rails gem 一起使用 (github.com/joliss/jquery-ui-rails)?【参考方案2】:

acts_as_taggable_on_steroids gem 可能是您最好的选择。我发现许多标记 gem 更像是一个“开始的好地方”,但需要进行大量自定义才能获得所需的结果。

【讨论】:

谢谢,我会看看可标记的类固醇。 Rails gems/plugins 的名字肯定很有趣【参考方案3】:

我最近为此写了blog post;为简洁起见,我的方法允许您拥有(可选的)context 过滤标签(例如,按模型和模型上的属性),而 @Tim Santeford 的解决方案将为您提供所有标签模型(未按字段过滤)。以下是逐字帖。


我尝试了Tim Santeford's solution,但问题出在标签结果上。在他的解决方案中,您会通过自动完成功能返回所有个现有标签,而不是限制在您的模型和可标记字段中!所以,我想出了一个在我看来要好得多的解决方案;它可以自动扩展到您想要标记的任何模型,它很高效,最重要的是它非常简单。它使用 acts-as-taggable-on gem 和 select2 JavaScript 库。

安装 Acts-As-Taggable-On gem

    将作为可标记的行为添加到您的 Gemfile:gem 'acts-as-taggable-on', '~&gt; 3.5' 运行bundle install进行安装 生成必要的迁移:rake acts_as_taggable_on_engine:install:migrations 使用rake db:migrate 运行迁移

完成!

在 MVC 中设置常规标记

假设我们有一个Film 模型(因为我有)。只需将以下两行添加到您的模型中:

class Film < ActiveRecord::Base
    acts_as_taggable
    acts_as_taggable_on :genres
end

模型就是这样。现在进入控制器。您需要接受参数中的标签列表。所以我的FilmsController 中有以下内容:

class FilmsController < ApplicationController
    def index
        ...
    end
    ...

    private

    def films_params
        params[:film].permit(..., :genre_list)
    end
end

请注意,参数不是我们在模型中指定的genres。不要问我为什么,但是acts-as-taggable-on 需要 singular + _list,而这正是视图中所要求的。

到视图层!我使用SimpleForm gem 和Slim 模板引擎来查看视图,因此如果您不使用 gem,我的表单可能看起来与您的有所不同。但是,它只是一个普通的文本输入字段:

= f.input :genre_list, input_html: value: @film.genre_list.to_s

您需要设置了该值的 input_html 属性,以便将其呈现为逗号分隔的字符串(这是控制器中预期的可标记行为)。当您提交表单时,标记现在应该可以工作了!如果还不行,我推荐观看(精彩)Ryan Bates' Railscast episode on tagging。

将 select2 集成到您的表单中

首先,我们需要包含 select2 库;您可以手动包含它,也可以使用(我的偏好)select2-rails gem,它为 Rails 资产管道打包 select2。

将 gem 添加到您的 Gemfile:gem 'select2-rails', '~&gt; 4.0',然后运行 ​​bundle install

在您的资产管道中包含 JavaScript 和 CSS:

application.js 中://= require select2-full。在 application.css 中:*= require select2.

现在您需要稍微修改一下您的表单以包含 select2 对标记的期望。这似乎有点令人困惑,但我会解释一切。更改您之前的表单输入:

= f.input :genre_list, input_html: value: @film.genre_list.to_s

到:

= f.hidden_field :genre_list, value: @film.genre_list.to_s
= f.input :genre_list,
    input_html:  id: "genre_list_select2",
                name: "genre_list_select2",
                multiple: true,
                data:  taggable: true, taggable_type: "Film", context: "genres"  ,
    collection: @film.genre_list

我们添加一个 隐藏 输入,它将作为发送到控制器的真实值。 Select2 返回一个数组,其中acts-as-taggable-on 需要一个逗号分隔的字符串。 select2 表单输入在其值更改时转换为该字符串,并设置为隐藏字段。我们很快就会解决这个问题。

f.inputidname 属性实际上并不重要。它们不能与您的 hidden 输入重叠。 data 哈希在这里非常重要。 taggable 字段允许我们使用 JavaScript 一次性初始化所有 select2 输入,而不是通过 id 手动初始化每个输入。 taggable_type 字段用于过滤特定型号的标签,context 字段用于过滤之前在该字段中使用过的标签。最后,collection 字段只是在输入中适当地设置值。

下一部分是 JavaScript。我们需要在整个应用程序中初始化所有 select2 元素。为此,我只需将以下函数添加到我的 application.js 文件中,以便它适用于每条路线:

// Initialize all acts-as-taggable-on + select2 tag inputs
$("*[data-taggable='true']").each(function() 
    console.log("Taggable: " + $(this).attr('id') + "; initializing select2");
    $(this).select2(
        tags: true,
        theme: "bootstrap",
        width: "100%",
        tokenSeparators: [','],
        minimumInputLength: 2,
        ajax: 
            url: "/tags",
            dataType: 'json',
            delay: 100,
            data: function (params) 
                console.log("Using AJAX to get tags...");
                console.log("Tag name: " + params.term);
                console.log("Existing tags: " + $(this).val());
                console.log("Taggable type: " + $(this).data("taggable-type"));
                console.log("Tag context: " + $(this).data("context"));
                return 
                    name: params.term,
                    tags_chosen: $(this).val(),
                    taggable_type: $(this).data("taggable-type"),
                    context: $(this).data("context"),
                    page: params.page
                
            ,
            processResults: function (data, params) 
                console.log("Got tags from AJAX: " + JSON.stringify(data, null, '\t'));
                params.page = params.page || 1;

                return 
                    results: $.map(data, function (item) 
                        return 
                            text: item.name,
                            // id has to be the tag name, because acts_as_taggable expects it!
                            id: item.name
                        
                    )
                ;
            ,
            cache: true
        
    );
);

这可能看起来很复杂,但并不难。基本上,$("*[data-taggable='true']") 选择器只会获取我们在数据中设置了taggable: true 的每个 HTML 元素。我们刚刚将它添加到表单中,这就是为什么 - 我们希望能够为所有 taggable 字段初始化 select2。

剩下的只是与 AJAX 相关的代码。本质上,我们使用参数nametaggable_typecontext/tags 进行AJAX 调用。听起来有点熟?这些是我们在表单输入中设置的数据属性。当返回结果时,我们只需给 select2 标签的名称。

现在你可能在想:我没有/tags 路由!。你说得对!但你即将:)

添加 /tags 路由

进入您的routes.rb 文件并添加以下内容:resources :tags。您不必为标签添加 all 路线,但我这样做是为了让我可以轻松地添加 CRUD 标签。你也可以这样做:get '/tags' =&gt; 'tags#index'

这确实是我们目前需要的唯一路线。现在我们有了路由,我们必须创建一个名为 TagsController 的控制器:

class TagsController < ApplicationController
    def index
        @tags = ActsAsTaggableOn::Tag
                .where("name ILIKE ?", "%#params[:name]%")
                .where.not(name: params[:tags_chosen])
                .includes(:taggings)
                .where(taggings: taggable_type: params[:taggable_type])
        @tags = @tags.where(taggings: context: params[:context] ) if params[:context]
        @tags.order!(name: :asc)
        render json: @tags
    end
end

这很简单。我们可以向/tags 发送请求,参数为name(标签文本)、tags_chosen(现有选定标签)、taggable_type(被标记的模型),和可选的context(被标记的字段)。如果我们有“喜剧”和“阴谋”的类型标签,然后在我们的表单中输入 co,呈现的 JSON 应该是这样的:

[
    
        "id": 12,
        "name": "comedy",
        "taggings_count": 1
    ,
    
        "id": 11,
        "name": "conspiracy",
        "taggings_count": 1
    
]

现在在 select2 输入中,您应该看到“喜剧”和“阴谋”作为自动完成的标签选项!

我的标签不会保存!

还有最后一步。我们需要将 select2 值设置到我们之前创建的 hidden 字段中。

此代码对您而言可能会有所不同,具体取决于您构建表单的方式,但您基本上希望获取 select2 输入,将字符串数组转换为 CSV 字符串(例如 ["comedy", "conspiracy"] --> "comedy, conspiracy"),并将该 CSV 字符串设置为隐藏字段。幸运的是,这并不太难。

您可以捕获 select2 输入更改事件,或任何其他适合您的事件。这是您的选择,但必须执行此步骤以确保 Rails 控制器正确接收该值。同样,在 application.js 中:

/*
* When any taggable input changes, get the value from the select2 input and
* convert it to a comma-separated string. Assign this value to the nearest hidden
* input, which is the input for the acts-on-taggable field. Select2 submits an array,
* but acts-as-taggable-on expects a CSV string; it is why this conversion exists.
*/
$(document).on('select2:select select2:unselect', "*[data-taggable='true']", function() 

    var taggable_id = $(this).attr('id')
    // genre_list_select2 --> genre_list
    var hidden_id = taggable_id.replace("_select2", "");
    // film_*genre_list* ($= jQuery selectors ends with)
    var hidden = $("[id$=" + hidden_id + "]")
    // Select2 either has elements selected or it doesn't, in which case use []
    var joined = ($(this).val() || []).join(",");
    hidden.val(joined);
);

成功转换值后,您应该在控制器操作中看到以下内容:"genre_list"=&gt;"comedy,conspiracy"

这就是您在 Rails 中使用acts-as-taggable-on 和 select2 自动完成标签所需要做的一切!

【讨论】:

非常有趣,我正在尝试让它工作,但标签控制器会抛出语法错误:“意外的 '.',期待 kEND .where”。为什么会这样?标记工作,但自动完成还没有!在一行上压缩它时,我得到:“参数数量错误(0 代表 1)” @SEJU 您实际上可能有语法错误。你能把你的代码贴在Pastebin或其他东西上并链接吗? 我把它放在pastebin.com/6tnWqHVy上。由于标记工作,但自动完成没有,我怀疑标签控制器可能是问题。 @SEJU 由于您使用的是 Rails 3,因此您似乎不能使用 .where.not 进行否定查询...您必须将其更改为类似于 in this answer。玩弄那个特定的语法...我从来没有使用过 Rails 3,所以我不知道如何更改它的精确示例,但是您肯定必须更改 .where.not 子句!如果你喜欢我的回答,请给我 +1 ;) 怎么样:“.where(:name != params[:tags_chosen])”,这样页面加载,但我得到一个无效的 mysql 语句。 pastebin.com/deCW0FsL

以上是关于如何在 Rails 中为现有模型添加自动完成标记?的主要内容,如果未能解决你的问题,请参考以下文章

如何在rails 5中为现有模型添加外键

如何在 Rails 应用程序中为我的用户添加角色?

Rails jQuery 自动完成和动态嵌套字段

Rails 4 在模型中添加新列或字段

如何在 Rails 中为 collection_select 设置 HTML 选项?

如何在 Rails 3 中为社交网络应用程序实现友谊模型?