Можно ли всерьёз обсуждать fullstack-разработку? Если смотреть в сторону больших фреймворков для frontend и для backend, то разговор про fullstack выглядит сомнительно. Предлагаю посмотреть на термин fullstack с точки зрения Ruby on Rails и прежних более простых принципов реализации интерактивности на классических веб-страницах. Представляю вашему вниманию обзор frontend-возможностей, предусмотренных во фреймворке Ruby on Rails или связанных с ним.

image


Ruby on Rails — MVC-фреймворк, сосредоточенный на быстрой разработке, уделяющий много внимания поддержанию устроенности внутри проекта (чтобы «быстро» не оказалось «как попало»). В нём предусмотрено множество инструментов как для backend, так и для frontend разработки. В классическом fullstack-направлении сложилось моментное упущение из-за неосведомленности о его развитии и заблуждения об отставании используемых средств. Задача этой статьи осветить каким путем развивается fullstack-подход и сколько в нём появилось разных интересных возможностей.

Webpacker


Webpacker — гем, который идет в поставке с Ruby on Rails.

Webpacker предоставляет обертку над Webpack: команды для подключения и стартовые конфигурации для работы. Webpacker де-факто устанавливает стандарт работы с frontend в Ruby on Rails, способствует использованию последних возможностей языка JavaScript и современных принципов работы с кодом (структурированность, модульность, сборка и многое другое).

Webpacker задает общие конфигурации, необходимые для начала работы, и структуру приложения, что повышает определенность и упрощает понимание проекта разными разработчиками. Для JavaScript-кода отводится папка app/javascript/ с первичным файлом app/javascript/packs/application.js.

Файлы и папки, добавленные Webpacker
config/webpacker.yml
config/webpack/
config/webpack/development.js
config/webpack/environment.js
config/webpack/production.js
config/webpack/test.js
package.json
postcss.config.js
babel.config.js
.browserslistrc
node_modules/
bin/webpack
bin/webpack-dev-server
app/javascript/
app/javascript/packs/
app/javascript/packs/application.js


Webpacker запускается по умолчанию в процессе создания нового приложения и выполняет свои настройки. Создать приложение можно сразу вместе с дополнительными конфигурациями для Stimulus, Vue, Typescript или другого из списка предусмотренных:

rails new myapp --webpack=stimulus

Или установить дополнительные конфигурации после создания приложения:

bundle exec rails webpacker:install:stimulus

Разрабатывать Frontend с фреймворком Ruby on Rails = использовать самые актуальные подходы к разработке на JavaScript. Все удобства от использования современных стандартов JavaScript хорошо интегрированны с Ruby on Rails. Предусмотрены необходимые конфигурации для работы с Webpack, что позволяет меньше отвлекаться на правильную организацию проекта и сосредоточиться на решении востребованных задач, используя привычное окружение.

Turbolinks


Turbolinks — JavaScript-библиотека, которая поставляется вместе с Ruby on Rails.

Приоритетная задача Turbolinks — облегчение нагрузки на сервер и сокращение «швов» при переходе по url-адресам приложения. Эту возможность часто сравнивают с SPA, так как создается впечатление перерендеринга содержимого в браузере вместо неказистых стандартных переходов между страницами.

Принцип работы Turbolinks: осуществлять навигацию между страницами не путем стандартного перехода на новый адрес, а за счет выполнения запроса на этот адрес «в фоне» через ajax, загрузки ответа в JavaScript, и замены содержимого страницы на новое. Этот процесс сопровождается специальными эвентами, которые позволяют добавить функциональность на переход между страницами и на возврат к предыдущим страницам. Например,
  • на запуск перехода к другому адресу: turbolinks:click, turbolinks:before-visit, turbolinks:visit;
  • или на обработку запроса к новой странице: turbolinks:request-start, turbolinks:request-end;
  • или на процесс отображения новой страницы: turbolinks:before-render, turbolinks:render, turbolinks:load.

Дополнительно Turbolinks имеет прогресбар, кеширует историю загруженных страниц и позволяет указать необновляемые элементы страницы.

ActionCable


ActionCable — часть фреймворка Ruby on Rails. ActionCable обустраивает работу с веб-сокетами. Для перечисления каналов на сервере предусмотрена папка app/channels/ с первичными файлами channel.rb и connection.rb. Для реализации подключения к этим каналам — папка app/javascript/channels/ с файлами index.js и consumer.js.

Знакомиться с возможностями ActionCable лучше сразу на примере. Простейшее подключение к веб-сокетам с его помощью можно реализовать всего за пару шагов.

  1. Создать файл с каналом

    app/channels/hello_channel.rb
    # app/channels/hello_channel.rb
    class HelloChannel < ApplicationCable::Channel
      def subscribed
        stream_from "hello_1"
      end
    end
    

  2. Создать подписку на этот канал
    app/javascript/channels/hello_channel.js
    // app/javascript/channels/hello_channel.js
    import consumer from "./consumer"
    
    consumer.subscriptions.create({ channel: "HelloChannel" }, {
      received(data) {
        console.log("Data received", data);
        document.body.innerText += `\nHello, ${data.name}!`
      }
    })
    


И подключение к веб-сокетам готово.

Теперь чтобы проверить его нам потребуется простейшая страница и экшен для рассылки. Для этого создадим контроллер и добавим в роуты его адреса

app/controllers/hello_controller.rb
# app/controllers/hello_controller.rb
class HelloController < ApplicationController
  def index
    render :html => "Hello page...", :layout => true
  end

  def new
    ActionCable.server.broadcast("hello_1", name: params[:name])
    head 200
  end
end


config/routes.rb
# config/routes.rb
get "/hello" => "hello#index"
get "/hello/new" => "hello#new"


Запускаем приложение, заходим по адресу 127.0.0.1:3000/hello и открываем консоль браузера, в ней можно будет видеть логирование сообщений, которые приходят от сервера через веб-сокеты.

Далее отправляем запрос на экшн рассылки:

curl http://127.0.0.1:3000/hello/new?name=World

И смотрим на страницу /hello и на вывод в её консоли.

Хелперы форм и Rails-ujs


Заслуживают внимания и некоторые не новые, но хорошо зарекомендовавшие себя возможности фреймворка Ruby on Rails. Среди них хелперы для представлений и форм. Изначальное удобство хелперов в том, что они облегчают интеграцию разметки с моделями, конфигурациями и другими backend-компонентами. Преимущество хелперов форм над обычной разметкой состоит в предоставлении возможности быстро перечислить поля формы не вдаваясь в подробности их привязки к атрибутам модели — с помощью хелперов связь между ними сформируется автоматически. Фрагмент, показывающий пример связывания полей формы с параметрами контроллера и атрибутами модели:

app/views/articles/new.html.erb
<%# app/views/articles/new.html.erb %>
<%# Перечисляем в форме поля модели %>
<%= form_with(model: Article.new) do |f| %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :text %>
    <%= f.text_area :text %>
  </div>
  <%= f.submit %>
<% end %>


config/locales/ru.yml
# config/locales/ru.yml
# текст для лейблов автоматически связывается с локализацией
ru:
  activerecord:
    attributes:
      article:
        title: Название статьи
        text: Текст статьи


config/application.rb
# config/application.rb
# для активации нашей локализации добавим строчку в config/application.rb
config.i18n.default_locale = :ru


app/controllers/articles_controller.rb
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController

  def new
    # метод new можно оставить пустым
    # rails по-умолчанию перейдёт к рендерингу соответствующего представления
  end

  def create
    # используем значения полей формы для создания нового объекта
    @article = Article.create(article_params)

    # редирект на модель - еще один удобный хелпер,
    # он автоматически подставит url нужного ресурса
    # для отображения страницы, соответствующей объекту модели
    redirect_to @article
  end

  private

  def article_params
    # для безопасности необходимо указать структуру параметров
    params.require(:article).permit(:title, :text)
  end
end


Подробнее с этим примером можно познакомиться здесь и здесь.

Rails-ujs


Rails-ujs — базовая часть фреймворка Ruby on Rails, отвечающая за ненавязчивый JavaScript.
Rails-ujs предоставляет несколько дополнительных опций для элементов страницы, которые меняют или расширяют работу с ними.

Опция remote — предназначена для элементов, которые выполняют обращение к серверу (ссылки, формы), чтобы сделать запросы асинхронными. Пример ссылки:

<%= link_to "Добавить статью", new_article_path, remote: true %>

Для отображения результатов такого запроса требуются дополнительные манипуляции, например, добавление обработчика к remote-событиям: ajax:success, ajax:error, ajax:complete.

Опция confirm — позволяет запросить подтверждение действия перед его выполнением.

<%= link_to "Удалить", article_path(article), method: :delete,
  data: { confirm: 'Вы уверены, что хотите удалить эту статью?' } %>


Опция disable_with — позволяет деактивировать элемент после действия
<%= form.submit data: { disable_with: "Сохранение..." } %>

Дополнительно в Rails-ujs есть несколько удобных функций. Вот некоторые из них:

Rails.fire(obj, name, data) — функция вызова события
Rails.ajax(options) — обертка над XMLHttpRequest
Rails.isCrossDomain(url) — проверка принадлежности url другому домену
Rails.$(selector) — обертка над document.querySelectorAll

Подключить их к вашему коду можно командой

import Rails from "@rails/ujs"

Stimulus


Stimulus — JavaScript-фреймворк от разработчиков Ruby on Rails.

Stimulus один из редких фреймворков и по своему уникальный, поскольку обустраивает frontend-разработку с применением новых подходов к JavaScript, при этом не стремится управлять всеми вашими действиями и не навязывает отделять frontend от backend.

Базовая задача Stimulus — привязка обработчиков к событиям. Согласно Stimulus исходный код нужно располагать по классам-контроллерам, а в роли обработчиков использовать их методы. По умолчанию для stimulus-контроллеров в проекте отводится папка app/javascript/controllers/ с первичным файлом index.js. Здесь мы можем добавлять наши контроллеры, для этого нужно создать файл с суффиксом _controller.js, например, articles_controller.js. Далее загрузчик Stimulus импортирует такие файлы и подключит контроллеры к соответствующим блокам на наших страницах.

У контроллеров в Stimulus предусмотрены дополнительные оснащения: инициализация объекта контроллера (initialize), хелперы для обращения к элементам внутри блока (targets, таргеты), присоединение объекта контроллера к блоку (connect) и отсоединение от него (disconnect), обращение к дата-аттирибутам блока (this.data.get). Ниже приведен пример блока с переключением состояния активен/неактивен, написанный на Stimulus.

app/views/home/show.html.erb
<%# app/views/home/show.html.erb %>

<%# назначаем контроллер home нашему блоку %>
<%# и дополняем данными его атрибуты (для полноты примера) %>
<div data-controller="home"
    data-home-active-text="Activated" data-home-deactive-text="Deactivated">

  <%# назначаем параграфу таргет text контроллера home  %>
  <p data-target="home.text"></p>

  <%# назначаем кнопке action click контроллера home %>
  <button data-action="home#click">Кнопка</button>

</div>



app/javascript/controllers/home_controller.js
// app/javascript/controllers/home_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  // объявляем таргеты
  static targets = [ "text" ]

  initialize() {
    // загружаем текст активного и неактивного состояния
    this.activeText = this.data.get("active-text");
    this.deactiveText = this.data.get("deactive-text");
  }

  connect() {
    // загружаем состояние активен/неактивен
    this.active = this.data.get("active") == "true";
    // обновляем отображение
    this.refresh();
  }

  disconnect() {
    // сохраняем состояние перед отключением контроллера
    this.element.setAttribute("data-home-active", !!this.active);
  }

  click() {
    // переключаем активность
    this.active = !this.active;
    // обновляем отображение
    this.refresh();
  }

  // функция обновления внешнего вида
  refresh(){
    // меняем цвет блока самого контроллера
    this.element.style.background =   this.active ? "none" : "#EEE";
    // меняем текст
    this.textTarget.innerHTML =   this.active ? this.activeText : this.deactiveText;
    // меняем цвет текста
    this.textTarget.style.color = this.active ? "black" : "#777";
  }
}


Несмотря на сохранение прежних принципов реализации интерактивной функциональности на классических страницах, подход к разработке со Stimulus значительно улучшается: по новому устроена структура исходного кода, изменяется привязка обработчиков к событиям, предусмотрены дополнительные оснащения. Благодаря этим удобствам и своей простоте фреймворк Stimulus позволяет быстро и грамотно структурировать даже большой frontend.

Дополнительно стоит подчеркнуть, что Stimulus хорошо сочетается с другими возможностями Ruby on Rails — почти в каждой связке возникает полезная эмерджентность.

Stimulus и Webpacker


В Webpacker предусмотрены команды для создания приложения с подключенным Stimulus:

rails new myapp --webpack=stimulus

Или для подключения его в уже созданный проект:

bundle exec rails webpacker:install:stimulus

Stimulus и JavaScript


Stimulus способствует применению современных принципов разработки на JavaScript для реализации интерактивности на ваших страницах. Со Stimulus frontend-решение собирается модульно, для обработчиков событий используется ООП, продуманно структурируется код. По средством таргетов с помощью stimulus-контроллеров удобно управлять подключением к элементам блока сложных графических компонентов, взятых из сторонних библиотек или написанных самостоятельно (календари, автокомплитеры, списки, деревья и другие). Благодаря этому, Stimulus — один из простейших способов перейти от устаревших frontend-инструментов и получить необходимую продуктивность от применения современного чистого JavaScript.

Stimulus и Ruby on Rails


Со структурой кода, рекомендуемой Stimulus, вы продолжаете писать на JavaScript по той же схеме, как писали бы на Ruby on Rails. Так же объявляете контроллеры, так же привязываете экшены к методам. Со Stimulus frontend-разработка становится схожа с backend, что облегчает работу и там, и там.

Stimulus и ActionCable


С помощью методов initialize и connect в stimulus-контроллерах удобно привязывать веб-сокеты не ко всей странице, а к отдельным её блокам и точечно реализовать работу с поступающими сообщениями. Становится проще организовать на одной странице сразу несколько параллельных потоков с независимым переключением по каналам.

Stimulus и Turbolinks


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

Turbolinks не только облегчает загрузку страниц, но и кеширует их содержимое при переходе. При возврате на закешированную страницу из истории Stimulus активируется автоматически, как при загрузке новой страницы. Если требуется сохранить какие-то значения перед отключением контроллера от блока, то можно воспользоваться методом disconnect — и тогда, при возврате и подключении контроллера, он сможет восстановить своё последнее состояние. В коде первого примера работы со Stimulus можно увидеть как при отключении (disconnect) в data-атрибуте блока контроллера фиксируется значение this.active, а при подключении (connect) извлекается обратно.

Возврат по страницам может стать полезным способом работы с приложением. Например, при работе с формой бывает необходимо заполнить/отредактировать значение в справочнике на другой странице, затем вернуться и выбрать новые данные.

Другой пример: допустим нам необходимо отредактировать или посмотреть значения на странице настройки веб-чата. Нам будет полезно при отключении контроллера от блока с беседой запомнить идентификатор последнего сообщения, чтобы при возврате сначала запросить недостающие новые сообщения, а потом уже открывать соединение по веб-сокетам.

Таким образом, возврат по истории страниц может использоваться как удобный прием работы с вашим веб-приложением.

Stimulus и Хелперы форм


Stimulus тесно работает с разметкой, а с помощью хелперов данные легко внедряются в html-блоки, благодаря этому можно часть данных загружать в data-атрибут блока и доставать их в контроллере.

app/views/articles/show.html.erb
<%# пример элемента с данными в представлении app/views/articles/show.html.erb %>

<%# извлекаем необходимые данные из модели %>
<% article_data = article.attributes
      .slice("id", "created_at", "updated_at", "author", "title").to_json %>

<%# объявляем тег div с помощью хелпера content_tag %>
<%= content_tag :div,
    :data => { controller: :articles },
    "data-articles-attributes" => article_data do %>
  <%# ... %>
<% end %>


app/javascript/controllers/articles_controller.js (фрагмент)
// затем распарсить данные
// в методе initialize контроллера app/javascript/controllers/articles_controller.js
initialize() {
  this.attributes = JSON.parse(this.data.get("attributes"));
  // другие действи инициализации
  // ...
}


Stimulus и Rails-ujs


С помощью Stimulus и remote-опций можно напрямую подключать контроллеры к ajax-событиям и обрабатывать результаты запросов. Объявим ссылку с использованием Rails-ujs и привяжем к ней stimulus-обработчик.

ссылка с опцией remote и stimulus-обработчиком
<%= link_to "Открыть статью",
  article_path(article, format: :html),
  data: { remote: true, action: "ajax:success->articles#showArticle" } %>


При нажатии на эту ссылку произойдёт асинхронный Ajax-запрос в rails-контроллер articles_controller.rb на экшен show. При получении положительного ответа сработает событие ajax:success и вызовется метод showArticle из контроллера app/javascript/controllers/articles_controller.js

метод showArticle контроллера app/javascript/controllers/articles_controller.js
showArticle(e) {

  // берем ответ сервера из объекта события
  const xhr = e.detail[2];

  // обновляем содержимое формы просмотра
  this.showFormTarget.innerHTML = xhr.responseText;

  // показываем форму
  this.showFormTarget.style.display = "block";
}


Что дальше?


Перечисленные средства вместе с фреймворком Ruby on Rails открывают новые горизонты для fullstack-разработки. При этом описанные инструменты относительно просты и не требуют длительных погружений — всё необходимое для успешного проекта находится на поверхности.

Создавайте веб-приложения с помощью современных и быстрых fullstack-инструментов разработки с фреймворком Ruby on Rails и получайте от этого удовольствие!