Я уже давно разрабатываю приложения используя 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