Как добавить теги с автозаполнением к существующей модели в Rails? - PullRequest
28 голосов
/ 08 февраля 2011

Я пытаюсь добавить "теги" к модели Article в приложении Rails 3.

Мне интересно, есть ли гем или плагин, который добавляет обе функции "тегирования"в модели, а также автозаполнения помощников для представлений.

Я нашел acts_as_taggable, но я не уверен, что это то, что я должен использовать.Есть ли что-то новее?Я получаю результаты с 2007 года, когда я Google google act_as_taggable

Ответы [ 3 ]

53 голосов
/ 15 февраля 2011

acts_as_taggable_on и rails3-jquery-autocomplete прекрасно работают вместе, чтобы создать систему, похожую на SO, как показано ниже Я не думаю, что для рельсов пока есть подходящий вариант «все в одном».

Выполните следующие действия, чтобы установить все это:

1. Сделайте резервную копию вашего приложения rails!

2 Установите jquery-rails

Примечание. Пользовательский интерфейс jQuery можно установить с помощью 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 'rails3-jquery-autocomplete'

5. Из консоли выполните следующие команды:

рельсы генерируют act_as_taggable_on: миграция
рейк дБ: мигрировать
рельсы генерируют автозаполнение: установить

Внесите эти изменения в ваше приложение

Включите необходимые файлы javascript и css в макет приложения:

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

Пример контроллера

РЕДАКТИРОВАТЬ: Внесены изменения на основе комментариев Сета Пеллегрино.

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

Примечание. В этом примере предполагается, что вы помечаете только одну модель во всем приложении и используете только тип тега по умолчанию: теги. В основном код выше будет искать все теги, а не ограничивать их тегами «Статья».

6 голосов
/ 08 февраля 2011

Драгоценный камень act_as_taggable_on_steroids , вероятно, ваш лучший выбор. Я обнаружил, что многие из драгоценных тегов являются «хорошим местом для начала», но затем требуют значительного количества настроек, чтобы получить желаемый результат.

0 голосов
/ 06 февраля 2017

Я недавно написал в блоге об этом;для краткости мой метод позволяет вам иметь (необязательно) context -фильтрованные теги (например, по модели и по атрибуту в модели), тогда как решение @Tim Santeford даст вам все теги для модели(не фильтруется по полю).Ниже приведено дословное сообщение.


Я опробовал Решение Тима Сэнтефорда , но проблема была в результатах тегов.В его решении вы получаете всех существующих тегов, возвращаемых через автозаполнение, а не в пределах ваших моделей и полей тегов !Итак, я нашел решение, которое, на мой взгляд, намного лучше;он автоматически расширяется на любую модель, которую вы хотите пометить, он эффективен, а главное - очень прост.Он использует гем cts-as-taggable-on и библиотеку JavaScript select2 .

Установите гем Acts-As-Taggable-On

  1. Добавьте актов-as-taggable-on в ваш Gemfile: gem 'acts-as-taggable-on', '~> 3.5'
  2. Запустите bundle install для его установки
  3. Создайте необходимые миграции: rake acts_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, как мы указали в модели.Не спрашивайте меня, почему, но действует, как taggable-on ожидает единственное число * _list , и это то, что требуется в представлениях.

Напосмотреть слой!Я использую гем SimpleForm и шаблонизатор Slim для представлений, поэтому моя форма может немного отличаться от вашей, если вы не используете гем.Но это просто обычное поле ввода текста :

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

Вам необходим этот атрибут input_html с этим значением, установленным для того, чтобы отобразить его в виде строки, разделенной запятыми (которая являетсячто действует в качестве контроллера тегов в контроллере).Маркировка теперь должна работать, когда вы отправляете форму!Если это не сработает, я рекомендую посмотреть (потрясающий) эпизод с Райканом Бейтсом в Railscast по тегированию .

Интеграция select2 в ваши формы

Прежде всего, нам нужновключить библиотеку select2;вы можете либо включить его вручную, либо использовать (мой выбор) гем select2-rails , который упаковывает select2 для конвейера ресурсов Rails.

Добавьте гем в свой Gemfile: gem 'select2-rails', '~> 4.0',затем запустите bundle install.

Включите JavaScript и CSS в свой конвейер ресурсов:

В application.js : //= require select2-fullapplication.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 возвращает массив , где действует-как-taggable-on ожидает разделенную запятыми строку .Форма ввода select2 преобразуется в эту строку при изменении ее значения и устанавливается в скрытое поле.Мы скоро к этому вернемся.

Атрибуты id и name для f.input на самом деле не имеют значения.Они просто не могут пересекаться с вашим 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']") просто получает каждый элемент HTML, в котором мы установили taggable: true в данных.Мы просто добавили это в форму, и именно поэтому - мы хотим иметь возможность инициализировать select2 для всех полей taggable .

Остальное - просто код, связанный с AJAX.По сути, мы делаем AJAX-вызов /tags с параметрами name, taggable_type и context.Звучит знакомо?Это атрибуты данных, которые мы устанавливаем при вводе формы.Когда результаты возвращаются, мы просто даем 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 (модель , которая помечена) инеобязательно 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"=>"comedy,conspiracy"

И этовсе, что вам нужно, чтобы делать автозаполнение тегов в Rails, используя акты-как-теги-включения и select2!

...