На днях я зарелизил новую версию своего gem Gon – 4.0.0 и решил привести пару примеров его возможностей и использования. Данная библиотека служит для упрощения работы с данными в MVC архитектуре. Она позволяет работать с данными контроллера из JS пропуская шаги перекидывания этих данных через вью. На сегодняшний день существуют реализации гона для RoR приложений, sinatra-like приложений (sinatra, padrino, etc.) и для .Net MVC.
Передо мной стояла задача — реализовать разделение предложений в Групоне по территориальным областям, которые бы мог редактировать администратор. Предложения из областей показываются людям которые живут в этих областях с большим приоритетом нежели предложения из других областей.
Решено было сделать в админке интерактивную карту на основе Яндекс.Карты, на которой должны отображаться точки предложений и области. Области можно добавлять и редактировать и их координаты соответственно будут храниться в базе данных. Яндекс.Карты предоставляют обширный API для рисования различных элементов на картах, мне подошли оттуда многоугольники для отображения областей и точки с тултипами для предложений. Скриншот ниже без точек предложений, тк секретная информация ;)

Я создал несколько моделей данных — CityArea для описывания сущности территориальной области в городе, CityAreaPoint для хранения координат точек из которых состоит CityArea, и AreaOffer — принадлежность предложения к определенной области. Подготовил страницу, JS для рисования областей, серверную логику для связывания объектов друг с другом и встал вопрос о выводе данных на страницу. Один из стандартных способов в MVC для меня – это выбрать нужные данные в экшене контроллера, отредактировать их до необходимого формата, записать в переменную конечный массив данных и далее во вью добавить элемент с data-атрибутом в который эти данные будут записаны и откуда будут считаны JS-ом. То есть я сделал тогда примерно вот так:
Получился очень загруженный контроллер — слишком много кода, которого там не должно быть. Можно вынести в модель, но код останется все равно примерно таким. Я решил использовать свой gem gon который имеет поддержку темплейтов rabl и jbuilder. Rabl — прекрасный gem, который отлично работает с коллекциями объектов и с ассоциациями объектов бд, поэтому код внутри шаблонов получается более оптимальным. Ну а гон позволяет использовать мощь rabl легко и просто:
Таким образом затратив минимум усилий на преобразование данных и прокидывание этих данных в JS я получил «чистый» контроллер и массивы нужных мне данных в JS.
Помимо работы с шаблонами rabl и jbuilder, гон отлично подходит для вывода каких-либо начальных данных или глобальных данных, которые задаются в любой точке проекта и доступны из любой точки проекта. То есть например если значение какой-то переменной должно быть на всех страницах проекта — достаточно задать эту переменную в инишалайзере. Для этого используется метод gon.global, который работает аналогично с gon за исключением того что область видимости переменных записанных в него глобальная. В JS переменные доступны через неймспейс gon.global.
Кроме того, вчера я зарелизил версию 4.0.0 в которой появилась функциональность обновления данных в переменной без перезагрузки страницы — gon.watch. Новая функциональность позволяет обновлять данные как в цикле с промежутком в n-секунд, так и разово, ответом на какое-либо событие. В качестве примера я покажу как несколькими строками кода можно вывести в реальном времени результаты команды top:

Допустим у нас есть новое rails приложение. Для отображения терминального топа нам достаточно сделать следующее:
1. Добавляем в Gemfile строку
2. Добавляем контроллер с экшеном например home#foo, вью для него
3. В экшене контроллера
4. В лэйауте
5. Во вью
6. В кофе файл добавляем код который у нас будет следить за переменной и обновлять содержимое тега pre:
Вот и все, запускаем
Карта в админке
Передо мной стояла задача — реализовать разделение предложений в Групоне по территориальным областям, которые бы мог редактировать администратор. Предложения из областей показываются людям которые живут в этих областях с большим приоритетом нежели предложения из других областей.
Решено было сделать в админке интерактивную карту на основе Яндекс.Карты, на которой должны отображаться точки предложений и области. Области можно добавлять и редактировать и их координаты соответственно будут храниться в базе данных. Яндекс.Карты предоставляют обширный API для рисования различных элементов на картах, мне подошли оттуда многоугольники для отображения областей и точки с тултипами для предложений. Скриншот ниже без точек предложений, тк секретная информация ;)

Я создал несколько моделей данных — CityArea для описывания сущности территориальной области в городе, CityAreaPoint для хранения координат точек из которых состоит CityArea, и AreaOffer — принадлежность предложения к определенной области. Подготовил страницу, JS для рисования областей, серверную логику для связывания объектов друг с другом и встал вопрос о выводе данных на страницу. Один из стандартных способов в MVC для меня – это выбрать нужные данные в экшене контроллера, отредактировать их до необходимого формата, записать в переменную конечный массив данных и далее во вью добавить элемент с data-атрибутом в который эти данные будут записаны и откуда будут считаны JS-ом. То есть я сделал тогда примерно вот так:
app/controllers/admin/map_controller.rb@location = City.find(params[:city_id].to_i) @offers = Offer.sorted.by_city(@location).by_day(params[:date]) @offers.map! do |offer| points = offer.points.map do |point| { id: it.id, longitude: it.longitude, latitude: it.latitude } end { id: offer.id, permalink: offer.permalink, title: offer.short_title, points: points } end @city_areas = CityArea.find_by_city @location @city_areas.map! do |area| # то же что и с офферами почти end
app/views/admin/map/map.html.haml.data-container{ data: { areas: @city_areas.to_json, offers: @offers.to_json } }
app/assets/javascripts/admin/map.js$('.data-container').data('offers') ...
Получился очень загруженный контроллер — слишком много кода, которого там не должно быть. Можно вынести в модель, но код останется все равно примерно таким. Я решил использовать свой gem gon который имеет поддержку темплейтов rabl и jbuilder. Rabl — прекрасный gem, который отлично работает с коллекциями объектов и с ассоциациями объектов бд, поэтому код внутри шаблонов получается более оптимальным. Ну а гон позволяет использовать мощь rabl легко и просто:
app/controllers/admin/map_controller.rb@location = City.find(params[:city_id].to_i) @offers = Offer.sorted.by_city(@location).by_day(params[:date]) @city_areas = CityArea.find_by_city @location gon.rabl template: 'app/rabl/offers.json.rabl', as: 'offers' gon.rabl template: 'app/rabl/areas.json.rabl', as: 'areas'
app/rabl/offers.json.rablcollection @offers => 'offers' attributes :id, :permalink, :short_title child :points do attributes :id, :latitude, :longitude end
app/assets/javascripts/admin/map.jsgon.offers …
Таким образом затратив минимум усилий на преобразование данных и прокидывание этих данных в JS я получил «чистый» контроллер и массивы нужных мне данных в JS.
Gon gem
Помимо работы с шаблонами rabl и jbuilder, гон отлично подходит для вывода каких-либо начальных данных или глобальных данных, которые задаются в любой точке проекта и доступны из любой точки проекта. То есть например если значение какой-то переменной должно быть на всех страницах проекта — достаточно задать эту переменную в инишалайзере. Для этого используется метод gon.global, который работает аналогично с gon за исключением того что область видимости переменных записанных в него глобальная. В JS переменные доступны через неймспейс gon.global.
Кроме того, вчера я зарелизил версию 4.0.0 в которой появилась функциональность обновления данных в переменной без перезагрузки страницы — gon.watch. Новая функциональность позволяет обновлять данные как в цикле с промежутком в n-секунд, так и разово, ответом на какое-либо событие. В качестве примера я покажу как несколькими строками кода можно вывести в реальном времени результаты команды top:

Допустим у нас есть новое rails приложение. Для отображения терминального топа нам достаточно сделать следующее:
1. Добавляем в Gemfile строку
gem 'gon' и запускаем bundle install2. Добавляем контроллер с экшеном например home#foo, вью для него
app/views/home/foo.html.erb и JS app/assets/javascripts/home.js.coffeerails g controller home foo
3. В экшене контроллера
app/controllers/home_controller.rb записываем в gon.watch переменную разовое выполнение команды top:def foo gon.watch.top = `top -l1` end
4. В лэйауте
app/views/layouts/application.html.erb добавляем хелпер с опцией watch в head:<head> <%= include_gon(watch: true) %>
5. Во вью
app/views/home/foo.html.erb добавляем тег, в котором у нас будет выводиться top:<pre style='font-family:monospace;'></pre>
6. В кофе файл добавляем код который у нас будет следить за переменной и обновлять содержимое тега pre:
$(document).ready -> renewPre = (data) -> $('pre').html(data.replace(/\\n/, "\n")) gon.watch('top', interval: 1000, renewPre)
gon.watch принимает два или три параметра — название переменной, опции (опциональные), callback. В данном случае мы передали опцию interval: 1000, которая зациклит выполнение кода gon.watch с периодичностью в 1 секунду. Это означает, что каждую секунду gon.watch посылает запрос в экшн контроллера в котором была назначена переменная и возвращает актуальное значение, при успешном выполнении запроса вызывается callback, который в свою очередь переписывает содержимое тега pre. Чтобы остановить цикл существует функция gon.unwatch, которая принимает параметрами имя переменной и callback функцию. Естественно, если не передавать опцию interval, то цикла не будет, будет разовый запрос и в случае его успешности — разовый вызов callback-а.Вот и все, запускаем
rails s, открываем 0.0.0.0:3000/foo — там у нас живой top.
