楽天APIのデータをテーブルに格納する方法

#はじめに
ポートフォリオ等でwebアプリを開発していると、「外部APIを利用してみたい」という方もいるかと思います。

今回紹介する楽天APIに関しては、データを取得すること自体は、そんなに難しくありません。
アプリIDを取得してgemをインストールすれば、割と簡単にデータを取得することができます。

ただし、「APIのデータをテーブルに格納して他のテーブルと関連付けて…」というように、取得したデータをアプリ内で活用しようとするとやや難易度が上がります（個人的な考えですが笑）

本記事では「取得したデータをテーブルに格納する方法」と「アソシエーションの設定」について記載していきます。
また、最後にアプリ内で検索機能を設けて、必要なデータを表示させるコードも簡単に記載しました。

これから楽天APIを使ってみたいという方の参考になれば幸いです。

#注意
- 本記事は楽天APIについて言及しております。
また楽天APIにも様々ありますが、今回は楽天ブックス書籍検索APIを用います。

- 本記事では、APIのデータ取得の部分（アプリIDの取得とgemのインストール）は割愛します。
データ取得部分については、以下の記事を参考にしてみてください。
　
https://freesworder.net/rakuten-api-rails/
https://qiita.com/hakusai_it/items/6453c4577647cb8995d3

#環境
- Ruby version 2.7.2
- Rails version 6.0.3.4

#ER図
今回は以下のER図にて、話を進めていきます。
Bookテーブルがデータを格納するテーブルです。
取得した本について、レビューを記載するために、Reviewテーブルを設けています。

![ER図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/799854/50532a32-f515-9c7c-f82e-e1505ea12426.png)

Bookテーブルのカラムについて少し説明します。
今回Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていきます。
『title』,『author』はそれぞれ、本のタイトルと著者名です。
『item_caption』は商品の説明、『item_url』は楽天の商品のurl、『middleimage_url』は本の画像です。
その他にも様々なデータがありますので、気になる方は以下のURLを参考にしてください。
https://webservice.rakuten.co.jp/api/booksbooksearch/

#実装工程
##概要
以下のような流れで実装していきます。

step1. テーブルの作成
step2. アソシエーションの設定
step3. ルーティングの設定
step4. コントローラーの設定
step5. 検索ページの作成

「アソシエーションの設定」はstep1・step2、
「取得したデータをテーブルに格納」はstep3・step4、
「検索ページの実装」はstep5で実装します

##step1. テーブルの作成
各テーブルを作成していきます。
前述の通り、今回は　User, Book, Reviewテーブルを作っていきます。

####Userテーブル作成
Userテーブルは特に変わったことはしません。
モデルを作成して、マイグレーションを実行していきましょう！

```java:
$ rails g model User name:string email:string password_digest:string
$ rails g db:migrate
```

####Bookテーブル作成
まずはモデルを作成していきます。

```java:
$ rails g model Book title:string author:string isbn:bigint url:string image_url:string
```

次にmigrationファイルを書き換えていきます。
Bookテーブルのprimary_keyは『id』ではなく、商品の固有の番号である『isbn』を使っていくため、ファイルの書き換えが必要になります。

ファイル名の米印にはMigration ID(日付等が書いてある数字）が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。

isbn部分に`null: false, primary_key: true`を追記します。

```java:**************_create_books.rb
class CreateBooks < ActiveRecord::Migration[6.0]
  def change
    create_table :books, id: false do |t|
      t.string :title
      t.string :author
      t.bigint :isbn, null: false, primary_key: true
      t.string :url
      t.string :image_url

      t.timestamps
    end
  end
end
```

マイグレーションファイルを書き換えたらマイグレーションしていきます。

```java:
$ rails g db:migrate
```

####Reviewテーブル作成
最後にReviewテーブルを作成していきます。

```java:
$ rails g model Review content:string user:references book:references
```
次にmigrationファイルを書き換えていきます。
ファイル名の米印にはMigration ID(日付等が書いてある数字）が入ります。
ActiveRecord::Migration[6.0]の部分は人によって異なると思います。

```java:**************_create_reviews.rb
class CreateReviews < ActiveRecord::Migration[6.0]
  def change
    create_table :books, id: false do |t|
      #bookの部分に記載してあったforeign_key: trueを削除する
      t.references :book, null: false
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end
    #この部分の新たに以下のコードを記載
    add_foreign_key :bookcases, :books, column: :book_id , primary_key: :isbn
  end
end
```
マイグレーションファイルを書き換えたらマイグレーションを実行していきます。

```java:
$ rails g db:migrate
```
これでテーブルの作成は以上です。
次はアソシエーションの設定です。

##step2. アソシエーションの設定
各model.rbのアソシエーションを設定してきます。
ここでもBookテーブルのprimary_keyを『isbn』になるようコードを書いていきます。

```java:user.rb
class User < ApplicationRecord
  has_many :reviews, dependent: :destroy
end
```

```java:book.rb
class Book < ApplicationRecord
  self.primary_key = "isbn"
  has_many :reviews, dependent: :destroy
end

```

```java:review.rb
class Bookcase < ApplicationRecord
  belongs_to :user
  belongs_to :book, primary_key: "isbn"
end
```
step2までで、テーブル作成とアソシエーションの設定は終了です。
step3以降はBookテーブルにデータを格納する方法を主に説明していきますので、User, Reviewモデルについては割愛し、Bookモデルについてのみ記載していきます。

##step3. ルーティングの設定
今回は検索欄と検索結果を表示するために/searchアクションを設けています。
必要であれば、ご自身で追加のアクションを設定してください。

```java:routes.rb
get 'books/search', to: "books#search"
```

###step4. コントローラの設定
まずはコントローラファイルを作成していきます。

```java:
$ rails g controller books
```
作成したコントローラファイルに以下のコードを記載していきます。

```java:books_controller.rb
class BooksController < ApplicationController

  def search
    #ここで空の配列を作ります
    @books = []
    @title = params[:title]
    if @title.present?
      #この部分でresultsに楽天APIから取得したデータ（jsonデータ）を格納します。
      #今回は書籍のタイトルを検索して、一致するデータを格納するように設定しています。
      results = RakutenWebService::Books::Book.search({
        title: @title,
      })
      #この部分で「@books」にAPIからの取得したJSONデータを格納していきます。
      #read(result)については、privateメソッドとして、設定しております。
      results.each do |result|
        book = Book.new(read(result))
        @books << book
      end
    end
    #「@books」内の各データをそれぞれ保存していきます。
    #すでに保存済の本は除外するためにunlessの構文を記載しています。
    @books.each do |book|
      unless Book.all.include?(book)
        book.save
      end
    end
  end

  private
  #「楽天APIのデータから必要なデータを絞り込む」、且つ「対応するカラムにデータを格納する」メソッドを設定していきます。
  def read(result)
    title = result["title"]
    author = result["author"]
    url = result["itemUrl"]
    isbn = result["isbn"]
    image_url = result["mediumImageUrl"].gsub('?_ex=120x120', '')
    book_genre_id = result["booksGenreId"]
    item_caption = result["itemCaption"]
    {
      title: title,
      author: author,
      url: url,
      isbn: isbn,
      image_url: image_url,
      book_genre_id: book_genre_id,
      item_caption: item_caption
    }
  end
end
```
step4までで、テーブルへのデータ格納は実装完了です。
step5では検索ページと結果の出力ページを作成していきます。

###step5. 検索ページの作成

search.html.erbというファイルを作成し、コードを書いていきます。
※本記事では最低限のコードのみ記載しております。適宜classを設定し、見た目を改善しましょう！

```java:search.html.erb
#検索バーを表示
<%= form_tag(books_search_path, method: :get) do %>
  <%= text_field_tag :title, @title %>
  <%= button_tag type: "submit" %>
<% end %>

#検索結果を表示
<% if @books %>
  <% @books.each do |book| %>
  #ご自身が表示させたいデータを記載してください。
  #以下のコードではは画像、タイトル、著者名、商品の説明を表示させています。
    <%= image_tag book.image_url %>
    <%= book.title %>
    <%= book.author %>
    <%= book.item_caption %>    
  <% end %>
<% end %>

```

以上で実装工程は終了となります。
何かご不明点や誤っている点がございましたら、コメントにて教えていただけると幸いです。