概要
通常 Rails の flash メッセージは、アクションごとに HTML をレンダリングする場合にしか使用できません。
Rails 側の各アクションで flash メッセージの設定を行う必要があるからです。
つまり、Ajax 通信が終了 (complete) した際に flash メッセージを表示する手段は、デフォルトでは用意されていません。
一応、サーバ側で flash メッセージを表示するための JavaScript を用意して、それをレスポンスとして返すなどの方法はあります。
それでも Ajax 通信 (JSON リクエスト) でも flash メッセージを表示したいよぉ!
というわけでその方法を調べてみました。
方法
Rails 側ではレスポンスヘッダ ('X-' で始まるカスタムヘッダ) に flash メッセージを格納してレスポンスを返します。
そして、JavaScript 側ではそれを取り出すことで実現できました。
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
after_action :flash_to_headers
private
def flash_to_headers
return unless request.xhr?
response.headers['X-Flash-Messages'] = flash_json
# ページをリロードした際に flash メッセージが残ってしまうのを防ぐ。
flash.discard
end
def flash_json
flash.inject({}) do |hash, (type, message)|
# XSS 対策を施す。
message = "#{ERB::Util.html_escape(message)}"
# 日本語のメッセージをレスポンスヘッダに含めるために URL エンコードしておく。
message = URI.escape(message)
hash[type] = message
hash
end.to_json
end
end
/app/controllers/magicas_controller.rb
class MagicasController < ApplicationController
respond_to :json, only: [:update]
def update
@magica = Magica.find(params[:id])
@magica.update!(update_params)
flash[:notice] = '更新しました!'
flash[:warning] = '更新しました?'
flash[:error] = '更新できず…orz'
respond_with @magica
end
end
/app/views/layouts/application.html.slim
doctype html
html lang="ja"
head
meta name="viewport" content="width=device-width, initial-scale=1.0"
title Magica System
- description_content = content_for?(:description) ? yield(:description) : "Bootstrap App"
meta name="description" content=description_content
= stylesheet_link_tag "application", media: 'all', 'data-turbolinks-track' => true
= javascript_include_tag "application", 'data-turbolinks-track' => true
= csrf_meta_tags
= yield(:head)
body class=body_class
= render 'layouts/navigation'
.js-main-content.container
/ ここに flash メッセージを表示する。
.js-flash
= render 'layouts/messages'
= yield
/app/assets/javascripts/flash.js.coffee
# Underscore.js (あるいは Lo-Dash) を導入していることを前提とする。
$(document).ajaxComplete (event, xhr, settings) ->
flash = $.parseJSON(xhr.getResponseHeader('X-Flash-Messages'))
return if _.isEmpty(flash)
$flash = $('.js-flash')
$flash.empty()
index = 1
_.each flash, (message, type) ->
alertType =
switch type
when 'notice'
'success'
when 'warning'
'warning'
when 'error'
'danger'
else
'info'
alertId = "js-alert-#{index}"
decodedMessage = decodeURIComponent(message)
$flash.append """
<div class='alert alert-#{alertType}' data-alert-id='#{alertId}'>
<a class='close' data-target='[data-alert-id=#{alertId}]' data-dismiss='alert'>×</a>
<div>#{decodedMessage}</div>
</i>
"""
index++
return
実行例
でた! ✌ ('ω' ✌ )三 ✌ ('ω') ✌ 三( ✌ 'ω') ✌