The other day I released a new version of my gem
Gon - 4.0.0 and decided to give a couple of examples of its capabilities and use. This library is used to simplify working with data in the MVC architecture. It allows you to work with the controller's data from JS by skipping the steps of transferring this data through the twist. Today there are rut implementations for
RoR applications ,
sinatra-like applications (sinatra, padrino, etc.) and for
.Net MVC .
Card in admin
My task was to implement the division of proposals in the Group into territorial regions that the administrator could edit. Offers from areas are shown to people who live in these areas with a higher priority than offers from other areas.
It was decided to make in the admin an interactive map based on Yandex.Maps, on which the offer points and areas should be displayed. Regions can be added and edited and their coordinates will be stored in the database respectively. Yandex.Maps provide an extensive API for drawing various elements on maps; I was approached from there by polygons to display areas and points with tooltips for sentences. A screenshot below without points of offers, the secret information;)
')

I created several data models - CityArea to describe the essence of the territorial area in the city, CityAreaPoint to store the coordinates of the points that make up the CityArea, and AreaOffer - the sentence belongs to a specific area. Prepared a page, JS for drawing areas, server logic for linking objects to each other, and the question arose of outputting data to a page. One of the standard methods in MVC for me is to select the necessary data in the controller action, edit it to the required format, write the final data array into a variable and then add the item with the data attribute to which this data will be written and JS will read from there. th That is, I did something like this:
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|
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') ...
The result was a very loaded controller - too much code that should not be there. You can bring it to the model, but the code will still look like this. I decided to use my gem gon which has support for templates
rabl and
jbuilder . Rabl is a great gem that works great with collections of objects and associations of objects from a database, so the code inside the templates is more optimal. Well, rut allows you to use the power of rabl easily and simply:
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.rabl
collection @offers => 'offers' attributes :id, :permalink, :short_title child :points do attributes :id, :latitude, :longitude end
app/assets/javascripts/admin/map.js
gon.offers …
Thus, having spent a minimum of efforts on data conversion and transferring this data to JS, I received a “clean” controller and arrays of the data I needed in JS.
Gon gem
In addition to working with the
rabl and
jbuilder templates , gon is great for displaying any initial data or global data that is set at any point in the project and is accessible from any point of the project. That is, for example, if the value of a variable should be on all pages of the project - it is enough to set this variable in the utility. To do this, use the method
gon.global , which works similarly to gon, except that the scope of the variables written to it is global. In JS, variables are available through the gon.global namespace.
In addition, yesterday I released version 4.0.0 in which the functionality of updating data in a variable without reloading the page appeared -
gon.watch . New functionality allows you to update the data in a loop with an interval of n-seconds, and one-time response to any event. As an example, I will show how you can display the results of the top command in real time with several lines of code:

Suppose we have a new rails application. To display the terminal top we just need to do the following:
1. Add the line
gem 'gon'
to the Gemfile and run
bundle install
2. Add a controller with an action such as home # foo, view for it
app/views/home/foo.html.erb
and JS
app/assets/javascripts/home.js.coffee
rails g controller home foo
3. In the
app/controllers/home_controller.rb
controller action,
app/controllers/home_controller.rb
write to gon.watch the variable one-time execution of the top command:
def foo gon.watch.top = `top -l1` end
4. In the
app/views/layouts/application.html.erb
layout, add a helper with the watch option to the head:
<head> <%= include_gon(watch: true) %>
5. In the view
app/views/home/foo.html.erb
add a tag in which we will display top:
<pre style='font-family:monospace;'></pre>
6. In the coffee file, add the code that we will have to monitor the variable and update the contents of the pre tag:
$(document).ready -> renewPre = (data) -> $('pre').html(data.replace(/\\n/, "\n")) gon.watch('top', interval: 1000, renewPre)
gon.watch
accepts two or three parameters - the name of a variable, options (optional), callback. In this case, we passed the
interval: 1000
option, which will loop the gon.watch code at intervals of 1 second. This means that every second
gon.watch
sends a request to the controller action in which the variable was assigned and returns the current value. If the request is successful, the callback is called, which in turn overwrites the contents of the pre tag. To stop the loop, there is a function gon.unwatch, which takes a variable name and a callback function. Naturally, if you do not pass the
interval
option, then there will be no cycle, there will be a one-time request and, if successful, a one-time callback call.
That's it, launch
rails s
, open
0.0.0.0:3000/foo
- there we have a live top.