【Rails】Amazon APIで書籍検索アプリ作成

## 概要
以前使用したAmazon APIについてまとめておきたいな〜と思いまして。。。。。
実際に導入から実装までの流れを含め、簡単な書籍検索アプリを作ってみました！
作成するアプリの内容としては、
検索ボックスにキーワードを書き込み、その検索結果を返してくれるというシンプルなものです！

さらに！
おまけとしてJSを適用させて、インクリメンタルサーチを行えるようにもしたいと思います！

実際はこんな感じで動きます。
![moview.gif](https://qiita-image-store.s3.amazonaws.com/0/77585/a998a974-79e8-fec3-bf61-cc20f9b9a860.gif)


## 環境
Ruby 2.1.3
Rails 4.2.4

## 目次
1. アプリケーションの作成,基本的な実装 
2. AmazonアソシエイトIDとAccess Keyの取得
3. Amazon APIを実際を使用して書籍検索機能の実装
4. インクリメンタルサーチの実装

## 実装
### 1. アプリケーションの作成,基本的な実装
アプリケーションの作成、routing, controllerの作成,viewの作成を行います。

##### 1.1 アプリケーションの作成
書籍検索アプリなので、アプリ名は`book_search`とします。

```bash
$ rails new book_search
$ cd book_search
```

現在の最新版(2015年12月)の`gem mysql2`のバージョンだとエラーが生じるので、バージョンを書き換えます。
デバックの際に使用する`pry-rails`を入れておきましょう。

```Gemfile
gem 'mysql2', '0.3.18'
gem 'pry-rails 
```

DBの作成を行っておきます。


```bash
$ bundle install
$ rake db:create
```


#### 1.2 基本的な実装
コントローラー、モデルの作成を行いましょう。
controller名は、`books_controller`とします。
ヘルパーとアセットファイルを生成する必要がないので、オプションをつけて作成しないようにします。
また、モデルに関しては、DBへのアクセスは必要がないので、ActiveRecordを継承しないモデル([参考サイト](http://famtom.hatenablog.com/entry/2013/10/27/091036))を作成します。



```bash
$ rails g controller books --assets false --helper false
```

では、ルーティングの設定, コントローラーにおけるアクションの定義、viewの編集を行いましょう。
ここも基礎の部分なので、説明は省略させていただきます。

```routes.rb
Rails.application.routes.draw do
  root to: 'books#search'
end
```

```books_controller.rb
class BooksController < ApplicationController
  def search
  end
end
```

```book.rb
class Book
  # プロパティの設定
  attr_accessor :title, :image_url, :url 

  def initialize(title, image_url, url)
    @title = title
    @image_url = image_url
    @url = url
  end
end
```

```search.html.erb
<div class='content'>
  <div id="search-box">
    <%= form_tag('/', method: :get) do %>
        <%= text_field_tag :book, "",  id: "book_search", name: "keyword", placeholder: "キーワードを打ち込んでください", style: "width: 200px;"%><button title="検索" type="submit">検索</button>
    <% end %>
  </div>
  <h2>検索結果</h2>
  <div id='book_list'>
  	<!-- コメントアウトしておく -->
   <!-- <%= render partial: 'book_list' %> -->
  </div>
</div>
```

```_book_list.html.erb
<% if @books.present? %>
  <% @books.each do |book| %>
    <div class="list">
      <%= link_to (image_tag(book.image_url, style: "width: 70px;height: 100px;display: inline-block;")), book.url %>
      <%= book.title %>
    </div>
  <% end %>
<% end %>
```

### 2. AmazonアソシエイトIDとAccess Key IDの取得
Amazon APIを使用するにあたって、以下のキーが必要となります。

- associate_tag 
- AWS_access_key_id
- AWS_secret_key

`associate_tag`はAmazonアフィリエイトアカウントを登録することで、取得できます。
また、`AWS_access_key_id`と`AWS_secret_key`はProduct Advertising APIに登録することで取得することができます。
何か分からないことがありましたら、[こちら(Access Key IDとSecret Access Keyの取得)](http://www.ajaxtower.jp/ecs/pre/index1.html)を参照ください。


##### 2.1 `AmazonアソシエイトIDの作成`
AmazonアソシエイトIDの作成は[こちら](https://affiliate.amazon.co.jp/)から行いましょう。
このとき、AmazonアソシエイトIDはログインページの右上のあるのが、覚えておいてください。


##### 2.2 `Product Advertising API`アカウント作成
Access Keyを取得するためには、`Product Advertising API`アカウント作成を行う必要があります。

先ほど登録したAmazonアソシエイトのページから
以下の写真のようにメニュータブの`Product Advertsing API`をクリックしてアカウント作成ボタンをクリックします。
![Presentation1.png](https://qiita-image-store.s3.amazonaws.com/0/77585/49e4ff14-a164-5a55-0418-2c329647df41.png "Presentation1.png")


注意点としては、

- Amazonアソシエイトで使用した同じメールアドレスを指定すること。
- すべて英語で入力すること。

です。

##### 2.3 `Access Key ID`と`Secret Access Key`の取得
登録が完了できたら以下のようにして、２つのAccess Keyを確認することができます。
注意として、シークレットキーは絶対に公開にしないようにしてください。

![Presentation2.png](https://qiita-image-store.s3.amazonaws.com/0/77585/2bff8e0e-dd85-b650-3b57-411887901b3d.png "Presentation2.png")


## 3 Amazon APIを実際に使用する
ついにAmazon APIを使用する下準備が整いました！！
これらカリカリとコードを書いていきます！！！
APIを使用し検索結果を表示するにあたって、以下の手順を踏みます。

- gemの`amazon-ecs`の追加、設定ファイルの記述
- コントローラーの編集
- Viewに検索結果を表示

### 3.1 gemの`amazon-ecs`の追加、設定ファイルの記述
Amazon APIを使用するにあたって、`amazon-ecs`というgemを使用します。(リポジトリは[こちら](https://github.com/jugend/amazon-ecs))


```Gemfile
gem 'amazon-ecs'
```

`config/environment.rb`にて、gemでインストールしたAmazon::Escを読み込みAmazon Web Serviceを利用するためのAPI Key設定します。
自身のアソシエイトキーとアクセスキーを指定します。

```config/environment.rb
Amazon::Ecs.options = {
      associate_tag:     '**************',
      AWS_access_key_id: '*****************',
      AWS_secret_key:   '***********************'
}
```

```bash
$ bundle install
```

### 3-2 コントローラーの編集
ここからAmazon Web Serviceにアクセスするための記述を行っていきます。
ソースコードは以下になります。

```boooks_controller.rb
class BooksController < ApplicationController
  def search
    if params[:keyword].present?
      #　デバックログ出力するために記述
      Amazon::Ecs.debug = true

      # Amazon::Ecs::Responceオブジェクトの取得
      books = Amazon::Ecs.item_search(
        params[:keyword],
        search_index:  'Books',
        dataType: 'script',
        response_group: 'ItemAttributes, Images',
        country:  'jp',
        power: "Not kindle"
      )

      # 本のタイトル,画像URL, 詳細ページURLの取得
      @books = []
      books.items.each do |item|
        book = Book.new(
          item.get('ItemAttributes/Title'),
          item.get('LargeImage/URL'),
          item.get('DetailPageURL'),
        )
        @books << book
      end
    end
  end
end
```


ひとつひとつ説明していきたいと思います。

##### `Amazon::Ecs.debug = true`
有効にすることで、以下の写真のように、デバックログが出力され、検索するにあたってどのような指定を行っているのか、また、エラー時にはエラー内容が出力されます。
さらに注目すべきは、`amazon-ecs`がリクエストしたURLも出力されているので、URLからAPIのリクエストの実行結果も見ることができます。

![スクリーンショット_2015-12-10_11_10_33.png](https://qiita-image-store.s3.amazonaws.com/0/77585/9243e1cb-c9ff-2c9f-4704-c5ba7a6bab23.png "スクリーンショット_2015-12-10_11_10_33.png")

##### `Amazon::Ecs::Responseオブジェクトの取得`
*item_searchメソッド*
幾つかの検索条件を満たす商品のXML要素の情報を配列として返します。
また、一度の検索に検索に最大10個の検索結果を返します。

今回の指定した引数に関しての説明をしていきたいと思います。

- `第一引数`に、自身が打ち込んだキーワード
- `第二引数`のseach_indexにて検索対象の指定を行う。`Books`としているので、検索対象は本としている。詳しくは、[パラメータの組み合わせはこちら](https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html)を参照してください。
- `第三引数`のresponce_groupでは、レスポンスの情報量を制御します。情報量を適切に保つには、ご使用のアプリケーションが必要とする情報だけを返すレスポンスグループを指定します。
商品の詳細情報と画像情報が欲しいので`ItemAttributesとImage`を指定します。
詳しいレスポンスグループの指定は[こちら](http://www.ajaxtower.jp/ecs/responsegroup/)。
- `第四引数`のcountryには地域の指定をします。
- `第五引数`の`power: "Not kindle"`と指定することで、検索結果からkindleの除外することがすることができます。[参考URL:kindleを検索結果から除外する](http://piyopiyocs.blog115.fc2.com/blog-entry-658.html)

ほかにも色々なオプションがありますので、気になる方はご自身で調べてみてください。

##### `本のタイトル,画像URL, 詳細ページURLの取得`
Amazon::Ecs::Responseのインスタンスbooksから必要な情報を取得します。
今回必要な情報は、タイトル,画像URL,詳細ページのURLです。
変数のbooksがどのような構造をしているか確認したい場合には、`binding.pry`を使用する、あるいは、コンソールで出力されているAPIリクエストのURLから確認することができます。
必要な要素の取得は`get`メソッドを使って取得できます。



取得結果をインスタンス変数@booksに渡してviewに渡すことができます。

##### `Viewに検索結果を表示`
`search.html.erb`のコメントアウトしていた箇所を解除してみましょう。


こんな感じになりましたか？？
ひとまずはAmazon APIの導入から実装までは以上です！

![moview.gif](https://qiita-image-store.s3.amazonaws.com/0/77585/73b6fc05-a446-1db3-9acc-7aa7e24c5960.gif "moview.gif")


### 4. インクリメンタルサーチの実装

ここは主題ではないので、簡単にソースコードの紹介とさせていただきます。
インクリメンタルサーチを行うにあたって、以下のファイルを生成、編集します。

- incremental_search.js
- search.js.erb

incremental_search.jsは`assets/javascripts/`に置き、
search.js.erbは`view/books/`に置きます。


では、jsをカキカキしていきましょう。
下に示すソースコードはあくまでも一例です。


```incremental_search.js
$(window).on('load', function() {
  var timer;
  var preInput = "";

  ajaxSearch = function(input) {
    $.ajax({
      url: "/",
      type: "GET",
      dataType: 'script',                          // jsファイルを指定したいので、dataTypeをscriptに指定
      data: ("keyword=" + input)
    });
  };

  triggerAjax = function(input) {
    if(preInput !== input){                        //文字列が変更ときのみメソッド実行する
      clearTimeout(timer);
      timer = setTimeout(ajaxSearch, 500, input); // 処理を500ms毎に実行
    };
  };


  $('#book_search').on('keyup', function() {
    input = $.trim($(this).val());                // 文字列の先頭と末尾の空白を削除
    triggerAjax(input);
    preInput = input;
  });
});
```

```search.js.erb
$("#book_list").html("<%= j(render 'book_list') %>");
```

これで検索ボタンをいちいち押す必要がなくなり、検索もスイスイ行うことが可能です！
完成！！

![moview.gif](https://qiita-image-store.s3.amazonaws.com/0/77585/3618a167-f3a3-62fb-1f7b-43d71c7c56f4.gif)