No_ideaのわからん日和

✏️...プログラミングが全然出来ない奴がわからんわからん言いながら様々な構文(記述方法やエラー構文など)と奮闘しながら成長していく成長記録です🐢

Ruby on Rails: インクリメンタルサーチと非同期通信...chapter16

今回は非同期通信、インクリメンタルサーチをやりたいと思います。

jQueryを使用します。

 

jQueryとは

Javascriptを扱いやすくしたものです。

Javascriptで10行くらいかけるものを1,2行で済ませてくれるのがjQueryです。

また、ブラウザによってJavascriptが動かない場合がありますが、jQueryではどのブラウザでも同じ動きをします。

 

 

Gemfile

jQueryが使用できるようにgemをインストールします。

$bundle install

app/javascript/users.jsファイル作成

application.js

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require('jquery')

require("users.js")

$rails s

これだけではエラーが出ました。

なので、解決策を検索。

https://qiita.com/masahisa/items/eaacb0c3b82f4a11fc13

$yarn add jquery

config/webpacker/enviroment.js

const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
})
)

module.exports = environment

$rails s

これでエラーがなくなりました。

 

yarnこれはRailsでいうところのGemみたいな役割をするJavascript版らしいです。

 

 

routes.rb

resources :users, only: [:edit, :update, :index] do
 collection do
  get 'users/user_page', to: 'users#user_page'
 end
end

indexアクションのルーティングを追加しました。

 

users_controller.rb

後ほど検索した結果をkeywordをキーとして受け取るようにします。

なのでparamsで受け取った検索がもし空ならnilで返します。

User.where()でUserモデルを使用し、whereでDBに検索をかけることができます。

検索したいのはnicknameなので"nickname"とします。

また"nickname LIKE?"とすることで"あいまい検索"ができるようになります。

?←これはプレースホルダーというもので、第2引数("%#{params[:keyword]}")を?のところに置き換えることができます。

つまりaaaというユーザーを検索したい時、「a」と入力するだけでaのつくユーザーを探してくれます。

しかし、aがどこについてもいいという検索は避けたいので(abc, bac, cbaなど)、ワイルドカードを使用し検索条件を設定します。

ワイルドカードは_と%があります。

%...0文字以上の任意の文字列

_...任意の1文字

%〇〇とするしてaのつくユーザーを検索します。

aのつく候補として(aaa, abc, bac, cba,aa,ab,ba)がいるとすると、aと入力すると

候補(aaa, cba, aa, ba)が出てきます。これは〇〇aとつくユーザーを検索できます。

次に〇〇%で検索してみます。

候補(aaa, abc, aa, ab)が出てきます。これはa〇〇とつくユーザーを検索できます。

次に_〇〇を試してみます。

候補(aa, ba)が出てきます。つまり○+aを検索できます。

%と違い_(アンダーバー)の数今回は1文字分を検索しているので、cbaは検索から弾かれます。

次に〇〇_を試します。

 

候補(aa, ab)が出てきます。a+○を検索しています。

今回はaと入力するとa○やa〇〇のように頭文字に検索したものの候補がくるようにしたいので〇〇%にしたいと思います。

https://www.wakhok.ac.jp/biblion/1994/DB/subsection2.4.3.5.html 

 

.where.not(id: current_user.id)

ログインユーザー(チャット新規作成しているユーザー)が検索されないようにwhere.not()を使いidを検索できないようにします。

@users = User.where("nickname LIKE?", "#{params[:keyword]}%").where.not(id: current_user.id)

そしてインスタンス変数@usersに代入します(検索結果を代入)。

respond_to do |format|
 format.html
 format.json
end

respond_to do |format|~end

ここに記述すると、通常はhtml形式で結果を取得するが他の形式で取得したい場合に指定できます。

チャット新規作成ページへ遷移した時はhtmlで取得したいのでformat.html。

そしてjsonを介してデータ(検索をかけたいユーザー)を取得する時はjsonを使用したいのでformat.jsonとしています。

 

app/views/users/index.json.jbuilder

json.array! @users do |user|
 json.id user.id
 json.nickname user.nickname
end

index.json.jbuilderファイルを作成します。

今回はjsonを使用したいと思います。

jsonとは"Javascript Object Notation"の略です。

〇〇.json.jbuilderファイルはjson形式を作成しやすくするために作成するファイルです。

このファイルに記述することによってjson形式の文字列のデータを作成することができます。

 

json.array!

インスタンス変数が複数あるときに使用します。

@usersの中身はユーザー情報が入っています。

例えば、aaaというユーザーの場合

https://gyazo.com/b524ee40f7612abfb59948b5a1ea9bf8

idやnickname、emailなど複数の情報が入っています。

 

json.キー名 "値"

jsonに変換したい(データ渡したい)のはidとnicknameなので、usersコントローラーで作成したindexアクションの中の処理で作成したインスタンス変数(@users)から繰り返し処理をして|user|一つずつuserを取り出ます。

そしてuser.idとuser.nicknameと記述してidとnicknameを取り出し、json形式でidとnicknameを使用できるようにidとnicknameをキー名として値を渡しています。

 

https://pikawaka.com/rails/jbuilder(jbuilderの使い方について)

 

 

app/views/chat_groups/new.html.erb

全体のコード:

https://gyazo.com/c420b3226691d57a3c0b2af0bf97eb55

検索フォーム部分のコード:

 https://gyazo.com/e43b87bc7504d87c3d9199a0a504be0a

検索した結果を

<div id="chat-group-new__member__search"></div>

↑部分に表示します。

 

https://gyazo.com/1a7f6fd9138f820a6cad55766ae69d8e

メンバー追加後:

<% @chat_group_new.users.each do |user| %> ~ <% end %>

ここのコードは検索して追加したユーザーが表示されるところです。

このコードが出現したり削除をクリックしたときに消えたりするようにします。

 

users.js

https://gyazo.com/385393aa49a1d79775ebc02bc07ed207

https://gyazo.com/9e88a345327134d97104af055aa3b903

$(function(){〇〇〇〇});

jQuery(document).ready(function(){〇〇〇〇});の略。

これはHTMLを読み込みが終わったら実行するという意味です。

 

function addUser(user){
 let html = `
  <div class="chat-group-new__member__search__users>
   <p class="chat-group-new__member__search__users--name">${user.nickname}</p>
   <div class="chat-group-new__member__search__users--add" data-user-id="${user.id}", data-user-name="${user.nickname}">
    追加
   </div>
  </div>
   `;
 $("#chat-group-new__member__search").append(html);
};

まず、関数を作成します。

関数とは、ある処理をまとめておけるようなものです。

関数を使用することでコードが見やすくなります。

関数を作成する時は、functionを最初に記述します。

続いてfunctionに任意の関数名を指定します。今回はaddUserにします。

()内に引数をします(引数user)。

ここに引数を指定しておくと、関数を呼び出されてた時に値が渡されている場合にその値を代入してくれます。後ほど、関数を呼び出すコードを記述します。

letは変数を宣言する時に使用します(これは変数ですよと分かりやすくしています)。

letを使用すると変数を再宣言できなくなります。

let a = '初期設定';

a = '再代入可能';

let a = '再宣言不可';

今回は変数htmlをletで宣言しました。

$(〇〇).append(html);で先ほど宣言した変数htmlを〇〇へ追加します。

 

function notUser(){
 let html =`
  <div class="chat-group-new__member__search__users">
   <p class="chat-group-new__member__search__users--none">一致するユーザーは見つかりません。</p>
  </div>`;
 $("#chat-group-new__member__search").append(html);
};

関数を作成します。

関数名notUserにし、今回は引数はなしなので()のみです。

letを使用し変数を宣言します。

そして、appendメソッドを使用し、ユーザーが見つからない時にid="chat-group-new__member__search"へ追加する関数を作成しておきます。

 

function removeUser(nickname, id){
 let html = `
  <div class="chat-group-new__add__right__user">
   <p class="chat-group-new__add__right__user--name">${nickname}</p>
   <a class="chat-group-new__add__right__user--remove-js add__user__remove--btn" data-user-id="${id}" data-user-name="${nickname}">削除</a>
  </div>`;
 $("#chat-group-new__add__right__js").append(html);
};

検索で追加したユーザーを表示するための処理をする関数を作成します。

上記と同様にappendで変数htmlの内容をid="chat-group-new__add__right__js"へ追加します。

 

function addMember(userId){
let html=`
<input name="chat_group[user_ids][]" type="hidden" value="${userId}" id="chat_group_user_ids_${userId}"/>`;
$("#chat-group-new__add__right__js").append(html);
};

https://gyazo.com/9f1db133b1cf2fa1d6ebc393af435825

ユーザーを追加した後に追加したユーザーを登録できるようにinputメソッドにユーザーidを入れて変数htmlに代入します。

そして、変数htmlをid="chat-group-new__add__right__js"へ追加できるように関数を作成します。

追加したユーザーの値を引き渡されるように引数を記述します(userId)。

上記のremoveUserは表示をするだけです。

このinputがないと新規作成ボタンを押した時にデータが保存できません。

 

 

$("#chat-group-new__members__right__field__js").on("keyup", function(){
 var input= $("#chat-group-new__members__right__field__js").val();
 $.ajax({
  type: 'GET',
  url: ' /users',gg
  data: {keyword: input},
  dataType: 'json'
 })
 .done(function(users){
  $("#chat-group-new__member__search").empty();
  if(users.length !==0){
   users.forEach(function(user){
   addUser(user);
  });
  } else if(input.length == 0){
   return false;
  } else {
   notUser();
  };
 })
 .fail(function(){
   alert("通信エラー");
 });
});

$(〇〇).on("keyup",function(){処理内容});

.on()これを使うと様々なイベント処理(マウス操作やキーボード入力、フォームを送信した時、画面をスクロールした時など)をすることができます。

https://www.sejuku.net/blog/38774

今回はkeyupを使用します。

keyupはキーボードを入力した時に発火するように(処理されるように)しています。

何を発火するのかというと、funciton(){var input=$("#chat-group-new__members__right__field__js").val()〜.fail(fucntion(){alert("通信エラー")});

};までです。

まず、varを使用し変数を宣言します。varはletとは異なり、値の再代入や再宣言ができます。

変数inputに.val()を使用して(ここに記述された値を取得します)、id="chat-group-new__members__right__field__js"を代入(つまり、検索をかけた時にその検索した内容の値を取得できます)。

↓ここのフォームに入力した値を取得

 

$.ajax({})

これはこれを記述することで画面を切り替えずに一部の情報をサーバーに送信して、情報を受け取ることができます。

type: リクエストタイプ(HTTP動詞)を決めます。GETかPOSTを選べますが、検索結果をただ表示したいだけなのでGETにしました。

url:送信先を指定します。

users GET    /users(.:format)              users#index

usersコントローラーのindexアクションへ送信したいのでurlである(/users)を記述します。

data:送信先へ送るデータをキー/値のペアで設定します。

keywordをキーとしてinputを値として送ります(つまり先ほどの変数inputで入力して取得した値)。

このデータはusersコントローラーのindexアクションへ渡されます。

dataType:サーバーから返ってくるデータの型を指定できます。

種類はjson,xml,htmlなどがあります。

今回はjsonを使用したいのでjsonを記述。

 

.done(処理内容)){

ここにデータの送受信が成功したときの処理を書きます。

$("#chat-group-new__member__search").empty();

.empty()を使用し、id="chat-group-new__member__search"の中を空にします(< id="chat-group-new__member__search">~</div>内)。

これにより、追加を押したら表示が消えます。

▼チャット参加メンバー▼の上のaaa 追加が消えます。

 

if(users.length !==0){
users.forEach(function(user){
addUser(user);
});

if文を使用してusers.lengthは検索をかけた時にユーザーが何件ヒットするかによって分岐しています。

ユーザーが0件ヒットでなければ該当があったら)、forEachメソッドを使用してヒットしたユーザーを繰り返し処理をします。

その繰り返し処理された内容を、関数(addUser(user))に値を引き渡します。

else if(input.length == 0){
 return false;
}

input.length(検索フォームに入力された値)が0ならば(入力を消した場合)、イベントを中断します。

else {
 notUser();
};

そうでなければ(ヒット件数0件)の場合は関数notUserを実行します。

.fail(処理内容))

ここにはデータの送受信を失敗したときの処理を記述します。

alert("通信エラー");

alert()メソッドを使用し、通信エラーとアラートを表示します。

 

$(document).on("click", ".chat-group-new__member__search__users--add", function(){
let userName = $(this).attr("data-user-name");
let userId = $(this).attr("data-user-id");
$(this).parent().remove();
removeUser(userName, userId);
addMember(userId);
});

$(document).on(処理内容)

このdocumentはhtml要素へ簡単にアクセスすることができます。

https://qiita.com/k152744/items/6453923550d5e4166557

console.logを使い、documentを見てみると...

https://gyazo.com/509e77874ce85173d89da109739f4ae4

今扱っているチャット新規作成ページのhtml要素を取得できてます。

そして、検索してヒットして「〇〇 追加」と表示され、この「追加」をclickしたらイベントが発火します。

let userName = $(this).attr("data-user-name");
let userId = $(this).attr("data-user-id");

変数をletで宣言します。

ここに記述してあるthisの中身↓

https://gyazo.com/b054fbfa854490c12f7e8d1c482c9429

これは追加をクリックした時のものです。

.attr()を使用し、このthisの中からdata-user-idとdata-user-nameを取得します。

そして取得した値を変数userName、userIdへ代入します。

https://techacademy.jp/magazine/26775(←.attr()について)

$(this).parent().remove();

このthisは↓

です。

.parent()は一つ上の階層の親要素を取得することができます。

つまり↓

let html = `
  <div class="chat-group-new__member__search__users>
   <p class="chat-group-new__member__search__users--name">${user.nickname}</p>
  <div class="chat-group-new__member__search__users--add" data-user-id="${user.id}", data-user-name="${user.nickname}">
   追加
  </div>
 </div>
`;

div class="chat-group-new__member__search__usersが親要素となります。

ここの要素から取得しています。

.remove()は$(〇〇).remove();の〇〇の要素を削除することができます。

よって、

↑を削除することができます。

.empty()と.remove()の違いは、emptyでは指定した要素の子要素のみが削除されます。

removeでは指定した要素の子要素まで削除されることになります。

つまりthisで取得しているものを削除したいのでremoveを使用します。

https://www.sejuku.net/blog/36261

https://uxmilk.jp/10889

 

removeUser(userName, userId);
addMember(userId);

そして変数に代入した値を引数を各関数へ渡します。

 

 

$(document).on("click", '.chat-group-new__add__right__user--remove-js', function() {
 $(this)
 .parent()
 .remove();
});

documentでhtml要素を取得し、「〇〇 削除」の削除をクリックした時にイベントを発火させます。

ここのthisは↓

https://gyazo.com/b776042932f00721eecce79ce8b30997

これを.parent()で一つ上の階層の親要素から取得します。

つまり↓

let html = `
 <div class="chat-group-new__add__right__user">
  <p class="chat-group-new__add__right__user--name">${nickname}</p>
  <a class="chat-group-new__add__right__user--remove-js add__user__remove--btn" data-user-id="${id}" data-user-name="${nickname}">削除</a>
 </div>`;

<div class="chat-grop-new__add__right__user">~</div>までを取得します。

.remove()で要素を削除します。つまりthis↓

を削除します。

 

app/views/layouts/application.html.erb

 https://gyazo.com/12fa3e76860b6a93791fa9ebe6f79585

チャット新規作成ページへ遷移した際にイベント発生しないのを防ぐためにbodyにdata-turbolinks="false" としておきます。

これでturbolinksが悪さをするのを防ぎます。

 

 

完成

https://gyazo.com/7688b4702266b4c0dbc763d75aecdd6c

 

 インクリメンタルサーチと非同期通信ができました。

 

 

 

 

 

 

 

以上です。