Ruby
Rails
Bootstrap

Ajax でも Rails の flash メッセージを表示したい!

More than 3 years have passed since last update.


概要

通常 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'>&times;</a>
<div>
#{decodedMessage}</div>
</i>
"""

index++

return



実行例

magica_system.png

でた! ✌ ('ω' ✌ )三 ✌ ('ω') ✌ 三( ✌ 'ω') ✌


参考