Ruby on Rails + simple javascript patternization

в 10:45, , рубрики: javascript, patterns, ruby on rails, Веб-разработка, метки: , ,

Я уже давно разрабатываю приложения используя haml и coffee script. Всячески стараюсь избегать случаев написание pure javascript кода, html или erb. К хорошему быстро привыкают.
Ruby on Rails ругают за низкую производительность, отчасти это правда, отчасти не все возможности оптимизации поддались постижению. В любом случае,
Views: 490.9ms | ActiveRecord: 14.4ms
выглядит печально и хабраэффекта я не переживу. Как раз настал момент рефакторинга, кода вопрос производительности встал ребром.

Решение лежало в области ejs.

Первым и единственным был отсмотрен gem 'haml_ejs', но:

%p
  ^= my_var
  ^if sunny
    Nice, eh?

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

1. Шаблон

У меня есть partial который выглядел примерно следующим образом.

.row-fluid.car{:id => car.id}
    .span1
      = link_to  I18n.localize(car.updated_at, :format => '%d %b'),  '#', :class => 'login_from_source popop'
      = link_to Time.at(car.updated_at).strftime('%H:%M'),  '#', :class => 'login_from_source popop'
      = link_to (image_tag 'repair.png'), '#', :class => 'login_from_source popop' if car.crashed
      '... и т.д. ...'

После выпиливания всего руби из partial осталось следующее.

.row-fluid.car.car_id
  .span1
    %a.login_from_source.popop.car_updated_at_date{:href => '#'}
    %a.login_from_source.popop.car_updated_at_time{:href => '#'}
    %a.login_from_source.popop.car_crashed{:href => '#'}
      '... и т.д. ...'

Я добавил к каждому тегу класс, чтобы проще было идентифицировать объект в DOM-е, почти так же как это делается в ejs.
Т.к. haml пришлось бы переписать в ejs-подобный вид, то на этом этапе экономия времени очевидна.

2. Контроллер

Контроллер, который занимался сбором данных и инструкциями рендеринга, никогда не вспомню как выглядел, там было что-то стандартное, типа

 format.html { render :layout => 'cars' } 

Что, в свою очередь, приняло вид:

 
format.json { render :json => {
     :cars => cars_for_template(@cars).to_json, 
     :template => render_to_string(current_user ? 'cars/registered.html.haml' : 'cars/anonym.html.haml')
   }
# где cars/anonym.html.haml и  cars/registered.html.haml очищенные от руби 
# шаблоны, как в пункте 1

для анонимных и зарегистрированных пользователей использовались разные шаблоны.

Функция cars for template собирает коллекцию для рендеринга.

 
def cars_for_template(cars)
    collection = []

    for car in cars
      hash = {}
      hash[:id] = car.id
      hash[:updated_at_date] = I18n.localize(car.updated_at, :format => '%d %b')
      hash[:updated_at_time] = Time.at(car.updated_at).strftime('%H:%M')
     '... more code here...' 
     collection.push hash
      
   end
   
   collection
end
3. Велосипед

Осталось по готовности DOM-а получить мой json и вставить отрендереным на страницу.

$(document).ready ->
  $.ajax
      url: "/cars.json"
      beforeSend: ->
        $(".slider").show()

      success: (response) ->

        data = JSON.parse(response.responseText)
        cars = JSON.parse(data.cars)
        append_from_template(cars,data.template)
        $(".car").show()
        $(".slider").hide()

@append_from_template = (cars, template) ->
  for car in cars
    $('#cars').append(template)
    t = $('.car').last()
    t.attr('id', car.id)
    t.find('a.car_updated_at_date').html(car.updated_at_date)
    t.find('a.car_updated_at_time').html(car.updated_at_time)
    if car.crashed
      t.find('a.car_crashed').html("<img src="assets/repair.png">")
    t.find('a.car_image').html(car.photo_url)
   'и т.д.'
4. Результат:

1. В качестве шаблона остается HAML.
2. Минимум изменений в контроллере.
3. Производительность:
Было
Views: 490.9ms | ActiveRecord: 14.4ms
Стало
Views: 12.9ms | ActiveRecord: 17.1ms
На iPad 1 страничка из 15 Car рендерится без видимой задержки.
4. В отличии от EJS я могу производить дополнительные преобразования значений объектов на стороне клиента.
5. На велосипед ушло столько же времени, сколько ушло бы на использование ejs.

P.S.
Чего в этой статье нет:
1. Нет валидации при сборе объектов в джейсон.
2. На данный момент времени переношу преобразования из функции cars_for_template(cars) в javascript, как то преобразования цены как Integer в человеко подобный вид типа '100 500 р' и других.
3. Не обрабатывается Ajax error, т.к. не имеет прямого отношения к теме и сбор ошибок в статье также не отражен.
Если эти моменты интересны — отражу в следующей статье.

Автор: Renius

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js