Представьте ситуацию, что у вас имеется некая модель, к которой вы хотите добавлять по средствам связей другие модели. Хорошо, когда данную связь можно описать обычным has_many, другое дело, когда нужно привязать разнородные модели, к примеру, у вас имеется модель вопросов, к которой нужно привязать город или страну (не очень наглядный пример, вряд ли он вам в жизни встретится, но я поделюсь своим опытом).
Как вы, наверное, догадались, основным инструментом будет выступать ruby an rails. Из гемов будем использовать simple_form для удобного создания форм, inherited_resources, чтобы писать намного меньше кода в контроллерах (сейчас не представляю без этого гема жизни).
Также нам понадобится плагин для jQuery tokenInput, который будет отображать выпадающий список с autoComplete’ом, из которого мы сможем выбрать одно значение из стран или городов (список будет один).
Реализация
Начнем с контроллера QuestionableController (не очень, конечно, удачное название), который будет отдавать солянку из городов и стран с возможностю поиска.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Лучше вынести всю логику в модель, но данный пример создан исключительно для демонстрации. Что мы делаем в этом контроллере? Ищем страны и города, удовлетворяющих параметру q и заполняем инстанс переменную @questionable
Также давайте заполним нашу вьюшку index.json.erb
1
|
|
в которой мы рендерим наш json, т.к. нам не нужны все поля модели мы используем только необходимые (only: [:id, :name])
, ключ methods: указывает на то, что мы помимо физических свойст, будем использовать метод модели id_with_class_name.
Пока не забыл, давайте пропишем пару строк в наш config/routes.rb
1 2 |
|
Перейдем к моделям:
models/question.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Самая интересная строчка – это та, которая начинается с belongs_to
, в ней мы объявляем полиморфную связь questionable. Дальше идет метод, который мы вызываем каждый раз перед сохранением в базу данных. В нем мы парсим строку из autoComplete’а, которая будет иметь вид id_ModelName и сохраняем отдельно id и ModelName.
Модель city.rb
1 2 3 4 5 6 7 8 |
|
В ней объявляется связь has_many
через questionable, который мы обявили в модели question полиморфной. Далее идем метод, который возвращает id записи и название класса модели (чтобы можно было отделить зерно от плевел).
Модель country.rb почти такая же
1 2 3 4 5 6 7 8 |
|
Связь обявлена также как и в предыдущей модели.
Наш маленький контроллер QuestionController
1 2 |
|
где остальное спросите вы. Это все. Это все, что нам нужно для того, чтобы создавать, удалять, редактировать вопросы. Как вы, наверное, заметили наш контроллер наследуется не от ApplicationController’а, а от inherited_resources. Что это значит? А это значит, если абстрагироваться от 80% возможностей этого гема, то, что гем берет всю “грязную” работу на себя, предоставляя нам в пользование переменные resource и collection, хранятся в которых ссылка на текущую запись (resource) и список всех записей (collection) соответственно. Для того, чтобы мы смогли создать свой первый вопрос нам необходимы лишь вьюшки. Начнем с _form.html.erb, которую мы будем подключать и в создании вопроса, и в редактировании.
app/views/questions/form.html.erb_
1 2 3 4 5 6 7 8 |
|
Как видите, почти никаких различий с нативным form_for нет. Во второй строке мы заполняем переменную pre json’ом выбранного города или страны, в предпоследней создаем инпут с атрибутом data-pre, равным json’у и классом, чтобы мы могли найти этот элемент без проблем.
new.html.erb и edit.html.erb абсолютно одинаковы и представляют собой следующее:
1
|
|
в них мы отрисовываем только форму.
Далее кинем наш скачанный jquery.tokeninput.js в папку app/assets/javascripts и допишем в application.js следующее:
1 2 3 4 5 6 7 8 |
|
Первым параметром к tokenInput мы указываем откуда брать данные, tokenLimit:1 указывает на то, что одной записи нам будет достаточно (одного элемента из выпадающего списка), tokenValue – откуда мы будем брать имя, которое будем отображать из json’а, prePopulate используем для того, чтобы заполнить элемент, если мы его уже выбрали (редактирование).
Сумбурным получилось изложения материала, потому что для меня это в новинку и писалось это для того, чтобы не забыть полезную “плюшку” в дальнейшем. Если заметка кому-нибудь еще пригодится, я буду очень рад :)
P.S.: Если данную заметку читает Александр, мало ли, то, надеюсь, он будет не против, что я позаимствовал его идею :)
Демо проект “лежит” на heroku, репозиторий, как всегда на github (там же лежат и миграции, нужные для запуска проекта).
Удачи вам в любых начинаниях и до новых встреч :)