0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】Amazon PA API v5.0 で書籍検索(非同期通信)

Posted at

こちらの記事の続きです。

前回はAPI連携後にviewファイルを表示させていましたが、今回はajax通信でデータを取得して書籍検索結果を表示します。
(理由はこの方がかっこいいからです笑)

完成イメージ

ee6e3c4e9c998d33288b32bff916570d.gif

(cssで見た目を少し整えたので、前回の記事と少し見た目は変わってます)

処理の流れ

検索フォームに入力して検索ボタンを押す(submitする)と以下のように処理されます

1. 検索フォームのキーワードを取得
2. json形式でデータ(キーワード)を持たせて、指定のURLにリクエスト(ajax通信)
3. コントローラでAPI連携の処理をし、jsonでデータを渡す
4. 返ってきたデータを使ってhtmlを書き換えて検索結果を表示

各ファイル

コントローラ

コントローラにjsonでレスポンスを返すという記述が必要です。
respond_to 〜のところ

searches_controller
class SearchesController < ApplicationController
  before_action :call_client, only: :index
  
  def index
    si = @client.search_items(keywords: keyword_params, SearchIndex: "Books")
    @items = si.items
    respond_to do |format|
      format.html
      format.json
    end
  end
  
  private
  
  def call_client
    require 'paapi'
    @client = Paapi::Client.new(access_key: Rails.application.credentials[:pa_api][:access_key_id],
                                secret_key: Rails.application.credentials[:pa_api][:secret_key],
                                market: :jp,
                                partner_tag: Rails.application.credentials[:pa_api][:associate_tag])
  end
  
  def keyword_params
    params.require(:keyword)
  end

end

jbuilder

views/[コントローラ名と同じ名前のディレクトリ]の中にindex.json.jbuilderを作成します。
今回はcontrollers直下にsearches_controller.rbを作っているので、views/searches/index.json.jbuilderとなります。

今回jsonデータは配列になっているので、各要素をどのように加工するか書く必要があります。取り出すデータは画像、タイトル、著者、出版社の情報にしています。

index.json.jbuilder
json.array! @items do |item|
  json.image_url item.image_url
  json.title item.title
  json.authors item.authors
  json.publisher item.publisher
end

jsファイル

api-search.js
// jQueryを使って記述します

$(function() {

  // 検索結果を表示する関数
  let search_list = $("#books")
  function appendBook(image_url, title, author, publisher) {
    const html = `<div class="search-book-content">
                  <div class="book-image">
                    <img class="book-image" src="${image_url}">
                  </div>
                  <div class="right-content">
                    <div class="book-info">
                      <div class="book-info__title">
                        ${title}
                      </div>
                      <div class="book-info__author">
                        ${author}
                      </div>
                      <div class="book-info__publisher">
                        ${publisher}
                      </div>
                    </div>
                  </div>
                </div>`
    search_list.append(html);
  }

  // 検索中の表示関数
  function dispLoading(msg){
    let dispMsg = "<div class='loadingMsg'>" + msg + "</div>";
    $("body").append("<div id='loading'>" + dispMsg + "</div>");
  }

  // 検索中の表示を消す関数
  function removeLoading(){
    $("#loading").remove();
  }

  // 検索ボタンを押した時にイベント発火
  $("#book-search-form").on("submit", function(e) {
    e.preventDefault();
    const keyword = $("#keyword").val();
    dispLoading("検索中...");

    // ajax通信の詳細
    $.ajax({
      url: '/searches',
      type: 'GET',
      data: {'keywords': keyword},
      dataType: 'json',
      timeout: 10000
    })
    
    // ajaxがうまくいったとき
    .done(function(items){
      $(".search-book-content").remove();
      items.forEach(function(item){
        let image_url;
        let author;
        let publisher;
        if (item.image_url == null) {
          image_url = `/assets/no_image-267acfcb976ba4942183409c682b62a768afb48c328b6ba60de7b57fd83c3b56.png`
        } else {
          image_url = item.image_url
        }
        if (item.authors.length == 0) {
          author = '不明な作者'
        } else {
          author = `${item.authors[0]}`
        }
        if (item.publisher == null) {
          publisher = '不明な出版社'
        } else {
          publisher = item.publisher
        }
        let title = item.title
        appendBook(image_url, title, author, publisher);
      })
    })
    // ajaxが失敗した時
    .fail(function(){
      alert("検索に失敗しました");
    })
    // ajaxが成功しても失敗しても共通の処理
    .always( function(data) {
      // Lading 画像を消す
      removeLoading();
    });
  });
})

コードを読めばわかると思いますが、処理の流れを書いてみます。


■ 関数の定義

  1. appendBook

    本のデータを元にhtml作成する関数
  2. dispLoading

    検索中のgifファイルを表示させる関数
  3. removeLoading

    検索中のgifファイルを非表示にする関数

■ イベント

  1. submitアクションでイベント発火
  2. フォームに入力されたキーワードを取得
  3. 検索中の画面を表示
  4. ajax通信のリクエスト(urlや送信データを指定)
  5. ajax通信が成功したときの処理

    各データを変数にして、appendBook関数に食わせる
    (データが取得できない場合は不明な作者などのデータが入るようにしておく)
  6. ajax通信が失敗したときの処理
  7. 共通処理として検索中の画像を消す

検索に少し時間がかかるので、検索中であることがわかりやすいように、ajax通信中はgif画像を表示させるようにしています。

また、検索に時間がかかりすぎた場合にエラーになるようにしています。(10秒)
ずっと検索中になっているとユーザー側が不安になるので。

検索中の画面(cssファイル)

cssファイル

#loading {
  display: table;
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  background-color: #fff;
  opacity: 0.8;
}

#loading .loadingMsg {
  display: table-cell;
  text-align: center;
  vertical-align: middle;
  padding-top: 140px;
  background: image-url("loading.gif") center center no-repeat;
}

gifファイルは無料で作れるサイトがいくつかあるので作ってみましょう。
僕はこのサイトでつくりました。

メモ

うまくいかなかった部分をメモに残しておきます。
(詳しい方は教えていただけると嬉しいです…)

  • 本当はkeyword_paramsだけを引数にしたい
    コントローラでkeywordを元にAPIを叩く際に、search_itemsの引数にはkeyword_paramsとしてハッシュ形式で渡せばスッキリするのですが、なぜかうまくいきませんでした。(activehashだと受け取ってくれない感じ…)

  • turbolinksを消すとうまく作動しない
    turbolinksが入っているとajax通信する際にうまくいかないことがあるらしく、いつも使わない設定にしていたのですが、逆にturbolinksがないせいでajax通信がうまく行かないという事態になりました笑。この辺の仕組みはいまいち理解できてません…

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?