Краткий этот туториал проведет вас, шаг за шагом, через все этапы создания элегантного слайдера на основе Ruby on Rails 6 и Bootstrap 5; мне хотелось,чтобы несложный этот rails-app обладал, тем не менее, некоторым рядом опций, управление которыми реализовано посредством удобной панели управления ActiveAdmin. Данная дока не является переводом или адаптацией чьих-то иных усилий: как авторство, так и любые щелчки по носу по праву принадлежат лишь автору в награду за его бессонную ночь (но не за "ночную сборку", не подумайте), ушедшую на написание этой статьи и (в значительно меньшей степени) создание собственно rails-app. Описанная далее последовательность команд выверена вполне добросовестно, придерживайтесь ее: приложение более-менее тщательно оттестировано.

Примечание. По ходу дела, если необходимо, можно сверяться с гитхабом автора. В любой момент, если что-либо не получается, вы можете клонировать полностью готовое к работе приложение и продолжить уже с ним:

$ git clone https://github.com/cmirnow/slider-on-rails.git
$ bundle install
$ yarn install --check-files
$ rails s

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

Из цикла "Сделай сам". Продвинутый слайдер изображений на Rails и Bootstrap - за час.
Из цикла "Сделай сам". Продвинутый слайдер изображений на Rails и Bootstrap - за час.

Панель управления автоматически создает превью загруженных изображений; возможно как удаление всех слайдов одним кликом, так и удаление любого из них кликом по превью. Возможно создавать любое количество слайдеров, каждый со своими картинками и опциями; переключение отображения реализовано буквально двумя-тремя кликами мыши (возможно и любое количество учетных записей админов, разумеется). После создания слайдера и добавления изображений не забудьте его опубликовать, нажав соответствующую кнопку.

Из цикла "Сделай сам". Продвинутый слайдер изображений на Rails и Bootstrap - за час.
Из цикла "Сделай сам". Продвинутый слайдер изображений на Rails и Bootstrap - за час.

Слайдер изображений работает во многом благодаря фреймворку ActiveStorage: в режиме разработки (т.е., иными словами, у вас на ПК) используется локальное хранилище, но после деплоя на Heroku приложение начинает работать как продакшн и, соответственно, под хранилище слайдов используется Amazon S3 либо любое иное совместимое. Впрочем, все эти дефолтлные настройки легко меняются в конфигах, при необходимости.

ОК. Единственное, что пропущу сейчас, ограничившись скороговоркой - это процедуру установки на ваш рабочий ПК необходимого для работы программного обеспечения. С этим - самостоятельно, доков в Сети хоть отбавляй, было бы желание и вообще не проблема. А ПО, установленное у меня на рабочем компе, таково:

$ ruby --version
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux]
$ rails --version
Rails 7.0.2.3
$ node --version
v14.18.3
$ yarn --version
1.22.18
$ psql --version
psql (PostgreSQL) 12.10

На момент публикации этого материала стабильная версия ActiveAdmin пока еще не знает (это ненадолго) седьмые рельсы, поэтому работать будем с шестеркой и его странным webpacker-ом, к которому только-только начинаешь (даже жаль расставаться, к слову) привыкать. Лиха беда начало, поехали:

$ mkdir slider_onrails && cd slider_onrails
$ echo "source 'https://rubygems.org'" > Gemfile
$ echo "gem 'rails', '6.1.5'" >> Gemfile
$ bundle install

Так, что касается PostgreSQL: необходим только лишь в том случае, если в качестве финала данного мини-семинара вы планируете залить слайдер на Heroku, дабы дать возможность коллегам и работодателям полюбоваться в вебе вашим новым сайтом. "Запушенное" на Heroku приложение работает в режиме production, загружая, как уже было сказано выше, картинки слайдера автоматом на Amazon S3, что очень удобно и вполне себе актуально; так работает, например, новостной агрегатор Медуза (признанный у нас иностранным, блаблабла и все такое прочее, что просится на язык, агентом), это те ж самые рельсы, кто не в курсе; правда, на Медузе есть еще и кэширующий сервер, но не суть. Если подобных планов у вас нет, можете смело убирать -d postgresql при вызове следующей команды (use sqlite3 as the database for Active Record):

$ bundle exec rails new . --force -d postgresql
$ bin/rails db:create

Здесь я опять сделаю паузу, перебив сам себя и сходу забежав вперед. Учитывая происходящие крайне невеселые события, и в связи с ними невозможность для многих указать номер Visa / MasterCard при регистрации в облаке Амазона - расскажу о нем очень кратко: для Амазона придется (это можно сделать в самом конце работы) добавить в Gemfile gem 'aws-sdk-s3' и еще раз bundle install, затем добавить в консоль Heroku (Reveal Config Vars) четыре пары ключ/значение, полученные в консоли Amazon S3:

AWS_ACCESS_KEY_ID '*********************'
AWS_SECRET_ACCESS_KEY '****************************'
REGION '****************************'
BUCKET '****************************'

, раскомментировав и слегка отредактировав:

# config/storage.yml
amazon:
   service: S3
   access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
   secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
   region: <%= ENV['REGION'] %>
   bucket: <%= ENV['BUCKET'] %>

и заменив local на amazon:

# config/environments/production.rb
config.active_storage.service = :amazon

Но ничего этого (конец ремарки) не нужно, если для начала мы попросту запустим слайдер на локальной машине. Итак, после успешного окончания rails new и db:create, продолжаем:

$ yarn add bootstrap@next @popperjs/core

(Или же попросту bootstrap, без '@next'. В приложении использован Bootstrap v.5.1.3, на момент публикации это по умолчанию).

Из цикла "Сделай сам". Продвинутый слайдер изображений на Rails и Bootstrap - за час.
Из цикла "Сделай сам". Продвинутый слайдер изображений на Rails и Bootstrap - за час.

Откроем application.html.erb и добавим одну строчку:

# app/views/layouts/application.html.erb
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

Создаем новый каталог и новый файл:

$ mkdir app/javascript/stylesheets && touch app/javascript/stylesheets/application.scss

, который приводим к следующему:

@import "bootstrap";

/* Carousel dark overlay */
.carousel-item:after {
    content: "";
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

/* Slider caption style */
.carousel-caption {
    left: 2%;
    top: 5% !important;
    transform: translateY(-50%);
    text-align: left;
    bottom: initial;
}

И теперь отредактируем application.js:

# app/javascript/packs/application.js
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import * as bootstrap from 'bootstrap'
import "../stylesheets/application"

Rails.start()
Turbolinks.start()
ActiveStorage.start()

Создаем модель с перечнем рабочих опций слайдера, также контроллер с вьюхой. Можно было бы использовать scaffold, но это даст нам вдовесок ненужную кучу файлов и груду бесполезного кода, которые останутся висеть мертвым грузом. Поэтому так:

$ rails g model Slider name:string published_at:datetime interval:integer dark:float fade:boolean captions:text color:string
$ rails g controller Slider index

Теперь добавим в Gemfile:

gem 'image_processing', '~> 1.2'
gem 'activeadmin'
gem 'devise'

Далее:

$ bundle install
$ rails g active_admin:install
$ rails active_storage:install
$ rails db:create
$ rails db:migrate
$ rails db:seed
$ rails g active_admin:resource Slider

Включаем в маршрутах следующую строчку:

# config/routes.rb
root "slider#index"

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

# config/application.rb
config.active_storage.replace_on_assign_to_many = false

Далее вносим корректуру в эти файлы:

# app/admin/sliders.rb
ActiveAdmin.register Slider do
  permit_params :published_at, :name, :captions, :color, :interval, :dark, :fade, images: []
  remove_filter :images_attachments, :images_blobs, :captions, :color, :interval, :dark, :fade

  scope :all
  scope :published
  scope :unpublished

  exclude_columns = [:captions]
  index do
    selectable_column
    Slider.attribute_names.each do |clmn|
      column clmn unless exclude_columns.include? clmn.to_sym
    end
    actions
  end

  action_item :publish, only: :show do
    link_to 'Publish', publish_admin_slider_path(slider), method: :put unless slider.published_at?
  end

  action_item :unpublish, only: :show do
    link_to 'Unpublish', unpublish_admin_slider_path(slider), method: :put if slider.published_at?
  end

  action_item :delete_images, only: :show do
    link_to 'Delete Images', delete_images_admin_slider_path(slider), method: :delete if slider.images.attached?
  end

  member_action :publish, method: :put do
    slider = Slider.find(params[:id])
    slider.update(published_at: Time.zone.now)
    redirect_to admin_slider_path(slider)
  end

  member_action :unpublish, method: :put do
    slider = Slider.find(params[:id])
    slider.update(published_at: nil)
    redirect_to admin_slider_path(slider)
  end

  member_action :delete_images, method: :delete do
    slider = Slider.find(params[:id])
    # asset = ActiveStorage::Attachment.find_by(params[:attachment_id])
    slider.images.purge_later
    redirect_to admin_slider_path(slider)
  end

  member_action :delete_image, method: :delete do
    slider = Slider.find_by(params[:name])
    slider.images[params[:id].to_i].purge_later
    redirect_to admin_slider_path(slider)
  end

  form do |f|
    f.inputs 'Slider' do
      f.input :fade, as: :boolean, label: 'carousel-fade'
      f.input :name
      f.input :captions, label: 'Captions. One image - one slogan - one line. The string is separated by "\r\n"'
      f.input :color, label: 'Captions color'
      f.input :interval, input_html: { value: f.object.interval || 5000 }
      f.input :dark, input_html: { value: f.object.dark || 0.2 }
      f.input :images, as: :file, input_html: { multiple: true }
    end
    f.actions
  end
  show do |t|
    attributes_table do
      if t.images.attached?
        t.images.each_with_index do |img, index|
          span do
            link_to delete_image_admin_slider_path(index), method: :delete do
              image_tag img.variant(resize_to_limit: [100, 100])
            end
          end
        end
      end
      row :name
      row :created_at
      row :updated_at
      row :published_at
      row :fade
    end
    para 'Click the preview to delete the image.'
  end
end
# app/controllers/slider_controller.rb
class SliderController < ApplicationController
  def index
    @slider = Slider.published.take
  end
end
# app/models/slider.rb
class Slider < ApplicationRecord
  has_many_attached :images, dependent: :purge_later
  scope :published, -> { where.not(published_at: nil) }
  scope :unpublished, -> { where(published_at: nil) }
end
# app/helpers/slider_helper.rb
module SliderHelper
  def slider_present?
    @slider&.images&.attached?
  end

  def slider_dark
    @slider.dark
  end

  def slider_interval
    @slider&.interval || 5000
  end

  def carousel_fade?
    'carousel-fade' if @slider.fade == true
  end

  def captions
    @slider&.captions&.split("\r\n")
  end

  def caption_color
    @slider.color
  end
end
# app/views/slider/index.html.erb
<div class="container pt-5">
<% if slider_present? %>
<style>
.carousel-item:after {
    background: rgba(0, 0, 0, <%= slider_dark %>);
}
.carousel-caption h3 {
    color: <%= caption_color %>;
}
</style>
	<div id="SliderOnRails" class="carousel slide <%= carousel_fade? %>" data-bs-ride="carousel" data-bs-interval="<%= slider_interval %>">
		<div class="carousel-inner">
			<% @slider.images.each_with_index do |img, index| %>
				<div class="carousel-item<%= ' active' if index == 0 %>">
					<%= image_tag(img, class:"d-block w-100") %>
					<div class="carousel-caption d-none d-md-block">
						<H3><%= captions[index] %></H3>
					</div>
				</div>
			<% end %>
		</div>
		<button class="carousel-control-prev" type="button" data-bs-target="#SliderOnRails" data-bs-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="visually-hidden">Previous</span> </button>
		<button class="carousel-control-next" type="button" data-bs-target="#SliderOnRails" data-bs-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="visually-hidden">Next</span> </button>
	</div>
<% else %>
	<div class="alert alert-warning" role="alert">
		No slides to display.
	</div>
<% end %>
</div>

Пожалуй это все. Готово:

$ rails webpacker:compile
$ rails s

Открываем в браузере http://localhost:3000 .

Как видите, вьюха содержит у меня пару стилей, формируемых динамически, что для Rails несколько "не по фэншую"; но в контексте демонстрационного приложения, пожалуй, подобное приемлемо. Дело в том, что компилируемые Rails Asset Pipeline файлы используются в качестве статических ресурсов, в большинстве случаев компиляция происходит либо при обновлении исходников (в режиме разработке), или во время развертывания (production mode).

Открываем http://localhost:3000/admin и логинимся:

User: admin@example.com
Password: password

Все работает?

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

Редактируем db/seeds.rb, убирая в самом конце if Rails.env.development? : напоминаю в очередной раз, приложение работает на Heroku в режиме продакшн, и без данной редакции вы не сможете установить дефолтные значения входа в панель управления activeadmin (об этом далее).

Вам понадобятся git и скрипты Heroku CLI, подробнее здесь (или много еще где). Далее:

git add .
git commit -m "initial commit"
heroku create
git push heroku master
heroku run rake db:migrate
heroku run rake db:seed

Логинимся и быстро меняем в панели управления данные для входа, в том случае, разумеется, если не сделали этого сразу, редактируя seeds.rb. В случае любых проблем открываем и внимательно рассматриваем содержание консоли:

$ heroku run rails console

Хотя, если все сделаете правильно, проблем быть не должно, проверял. В любом случае, удачи. Пишите, если что. :))

Комментарии (0)