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

发布于 2024-10-16 18:31:46 字数 229 浏览 5 评论 0原文

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

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

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

I'm trying to add "tags" to an Article model in a Rails 3 application.

I'm wondering if there is a gem or plugin that has adds both the "tagging" functionality in the model and also the auto-complete helpers for the views.

I've found acts_as_taggable but I'm not sure if that's what I should be using. Is there something newer? I'm getting results from 2007 when I google acts_as_taggable

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

诗酒趁年少 2024-10-23 18:31:46

acts_as_taggable_onrails3-jquery-autocomplete 可以很好地协同工作,以创建类似标记系统的 SO,请参见下面的示例。我认为 Rails 尚不存在合适的多合一选项。

按照以下步骤完成全部安装:

1 .备份您的 Rails 应用程序!

2.安装 jquery-rails

注意:您可以使用 jquery-rails 但我选择不这样做。

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 生成 actions_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的进行更改评论。

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。基本上,上面的代码将搜索所有标签,而不是将它们限制为“文章”标签。

acts_as_taggable_on and rails3-jquery-autocomplete work nicely together to make a SO like tagging system see example below. I don't think a suitable all in one option exists yet for rails.

Follow these steps to get this all installed:

1 . Backup your rails app!

2 . Install jquery-rails

Note: You can install jQuery UI with jquery-rails but I chose not to.

3 . Download and install jQuery UI

Choose a theme that will compliment your web design (be sure to test the autocomplete demo with the theme you choose, the default theme did not work for me). Download the custom zip and place the [zipfile]/js/jquery-ui-#.#.#.custom.min.js file into your app's /public/javascripts/ folder. place the [zipfile]/css/custom-theme/ folder and all files into your app's public/stylesheets/custom-theme/ folder.

4 . Add the following to your Gemfile and then run "bundle install"

gem 'acts-as-taggable-on'
gem 'rails3-jquery-autocomplete'

5 . From the console run the following commands:

rails generate acts_as_taggable_on:migration
rake db:migrate
rails generate autocomplete:install

Make these changes in your app

Include the necessary javascript and css files in your application layout:

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

Controller Example

EDIT: Made changes based on Seth Pellegrino's comments.

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

Model Example

class Article < ActiveRecord::Base
   acts_as_taggable_on :tags
end

Route.rb

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

View Example

<%= 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 %> 

Note: This example assumes that you are only tagging one model in your entire app and you are only using the default tag type :tags. Basically the code above will search all tags and not limit them to "Article" tags.

坚持沉默 2024-10-23 18:31:46

acts_as_taggable_on_steroids 宝石可能是您最好的选择。我发现许多标记宝石更像是一个“良好的起点”,但随后需要大量的自定义才能获得您想要的结果。

The acts_as_taggable_on_steroids gem is probably your best bet. I've found that many of the tagging gems are more of a "good place to start" but then require a fair amount of customization to get the result you want.

烟酒忠诚 2024-10-23 18:31:46

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


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

安装 Acts-As-Taggable-On gem

  1. 将 actions-as-taggable-on 添加到 Gemfile 中: gem 'acts-as-taggable-on', '~>; 3.5'
  2. 运行 bundle install 来安装它
  3. 生成必要的迁移: rakeacts_as_taggable_on_engine:install:migrations
  4. 使用 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属性,以便将其呈现为逗号分隔的字符串(这就是作用- as-taggable-on 期望在控制器中)。现在,当您提交表单时,标记应该可以使用!如果它不起作用,我建议观看(令人惊叹的)Ryan Bates 的 Railscast 关于标记的剧集

将 select2 集成到您的表单中

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

将 gem 添加到 Gemfile 中:gem 'select2-rails', '~>; 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 属性实际上并不重要。它们只是不能与您的隐藏输入重叠。 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 元素。我们刚刚将其添加到表单中,这就是为什么 - 我们希望能够为所有可标记字段初始化 select2。

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

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

添加 /tags 路由

进入 routes.rb 文件并添加以下内容:resources :tags。您不必为标签添加所有路由,但我这样做是为了可以轻松地使用 CRUD 标签。您也可以简单地执行以下操作: get '/tags' => '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(标记的模型)和可选的上下文(标记的字段)。如果我们有“喜剧”和“阴谋”的流派标签,然后在表单中输入 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"=>"comedy,conspiracy"

并且这就是您使用acts-as-taggable-on 和select2 在Rails 中执行自动完成标记所需的全部内容!

I wrote a blog post about this recently; for the sake of brevity, my method allows you to have (optional) context-filtered tags (e.g. by model and by attribute on the model), whereas @Tim Santeford's solution will get you all tags for a model (not filtered by field). Below is the verbatim post.


I tried out Tim Santeford's solution, but the problem was with the tag results. In his solution, you get all existing tags returned through autocomplete, and not scoped to your models and taggable fields! So, I came up with a solution which is in my opinion much much better; it's automatically extensible to any model you want to tag, it's efficient, and above all else it's very simple. It uses the acts-as-taggable-on gem and the select2 JavaScript library.

Install the Acts-As-Taggable-On gem

  1. Add acts-as-taggable-on to your Gemfile: gem 'acts-as-taggable-on', '~> 3.5'
  2. Run bundle install to install it
  3. Generate the necessary migrations: rake acts_as_taggable_on_engine:install:migrations
  4. Run the migrations with rake db:migrate

Done!

Set up normal tagging in your MVC

Let's say we have a Film model (because I do). Simply add the following two lines to your model:

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

That's it for the model. Now onto the controller. You need to accept the tag lists in your parameters. So I have the following in my FilmsController:

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

    private

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

Notice that the parameter isn't genres like we specified in the model. Don't ask me why, but acts-as-taggable-on expects singular + _list, and this is what is required in the views.

Onto the view layer! I use the SimpleForm gem and the Slim template engine for views, so my form might look a little different than yours if you don't use the gem. But, it's just a normal text input field:

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

You need this input_html attribute with that value set in order to render it as a comma-separated string (which is what acts-as-taggable-on expects in the controller). Tagging should now work when you submit the form! If it doesn't work, I recommend watching (the amazing) Ryan Bates' Railscast episode on tagging.

Integrating select2 into your forms

First off, we need to include the select2 library; you can either include it manually, or use (my preference) the select2-rails gem which packages select2 for the Rails asset pipeline.

Add the gem to your Gemfile: gem 'select2-rails', '~> 4.0', then run bundle install.

Include the JavaScript and CSS in your asset pipeline:

In application.js: //= require select2-full. In application.css: *= require select2.

Now you need to modify your forms a bit to include what select2 expects for tagging. This can seem a bit confusing, but I'll explain everything. Change your previous form input:

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

to:

= 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

We add a hidden input which will act as the real value sent to the controller. Select2 returns an array, where acts-as-taggable-on expects a comma-separated string. The select2 form input is converted to that string when its value changes, and set into the hidden field. We'll get to that soon.

The id and name attributes for the f.input actually don't matter. They just can't overlap with your hidden input. The data hash is really important here. The taggable field allows us to use JavaScript to initialize all select2 inputs in one go, instead of manually initializing by id for each one. The taggable_type field is used to filter tags for your particular model, and the context field is to filter on tags that have been used before in that field. Finally, the collection field simply sets the values appropriately in the input.

The next part is JavaScript. We need to initialize all select2 elements throughout the application. To do this, I simply added the following function to my application.js file, so that it works for every route:

// 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
        }
    });
});

This may look complex, but it's not too difficult. Basically, the $("*[data-taggable='true']") selector just gets every HTML element where we have taggable: true set in the data. We just added that to the form, and this is why - we want to be able to initialize select2 for all taggable fields.

The rest is just AJAX-related code. Essentially, we make an AJAX call to /tags with the parameters name, taggable_type and context. Sound familiar? Those are the data attributes that we set in our form input. When the results are returned, we simply give select2 the name of the tag.

Now you're probably thinking: I dont have a /tags route!. You're right! But you're about to :)

Adding the /tags route

Go into your routes.rb file and add the following: resources :tags. You don't have to add all the routes for tags, but I did so that I could have an easy way to CRUD tags. You could also simply do: get '/tags' => 'tags#index'

That's really the only route we need at the moment. Now that we have the route, we have to create a controller called 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

This is fairly simple. We can send a request to /tags, with the parameters name (the tag text), tags_chosen (the existing selected tags), taggable_type (the model that is tagged), and optional context (the field that is tagged). If we have genre tags for "comedy" and "conspiracy", then type co in our form, the JSON rendered should look something like this:

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

Now in the select2 input, you should see "comedy" and "conspiracy" as auto-completed tag options!

My tags won't save!

There's one last step. We need to set the select2 values into our hidden field that we created earlier.

This code may be different for you depending on how you structure your form, but you essentially want to get the select2 input, convert the array of strings to a CSV string (e.g. ["comedy", "conspiracy"] --> "comedy, conspiracy"), and set that CSV string into the hidden field. That's not too difficult, fortunately.

You can catch the select2 input changed event, or anything else that suits you. It's your choice, but this step must be done to ensure that the Rails controller receives the value correctly. Again, in 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);
});

You should see the following in your controller action once you have successfully converted your values: "genre_list"=>"comedy,conspiracy"

And that's all you need to do autocomplete tags in Rails using acts-as-taggable-on and select2!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文