Как добавить теги с автозаполнением к существующей модели в Rails?
Я пытаюсь добавить теги к Article
модель в приложении Rails 3.
Мне интересно, есть ли гем или плагин, который добавляет в модель функциональность "пометки", а также помощники по автозаполнению для представлений.
я обнаружил acts_as_taggable
но я не уверен, что это то, что я должен использовать. Есть ли что-то новее? Я получаю результаты с 2007 года, когда я Google google act_as_taggable
3 ответа
acts_as_taggable_on и https://github.com/crowdint/rails3-jquery-autocomplete прекрасно работают вместе, чтобы создать систему меток типа SO, см. пример ниже. Я не думаю, что подходящие все в одном варианте еще существуют для рельсов.
Выполните следующие действия, чтобы установить все это:
1 Сделайте резервную копию вашего приложения!
2 Установите jquery-рельсы
Примечание. Пользовательский интерфейс jQuery можно установить с jquery-rails
но я решил не делать этого
3 Загрузите и установите jQuery UI
Выберите тему, которая будет дополнять ваш веб-дизайн (обязательно протестируйте демонстрацию автозаполнения с выбранной темой, тема по умолчанию у меня не сработала). Загрузите пользовательский почтовый индекс и поместите [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 %>
Примечание. В этом примере предполагается, что вы помечаете только одну модель во всем приложении и используете только тип тега по умолчанию: теги. В основном код выше будет искать все теги, а не ограничивать их тегами "Article".
Возможно, ваш лучший выбор - это https://github.com/jviney/acts_as_taggable_on_steroids/ gem. Я обнаружил, что многие из драгоценных тегов являются "хорошим местом для начала", но затем требуют значительного количества настроек, чтобы получить желаемый результат.
Я недавно написал в блоге об этом; для краткости мой метод позволяет вам иметь (необязательно) теги, отфильтрованные по контексту (например, по модели и по атрибуту в модели), тогда как решение @Tim Santeford получит вам все теги для модели (не отфильтрованные по полю), Ниже стенографический пост.
Я опробовал решение Тима Сэнтефорда, но проблема была в результатах тегов. В его решении вы получаете все существующие теги, возвращаемые через автозаполнение, а не ограниченные вашими моделями и теговыми полями! Итак, я нашел решение, которое, на мой взгляд, намного лучше; он автоматически расширяется на любую модель, которую вы хотите пометить, он эффективен, а главное - очень прост. Он использует gem акт-as-taggable-on и библиотеку JavaScript select2.
Установите драгоценный камень Acts-As-Taggable-On
- Добавьте акты-как-taggable-on в ваш Gemfile:
gem 'acts-as-taggable-on', '~> 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
как мы указали в модели. Не спрашивайте меня почему, но действует как as taggable-on, ожидая единственного + _list, и это то, что требуется в представлениях.
На слой просмотра! Я использую гем SimpleForm и механизм шаблонов Slim для представлений, поэтому моя форма может немного отличаться от вашей, если вы не используете гем. Но это просто обычное поле ввода текста:
= f.input :genre_list, input_html: {value: @film.genre_list.to_s}
Ты нуждаешься в этом input_html
атрибут с этим значением, установленным для того, чтобы отобразить его в виде строки, разделенной запятыми (что и действует в контроллере действует как метка тега). Маркировка теперь должна работать, когда вы отправляете форму! Если это не сработает, я рекомендую посмотреть (потрясающий) эпизод Райкана Бейтса о тегах.
Интеграция select2 в ваши формы
Прежде всего, нам нужно включить библиотеку select2; Вы можете либо включить его вручную, либо использовать (в моем предпочтении) гем select2-rails, который упаковывает select2 для конвейера ресурсов Rails.
Добавьте драгоценный камень в свой 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 возвращает массив, в котором действует-как-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 для всех полей с тегами.
Остальное - просто код, связанный с 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, используя такие же действия, как и taggable-on, и select2!