この記事の基本的な方針
Ajaxはなんだか難しい!は、勘違いです。一歩一歩きちんと進んでいけば、普通のことだと思えてくるでしょう。
ここではAjax非同期通信を理解するためだけの簡易なアプリを一つ丁寧に作成して、Ajax学習の基礎を完了することを目的としています。
この記事は、以下の「登録画面」「ログイン画面」「TOP画面」の3画面の簡単なアプリを元に拡張していきます
【TOP画面(ログイン前)】 【TOP画面(ログイン後)】
【登録画面】
【ログイン画面】
手を動かしながら読みたいようでしたら、以下でこの3画面アプリを手に入れてください。
$ git clone -b 超最低限のRailsアプリ(messageコントローラVer) https://github.com/annaPanda8170/minimum_rails_application.git
$ bundle install
$ bundle exec rake db:create
$ bundle exec rake db:migrate
これ自体の作り方はこちら。
想定する読み手
既に一度Railsアプリをチュートリアルやスクール等で作ったことがある方であり、JQueryの基本文法を理解している方を想定しております。
Mac使用で、パソコンの環境構築は完了していることが前提です。
取り急ぎ同期通信のメッセージ投稿機能をつくる
※本筋ではないので急ぎ足で説明します。詳しい説明が必要な方は、別記事をご覧ください(newアクションを使わず、formをindexに置くという違いがあります)。
① メッセージテーブルを作る
マイグレーションファイル
とモデル
を作るため、
$ rails g model message
を打ちます。
マイグレーションファイルに
class CreateMessages < ActiveRecord::Migration[5.2]
def change
create_table :messages do |t|
t.string :message
t.references :user, foreign_key: true
t.timestamps
end
end
end
となるように追記し、
$ rails db:migrate
します。
これを済ませたら、メッセージテーブルは完成です。一応ちゃんと出来ているかデータベースを見に行ってみましょう。
私はSequelProを使っていてこんな感じです。
モデルにテーブル同士の関係を書きましょう。これが無くても投稿できなくはないのですが、投稿した内容を引き出して扱う上で便利なので今済ませてしまいましょう。以下を追記します。
belongs_to :user
has_many :messages
②メッセージだけを投稿できるようにする
ルーティング、ビュー、コントローラを編集します。今回はindexにフォームを置くのでnewアクションはなしです。
Rails.application.routes.draw do
devise_for :users
root 'messages#index'
resources :messages, only: [:index, :create]
end
<% if user_signed_in? %>
<%= current_user.email %>
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
<%= form_with(model: @message, local: true, class: "form") do |f| %>
<%= f.text_field :message %>
<%= f.submit "投稿" %>
<% end %>
<% else %>
<%= link_to '新規登録', new_user_registration_path %>
<%= link_to 'ログイン', new_user_session_path %>
<% end %>
この時点で、localhost:3000
でもlocalhost:3000/messages
でも
が表示されるはずです。この時点ではこの投稿フォームただの飾りなので、データベースに保存できるよう中身を作ります。
コントローラは以下です。
class MessagesController < ApplicationController
before_action :to_root, except: [:index]
def index
@message = Message.new
end
def create
@message = Message.new(message_params)
@message.save
redirect_to root_path
end
private
def message_params
params.require(:message).permit(:message).merge(user_id: current_user.id)
end
def to_root
redirect_to root_path unless user_signed_in?
end
end
これで一度投稿してみましょう。
問題なさそうですね。 ## ③投稿を表示 あとはTOP画面に投稿されたものを全て表示させます。以下を追記します。class MessagesController < ApplicationController
〜省略〜
def index
@messages = Message.all
@message = Message.new
end
〜省略〜
end
〜省略〜
<% @messages.each do |m| %>
<div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
<% end %>
投稿するときに画像の上の丸い矢印が一瞬×
になって投稿が反映されると思いますが、これなしに反映されるのが非同期通信です。
④JQueryを導入し、turbolinksを削除する
turbolinksを削除する理由は、JQueryの動きを阻害する可能性があるからです。(リロードすればjsが動作するのに、リンクで移動すると動作しない、等)
Gemfile
にgem 'jquery-rails'
を加え、gem 'turbolinks'
をコメントアウトするか消し、
× gem 'turbolinks'
gem 'jquery-rails'
bundle install
しサーバの再起動します。
app/assets/javascripts/application.js
に//= require jquery
と//= require jquery_ujs
を//= require_tree .
より上に追記し、//= require turbolinks
を消します。
× //= require turbolinks
//= require jquery
//= require jquery_ujs
//= require_tree .
以下を修正します。
```erb:app/views/layouts/application.html.erb`
× <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
|
v
◯ <%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
続いて`app/assets/javascripts`に`messages.js`を作ります。`app/assets/javascripts/messages.coffee`があると作成したファイルが機能しないので削除します。
```js:app/assets/javascripts/messages.js
$(function () {
console.log("OK")
});
を書いて、ブラウザをどの画面でもいいのでリロードします。
コンソールにOKが表示されたら成功です。
これで準備は終わりです。
いよいよ本筋、非同期実装
完成品GitHub(masterではなく一つのブランチなので注意して下さい)
①投稿ボタンを押すとイベント発火させる
以下のようにjsファイルを直しフォームの投稿ボタンが押されたときにコンソールにOk
が出てくるか確認します。
function後の()にeをお忘れなく。
$(function (e) {
$(".form").on("submit", function () {
console.log("Ok")
})
});
一瞬だけ表示されてすぐに消えますね。投稿されたらTOP画面に(つまり同じ画面に)リダイレクトするのですから当然ですね。今はリダイレクトせずに投稿が反映されるようにするためにこの動きをjs内で止めます。以下に直してください。
$(function () {
$(".form").on("submit", function (e) {
console.log("Ok")
e.preventDefault();
})
});
これでOk
が残るようになりました。
②formの情報をjsで受け取り、createアクションに渡して投稿する
まずjsでformの情報を受け取る型は以下のような感じです。
$(function () {
$(".form").on("submit", function (e) {
e.preventDefault();
$.ajax({
url: (1) ,
type: (2) ,
data: (3) ,
dataType: 'json',
})
})
});
加わったのは$.ajax
のくだりですね。
4つの項目がありますが、dataType
はとりあえず'json'
でいいです。jsonとはデータの形式で、{a: b,c: d}
みたいなやつです。Rubyでいうハッシュ、JavaScriptでいくオブジェクトですね。簡単です。
(json以外にも、XMLやHTMLでもできるみたいですね)
(1)〜(3)を埋めて行きます。
(1)はcreateアクションにいくurlです。rails routes
で確認すれば一発ですね。今回の私の場合は/messages
です。
(2)は、HTTPメソッドです。createアクションに行くので、'POST'
ですね。
(3)は、検証ツールでメッセージを書き込むinputタグのname属性をみればわかります。
ありました。このように参照される値なので、{message: {message: <投稿内容> }}
のように渡せばいいですね。
では投稿内容はどうすれば良いかというとidがmessage_message
になっているので、$("#message_message").val()
で取れます。(詳しい説明は省きます)
これを埋めて、投稿完了したときにたどり着くdoneメソッドをajaxメソッドに連ねて書きます。
$(function () {
$(".form").on("submit", function (e) {
e.preventDefault()
$.ajax({
url: "/messages" ,
type: "POST" ,
data: {message: {message: $("#message_message").val() }} ,
dataType: 'json',
}).done(function (data) {
console.log("ok");
});
})
});
これで一度投稿してみましょう。データベースを見れば投稿は成功しているのがみて取れます。
以下に同期・非同期の両方でのターミナルでの状態を掲載します。
このような違いが出てますね。そしてなぜかコントローラのcreateアクションの最後のredirect_to root_path
が効かなくなりました。今コンソールにok
は見られません。コントローラに残って機能しなくなったredirect_to root_path
が阻害しているようです。これを削除してもう一度投稿すれば、コンソールにok
が見られるはずです。
このあと,投稿が完了した時に、formの値を全てなくして、投稿ボタンを蘇るようにします。
formの値をまとめてなくすには$('.form')[0].reset();
を追記します。[0]
がなぜ必要なのかはよくわかりません。
続いて投稿ボタンはerbファイルでボタンに適当にidを指定して(私は<%= f.submit "投稿", id:"bbb" %>
こうしました)、$('#bbb').prop('disabled', false);
を追記します。
全体を見てみます。
$(function () {
$(".form").on("submit", function (e) {
e.preventDefault()
$.ajax({
url: "/messages" ,
type: "POST" ,
data: {message: {message: $("#message_message").val() }} ,
dataType: 'json',
}).done(function () {
$('.form')[0].reset();
$('#bbb').prop('disabled', false);
});
})
});
これでリロードしなくでも、何回でも投稿できるようになりました。
あとは表示できるようにすればOKですね。
③投稿内容を非同期で表示させる
Ajaxでやってきたデータを扱うにはrespond_to
メソッドを使います。
createアクションの最後のredirect_to root_path
があった場所に、以下を追記します。
respond_to do |format|
format.json {render json: { ccc: @message.message , ddd: @message.user.email}}
end
メッセージ投稿内容とメッセージを送った人のEmailをそれぞれcccとdddに格納してjson形式でレンダーしますよってことですね。
あとは、doneイベント内で情報を受け取ってHTMLに整形してappendするだけです。
doneイベント内のfunction後の()
内に何か文字をおけばそこに上のjsonデータが格納されます。今回はeee
としてみました。これをコンソール出力してみます。
〜省略〜
}).done(function (eee) {
console.log(eee);
$('.form')[0].reset();
$('#bbb').prop('disabled', false);
});
〜省略〜
これで投稿してみると、コンソールで
{ccc: "ハロー", ddd: "aaa@aaa"}
大丈夫そうですね。 これがeee
の中に入っているわけですから、"ハロー"を取得するにはeee.ccc
で、"aaa@aaa"を取得するには…大丈夫ですね。
あとは、これを表示させます。appendするために
〜省略〜
<% @messages.each do |m| %>
<div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %></span><%= m.message %></div>
<% end %>
これをdivタグで囲って、適当なidをつけます。今回はaaa
としました。
〜省略〜
<div id="aaa">
<% @messages.each do |m| %>
<div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %> </span><%= m.message %></div>
<% end %>
</div>
あとは
$("#aaa").append(`<div style="margin-top: 20px;"><span style="color: red;">${eee.ddd}</span>${eee.ccc}</div>`)
を追記するだけです。
これで完成です。投稿して確認してください。
最後に再掲します。
$(function () {
$(".form").on("submit", function (e) {
e.preventDefault();
$.ajax({
url: "/messages",
type: "POST",
data: { message: { message: $("#message_message").val() } },
dataType: 'json',
}).done(function (eee) {
$('.form')[0].reset();
$('#bbb').prop('disabled', false);
$("#aaa").append(`<div style="margin-top: 20px;"><span style="color: red;">${eee.ddd}</span>${eee.ccc}</div>`)
});
})
});
<% if user_signed_in? %>
<%= current_user.email %>
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
<%= form_with(model: @message, local: true, class: "form") do |f| %>
<%= f.text_field :message %>
<%= f.submit "投稿" , id: "bbb"%>
<% end %>
<div id="aaa">
<% @messages.each do |m| %>
<div style="margin-top: 20px;"><span style="color: red;"><%= m.user.email %> </span><%= m.message %></div>
<% end %>
</div>
<% else %>
<%= link_to '新規登録', new_user_registration_path %>
<%= link_to 'ログイン', new_user_session_path %>
<% end %>
まとめ
本当に最低限です。
これを、整えて十分な状態にするための続きをまた書きます。
フォローしてお待ち下さい。