Данная дока не является переводом или адаптацией чьих-то иных усилий: как авторство, так и любые щелчки по носу по праву принадлежат лишь автору в награду за его бессонную ночь (но не за "ночную сборку", не подумайте), ушедшую на написание этой статьи и (в значительно меньшей степени) создание собственно rails-app. Описанная далее последовательность команд выверена вполне добросовестно, придерживайтесь ее: приложение тщательно оттестировано.
Примечание. По ходу дела, если необходимо, можно сверяться с гитхабом автора. В любой момент, если что-либо не получается, вы можете клонировать полностью готовое к работе приложение и продолжить уже с ним:
$ git clone https://github.com/cmirnow/slider-on-rails.git
$ cd slider-on-rails
$ bundle install
$ yarn install --check-files
$ rails s
Да, и сразу о функционале. Слайдер имеет два способа анимации смены изображений, возможность регулирования временного интервала показа слайда, также изменение стиля отображения "светло/темно" в границах от 0.0 до 0.9, плюс возможность добавить для каждого изображения (или не для каждого) текстовой слоган, отображаемый в левом верхнем углу, цвет текста слогана также доступен для выбора. Добавляем следующим образом: один слоган - одна линия - один слайд, пустая строчка означает отсутствие текста для соответствующего изображения:
Панель управления автоматически создает превью загруженных изображений; возможно как удаление всех слайдов одним кликом, так и удаление любого из них кликом по превью. Возможно создавать любое количество слайдеров, каждый со своими картинками и опциями; переключение отображения реализовано буквально двумя-тремя кликами мыши (возможно и любое количество учетных записей админов, разумеется). После создания слайдера и добавления изображений не забудьте его опубликовать, нажав соответствующую кнопку.
Слайдер изображений работает во многом благодаря фреймворку 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, на момент публикации это по умолчанию).
Откроем 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:
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: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
Как видите, вьюха содержит у меня пару стилей, формируемых динамически, что для Rails несколько "не по фэншую"; но в контексте демонстрационного приложения, пожалуй, подобное приемлемо. Дело в том, что компилируемые Rails Asset Pipeline файлы используются в качестве статических ресурсов, в большинстве случаев компиляция происходит либо при обновлении исходников (в режиме разработке), или во время развертывания (production mode).
Открываем в браузере:
http://localhost:3000/admin
и логинимся:
User: Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
Password: password
Все работает, не правда ли?
Напоследок вернусь к деплою слайдера на Heroku: поверьте, тоже без особых проблем. Но ряд деталей имеет значение, и в первый раз немудрено изрядно поплутать в трех соснах... итак, что еще необходимо в контексте деплоя слайдера на Heroku, помимо описанного выше:
Редактируем db/seeds.rb, убирая в самом конце if Rails.env.development? : напоминаю в очередной раз, приложение работает на Heroku в режиме продакшн, и без данной редакции вы не сможете установить дефолтные значения входа в панель управления ActiveAdmin (об этом далее).
Если нужно, чтобы работала функция восстановления пароля - отредактируйте production.rb так, как описано в самом конце этого руководства. Кроме того, помимо уже названных выше Config Vars, в консоль Heroku придется добавить еще три. Будьте внимательны.
Вам понадобятся git и скрипты Heroku CLI, подробнее здесь (или много еще где). Далее:
git add .
git commit -m "initial commit"
heroku create
git push heroku main
heroku run rake db:migrate
heroku run rake db:seed
Быстро логинимся и меняем в панели управления данные для входа, в том случае, разумеется, если не сделали этого сразу, редактируя seeds.rb (в этом случае можно не спешить, но все равно поменяйте пароль). В случае любых проблем открываем и внимательно рассматриваем содержание консоли:
$ heroku run rails console
Хотя, если все сделаете правильно, проблем быть не должно, проверял. В любом случае, удачи. Пишите, если что.
Комментарии в блоге