トップ画面をスライダー形式に変更

2021.6.2  応用編9

 

 <目標>

  • ブログのトップ画像は複数枚の画像が一定間隔で切り替わるようにする
  • 切り替わる画像は、管理画面でアップロードと削除ができるようにする
  • faviconやog-imageに関しても、個別に削除できるようにする
  • main_imagesには、複数の画像を一度に登録できるようにする

 

エラーが出た点

カスタムバリデータ

もともとの実装だと、faviconとog-imageの保存は可能だが、複数の画像を保存した時にエラーになる。

こんな感じ

f:id:mmm_st:20210605124338p:plain

なぜか確認した...

f:id:mmm_st:20210605124508p:plain

valueに値は入っている。byte_sizeがだめなのか?

まず、bute_sizeって何か?

→アップロードされる画像のデータ量バイト数..?

(schema.rb)

create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false #これ
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

 

違いを見てみる

byebug で確認

faviconとog-image→保存可能

main_images→エラー。違いは複数選択しているところか...?

f:id:mmm_st:20210605131558p:plain

 

f:id:mmm_st:20210605131611p:plain

画像を複数選択したときのvalue.byte_size対応がない...?

(attachment_validator.rb)

class AttachmentValidator < ActiveModel::EachValidator
include ActiveSupport::NumberHelper

def validate_each(record, attribute, value)
return if value.blank? || !value.attached?

has_error = false

if options[:maximum]
has_error = true unless validate_maximum(record, attribute, value)
end

if options[:content_type]
has_error = true unless validate_content_type(record, attribute, value)
end

record.send(attribute).purge if options[:purge] && has_error
end

private

def validate_maximum(record, attribute, value)
if value.byte_size > options[:maximum] #ここでエラーが出ている
record.errors[attribute] << (options[:message] ||
"は#{number_to_human_size(options[:maximum])}以下にしてください")
false
else
true
end
end

def validate_content_type(record, attribute, value)
if value.content_type.match?(options[:content_type])
true
else
record.errors[attribute] << (options[:message] || 'は対応できないファイル形式です')
false
end
end
end

 

ここでギブアップ。回答を確認した。

考え的には間違っていなかった。複数選択した時にvalueにbyte_sizeカラムがないのでNoMethodErrorが発生している。

元のバリテーションだと、複数の画像を選択した時にbyebugで確認したvalueの中身のように正しいデータが引き渡されていない。

→一枚選択の場合と、複数選択の場合に分けて実装する必要がある。

 

class AttachmentValidator < ActiveModel::EachValidator
include ActiveSupport::NumberHelper

def validate_each(record, attribute, value)
return if value.blank? || !value.attached?
valueが空欄ならtrue あればfalse attachmentedがないならtrue あるならfalse
#valueが空欄またはattachedがなければ以下を返す。has_error=falseを返して終了。
valueがないので、判断する必要なし。
valueあればreturnは実行されないので下まで実行される。

has_error = false

if options[:maximum] #もし画像が選択されたら
 if value.is_a?(ActiveStorage::Attached::Many) #複数の画像が保存された時
value.each do |one_each|
unless validate_maximum(record, attribute, one_value)
#value一つ一つにpeivateで作成したメソッドを当てていく
has_error = true #unless文なので、↑がfalseだったら代入する
break #ループする分から抜け出す。falseが出たら次のvalueに行かずエラー出す。
end
end
else #一枚の時
has_error = true unless validate_maximum(record, attribute, value)
#validate_maximumがtrueであれば、文の最後にあるfalseが返ってくる。
→unless文なので、false=trueになるから、has_error = true になる。
end
end

if options[:content_type]
if value.is_a?(ActiveStorage::Attached::Many)
value.each do |one_value|
unless validate_content_type(record, attribute, one_value)
has_error = true
break
end
end
else
has_error = true unless validate_content_type(record, attribute, value)
end
end

record.send(attribute).purge if options[:purge] && has_error
end

private

def validate_maximum(record, attribute, value)
if value.byte_size > options[:maximum] #bite_sizeがmaximumより大きかったら
エラーを末尾に追加する
record.errors[attribute] << (options[:message] || "は#{number_t
o_human_size(options[:maximum])}以下にしてください")
false
else
true
end
end

def validate_content_type(record, attribute, value)
if value.content_type.match?(options[:content_type])
true
else
record.errors[attribute] << (options[:message] || 'は対応できないファイル形式です')
false
end
end
end

 

参照サイト

【Rails 5.2】 Active Storageの使い方 - Qiita

ActiveStorageについて|moeno|note

 

今回の課題のポイント

画像削除の処理はparamsで条件分岐をせずにActiveStorage::attachmentから取得していること。

paramsを用いると、

@site.favicon.purge if params[:status] == 'favicon' 

という感じで全ての要素に対して実装しなければならなくなる。

actuve_storage_attachmentsテーブルにはrecord_typeカラムがある。=idが一意になっている。これを利用して削除対象のリソースを取得する。

 (app/controllers/admin/site/attachments_controller.rb)

class Admin::Site::AttachmentsController < ApplicationController
def destroy
authorize(current_site)
image = ActiveStorage::Attachment.find(params[:id])
image.purge
redirect_to edit_admin_site_path
end
end

 

f:id:mmm_st:20210610164634p:plain

 

swiper

jQueryプラグイン

CSSとJSを使用することで、画像などをスライドできる機能を実装するもの。

↓このサイトを見るとイメージが湧きやすい

Swiper公式サイトのデモ

ガリガリコード

 

導入方法

RailsでSwiperを導入する方法(Swiperは2020年7月にバージョンアップし、従来と設定方法が変わりました!) - Qiita

Getting Started With Swiper

Swiperをインストールする

今回はnpmでインストールした

$ npm install swiper

nodes_modules配下のディレクトリにファイルが追加される

 

f:id:mmm_st:20210607194810p:plain

 

追加されたファイルを読み込む

(assets/javascripts/application.js)

//= require swiper/swiper-bundle.js
//= require swiper.js #今回はこれいらない

(assets/stylesheets/application.css.scss)

@import 'swiper/swiper-bundle';

 

node_modulesをassetsに追加する

(config/initializers/assets.rb)

Rails.application.config.assets.paths << Rails.root.join('node_modules')

 

HTML

(app/views/layout/_header.html.slim)

header
.swiper-container #クラスを指定する
.swiper-wrapper
- if current_site.main_images.present?
- current_site.main_images.each do |main_image|
= image_tag url_for(main_image), class: 'swiper-slide'
- else
= image_tag '/images/cover.jpg', class: 'swiper-slide'
.container.blog-title
h1 = link_to current_site.name, root_path
p.lead = current_site.subtitle

$(document).ready(function() { ❶
new Swiper('.swiper-container', { ❷
loop: true, ❸
autoplay: { ❹
delay: 3000, ❺
},
})
})

❶画像などを除いて、HTML=DOMの読み込みが終わったらfanction()の中の処理を実行する

❷クラス指定

❸最後に達したら先頭に戻る

❹自動的にスライドを開始(再生)させる

❺スライド間の感覚をミリ単位で指定。(今回は3秒)

参照

jQueryの基本 - $(document).ready - Qiita

スライダープラグイン Swiper(v5)の使い方 / Web Design Leaves

 

ActiveStorage

variantメソッド   processedメソッド

(app/views/admin/sites/edit.html.slim)

 
= simple_form_for [:admin, @site], url: admin_site_path do |f|
省略
 
= f.input :main_images, as: :file, input_html: {multiple: true},
hint: 'JPEG/PNG (1200x400)' #複数アップロードのオプション htmlの属性なので、
「input_html:」オプションをつけて指定している
 
- if @site.main_images.attached?
.main_images_box
- @site.main_images.each do |main_image|
.main_image
= image_tag main_image.variant(resize:'300x100').processed
= link_to '削除', admin_site_attachment_path(main_image.id),
method: :delete, class: 'btn btn-danger'

variant : 

(app/models/site.rb)モデルでhas_many_attachedなどを指定して、

class Site < ApplicationRecord
 
has_many_attached :main_images
 
validates :main_images, attachment: { purge: true, content_type:
%r{\Aimage/(png|jpeg)\Z}, maximum: 524_288_000 }

view側でこのようにサイズをその都度指定できる。

variant(resize:'300x100').processed

 

processed : すでにそのサイズで保存されている画像であれば、変換処理は行われず、即時にURLが返される。

 

参照

Active StorageのVariantの指定方法いろいろ - Qiita

 

 

 

 

 

(app/views/layouts/application.html.slim)

赤文字の部分bodyの下にあったのでheadbに追加

doctype html
html
head
= render 'layouts/meta_tags'
= stylesheet_link_tag 'application', media: 'all'
= yield 'stylesheets'
= javascript_include_tag 'application'
body
= render 'layouts/header'
section.container.page-content
省略

 

purgeメソッド

ファイルを削除したいときに使用する

(app/controllers/admin/site/attachments_controller.rb)

class Admin::Site::AttachmentsController < ApplicationController
def destroy
authorize(current_site) #権限確認
image = ActiveStorage::Attachment.find(params[:id])
image.purge
redirect_to edit_admin_site_path
end
end

 

validatesでpurge: trueにしておく必要がある

 

validates :og_image, attachment: { purge: true, content_type:
%r{\Aimage/(png|jpeg)\Z}, maximum: 524_288_000 }
validates :favicon, attachment: { purge: true, content_type:
%r{\Aimage/png\Z}, maximum: 524_288_000 }
validates :main_images, attachment: { purge: true, content_type:
%r{\Aimage/(png|jpeg)\Z}, maximum: 524_288_000 }

 

 

ActiveStorage::attachmentについて

Active Storageでキー名を指定するには - Qiita

 

 

 

RSpec

attach_fileメソッド

アップロードのinput要素にテスト用画像を添付することができる。第1引数:locator
第2引数:アップロードする画像のパス
第3引数:オプション

参照

Method: Capybara::Node::Actions#attach_file — Documentation for capybara (3.35.3)

Rails RSpec による画像添付結合テスト - Qiita

 

capybaraのlocatorについて


capybaraにおける要素の指定は「locator文字列」によって操作対象の要素を指定する。
locatorには「id」「name」「innerHTML(一般的にHTMLに含まれているタグ名のこと)」などを指定することができる。

[locatorに指定できる値]
・対象の要素のid属性の値
・対象要素のname属性の値
・対象要素のinnerHTMLの値
・対象の要素に対応する<label>の値(innerHTML)

 

例えば
attach_file ファイルセクレタにファイルを設定する
公式によるとattach_file ([locator], paths, options)
→attach_file (input要素のname属性とか、id属性とか, 画像へのパス, オプションあればオプション)って感じの構造になる。


admin_sites_spec.rbの

attach_file('site_main_images', 'spec/fixtures/images/runteq_man_top.jpg')

attach_file('site[main_images]', 'spec/fixtures/images/runteq_man_top.jpg')

こっちてもいけた


最初は'site_main_images'がdivタグのclass属性を指定してるのか謎だったけど、locatorの仕組みを理解したので納得できた!!!

site_main_images input要素のidを指定している。

site[main_images] input要素のnameを指定している。

 参照

Capybaraメモ - Qiita

 

%w()

配列を作成する。今回は画像を複数枚これで指定した。

Rubyで%記法(パーセント記法)を使う - Qiita