APIデータをテーブルに保存する方法

#はじめに
ポートフォリオを作成していく中で楽天APIのデータをテーブルに保存したいと思い探すと以下の記事を見つけました
https://qiita.com/Hiroaki_jr/items/983b11a45e2b42c8f3dc
こちらの記事を参考に自分なりに改良してコードを書き直したので誰かの参考になればと思い書かせていただきます。

#環境
ruby 2.6.5
rails 6.0.3
mysql 8.0.23

#ER図
![スクリーンショット 2021-04-06 12.06.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628160/f43b2a12-e485-41cd-34d3-c2b38c22fc20.png)
ユーザー(Userモデル)がアイテム(Rakutenモデル)を購入したいリストとして登録(Buylistモデル)できるようなアソシエーションを作りました。

例としてはユーザーが投稿に対していいねができるのようなアソシエーションと同じだと思います。
Rakutenのカラムはjsonで送られてくるデータの中で必要なデータをカラムとして作成しました

#Controller
テーブルの作成、ルーティングの記述、モデルのアソシエーションの設定は割愛させていただきます。
主にコントローラー、ビューの部分の記述を説明させていただきます。

```ruby:rakutens_controller.rb

  def index
　　　#長いためprivetメソッドに定義
    create_rakuten_data(params)

    @items = Rakuten.all.page(params[:page])
    # 全アイテム数
    @total_count = @results.response.count
  end

  def search
    @items = @q.result.page(params[:page]).per(30)
    @count = @q.result.count
  end

  private
    #楽天市場の店指定のアイテムを取得
    def create_rakuten_data(params)
      @results = RakutenWebService::Ichiba::Item.search(
        shopCode: 'shop-senjin',
        page: params[:page],
        hits: 30)
　　　 #ここで取得したアイテムをテーブルに保存するがすでに保存されているアイテムは保存しない処理を行う
      @results.each do |result|
        #取得したデータの配列のresultを引数にprivetメソッドreadを呼び出し
        item = Rakuten.new(read(result))
        #rakutensテーブルに同じアイテムがあれば保存しない処理
        unless Rakuten.all.exists?(item_name: item.item_name)
          item.save
        end
      end
    end

    def read(result)
      #Rakutenモデルのインスタンスに埋め込む
      image_url = result['mediumImageUrls'][0]
      item_name = result['itemName']
      item_price = result['itemPrice']
      item_url = result['itemUrl']
      shop_name = result['shopCode']
      genre_id = result['genreId']
      review_average = result['reviewAverage']
      {
        image_urls: image_url,
        item_name: item_name,
        item_price: item_price,
        item_url: item_url,
        shop_name: shop_name,
        genle_id: genre_id,
        review_average: review_average
      }
    end
```
楽天APIで取得したBooksデータには一意なカラム「isbn」があるが、Itemには一意なカラムがないためitem_nameを一意のカラムとして扱いました。

#View
```ruby:rakutens/index.html.haml
.senjin-search
    = search_form_for @q, url: search_rakutens_path do |f|
      .text-center.my-3
        %h4.my-2.font-weight-bold キーワード検索
        = f.search_field :item_name_or_item_price_cont, class: 'senjin-field'
        = button_tag type: 'submit', class: 'icon senjin-icon' do
          %i.fas.fa-search
      .text-center.my-3
        %h4.my-2.font-weight-bold ジャンル検索
        = f.select :genle_id_eq, {'バッグ、ポーチ、入れ物': 213693, 'バックパック,リュック': 111926, '手袋': 509058, '靴下': 408907, '小物,装具': 213694, 'Tシャツ': 551180, 'コンパス': 208078, 'GPS': 402305, 'サングラス、ゴーグル、フェイスフード': 213692, 'クッション、ベッド部品': 111925, 'ビニールテープ': 301594, 'ジャケット': 558873, '身分証入れ': 403809, 'トレーニングウェア': 302491, 'マット類': 501931}, { include_blank: '指定なし'}, class: 'senjin-field'
        = button_tag type: 'submit', class: 'icon senjin-icon' do
          %i.fas.fa-search
  .senjin-counts
    全アイテム数: #{@total_count} 件
  .container 
    .row
      = render 'commons/rakuten_item', items: @items
    = paginate @items
```
```ruby:commons/rakuten_item
- items.each do |item|
  .col-sm-4.col-md-4.col-lg-3.col-xl-2.mb-3
    .senjin-item.mx-auto
      .api-img
        = link_to item.item_url do
          = image_tag item.image_urls
      .api-content
        = item.item_name
        .api
          .api-title
            購入金額
          .api-value
            #{item.item_price}円
        .api
          .api-title 
            平均評価
          .api-value
            #{item.review_average}
        .buy-list.mt-2
      - if current_user.has_buylist?(item)
        .not-buy#unfollow-btn
          = link_to '購入リストから解除', rakuten_buylist_path(item), method: 'delete',class: 'rakuten-delete'
      - else
        .buy#follow-btn
          = link_to '購入リストに登録', rakuten_buylist_path(item), method: 'post',class: 'rakuten-post'
```
#完成イメージ
![スクリーンショット 2021-04-06 13.07.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628160/69790b7b-348f-e562-12df-99995499b863.png)

以上でざっくりですが説明を終わらせていただきます。
何かご質問や不備な点などあればコメントいただきたいです