Можно ли всерьёз обсуждать 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 и получайте от этого удовольствие!