【Rails】openBD APIを使って簡単な本レビューアプリを作成する【jQuery,Ajax 】

初めまして。qiita初投稿になります。
業務未経験なため、間違いがある箇所もあると思いますがご指摘頂ければと思います。
今回書くことは基礎的なことですが、私のようなrails初心者のお力になれれば幸いです。


# openBD APIについて
書誌情報・書影を、だれでも自由に使える、高速なAPIで提供しているプロジェクトです。

24,747社の約76万タイトルの本の書誌情報、書影、ためし読み、書評掲載情報を利用でき、
書誌情報1件あたり1ミリ秒以下での応答と非常に高速なAPIです。

公式サイト
https://openbd.jp/

# 作成するレビューアプリの概要

- openbdというAPIを使用し、ajaxを利用して非同期で本の情報を取得します。
- あらかじめ用意したフォームに取得した任意の情報が自動入力され、レビューを入力し、投稿ボタンを押す事でreviewが投稿されます。
- DBには画像はURLのみ保存し、画像自体は保存しません。
- 取得する情報がない場合(画像や紹介文)は、「情報がありません」旨を表示させます。
- bootstrap4を使用しviewを作成します。

# アプリ構築環境

- ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
- Rails 5.2.2


# アプリの作成の流れ
1.アプリを作成し、必要なgem(bootstrap4等)を導入します。
2.reviewsコントローラを作成します。
3.本情報およびレビューを格納するためのReviewモデルを作成します。
4.フォームを表示するためのviewを作成します。
5.非同期(ajax)で本情報を取得するためのjsファイルを作成します。
6.投稿したレビューを表示させるためのviewを作成します。
7.railsサーバを起動します。

## 1.アプリ作成

以下のコマンドでアプリを作成します。

```
rails new book_review  
```

アプリが作成出来たらGemfileを編集します。

```
cd book_review
vi Gemfile
```

以下のGemをファイルの一番下に記述します。

```ruby:Gemfile
gem 'haml-rails' #erbではなくhamlで記述するために導入します。
gem 'erb2haml' #既存のerbファイルをhamlに変換します。
gem 'jquery-rails' #railsでjqueryを利用するために導入します。
gem 'bootstrap' #versionをしていないのでbootstrap4がインストールされます。
gem 'bootstrap_form' #formを自動でbootstrapレイアウトにしてくれます。
```
いつものようにbundleインストールします。

```
bundle install
```
bootstrap4とbootstrap_formを使用するために以下のファイルに記述します。

`book_review/app/assets/stylesheets/`　配下のファイルを

`application.css`から`application.scss`に名前変更してください。

```ruby:application.scss
@import "bootstrap";
```
```ruby:application.scss
*= require rails_bootstrap_forms
```

javascriptも使用するため、以下のファイルにも以下を記述します。
`//= require_tree .`の上に記述してください

```javascript:app/assets/javascripts/application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery3 //追加
//= require bootstrap-sprockets //追加
//= require bootstrap //追加
//= require_tree .
```

以上で、bootstrap4とjavascriptが利用可能になります。

##
いよいよ、アプリ作成に入っていきます。
まずはデフォルト状態ではapplicationはerbで書かれているのでhamlに変更します。以下のコマンドを入力してください。

```
rake haml:replace_erbs
```

## 2.reviewsコントローラ作成
以下のコマンドでreviewsコントローラを作成します。

```
rails g controller reviews show new
# showとnewを記述することで、show.html.haml,new.html.hamlも同時に作成してくれます。
```
上記コマンドでエラーが出た場合はGemfileを編集し、sqlite3のバージョンを指定してください

```ruby:Gemfile
gem 'sqlite3', '~> 1.3.6'
```

作成できたらreviews_controllerを編集します。

```ruby:app/controllers/reviews_controller.rb
class ReviewsController < ApplicationController
    def show
       @review = Review.find(params[:id])
    end
    def new
        @review = Review.new
        respond_to do |format|
          format.html
          format.json ##jsonで出力します。
          end
    end
    def create
        @review = Review.new(review_params)
        if @review.save
          redirect_to review_path(@review)
        else
          redirect_to new_review_path
        end
    end
    def review_params #ストロングパラメータで制限する。
        params.require(:review).permit(:name, :author, :review, :image_url, :introduction)
    end
end
```
合わせてレビューを投稿するためのルーティングも編集します。

```ruby:config/routes.rb
  root 'reviews#new' # 今回はindexページがないので、review投稿ページをrootにします。
  resources :reviews

  #resourcesとすることで自動的にルーティングを作成してくれます。
  #作成されたrouteは rails routesコマンドで確認可能です。
```
## 3.Reviewモデル作成
レビューを格納するためにreviewモデルを作成します。

```
rails g model Review name:string author:string review:text image_url:string introduction:text
```
上記コマンドで以下のテーブルとカラムが作成されます。
### reviewsテーブル
|Column|Type|Options|
|------|----|-------|
|name|string|
|author|string|
|review|text|
|introduction|text|
|image_url|string|

作成したmigrationファイルをdbにmigrateします。

```
rails db:migrate
```

## 4.本情報取得フォーム作成
以下のviewファイルを作成し、編集します。
bootstrap_formというgemを使用しているため、form_forをbootstrap_form_forとしています。
自動でbootstrapのスタイルが適用されます。

```ruby:app/views/reviews/new.html.haml
.container
  .result
  .panel-title
    本情報取得フォーム
    .form-group{id:"get-book"}
      %input{type:"text",class:"form-control",placeholder:"ISBNを入力して下さい",id:"isbn"}
    .form-submit
      %button{type:"submit",id:"submit",class:"btn btn-outline-dark"} 本検索
  .result-image
  = bootstrap_form_for @review ,id:"form-result" do |f|
    = f.text_field :name,label: "タイトル",placeholder:"本のタイトルを入力して下さい",id:"book-name"
    = f.text_field :author,label: "著者",placeholder:"著者の名前を入力して下さい",id:"author-name"
    = f.text_area :introduction,type:"hidden",hide_label: true,id:"introduction",style:"display:none"
    = f.text_field :image_url,type:"hidden",hide_label: true, id:"image_url"
    = f.text_area :review,label: "レビュー",placeholder:"レビューを入力して下さい",size: "20x10",id:"review"
    = f.submit "投稿",class:"btn btn-outline-dark"
```
## 5.本情報取得jsファイル作成
book_review/app/assets/javascripts/の配下にreviews.js.erbを作成します。
拡張子にerbを入れているのは画像がなかった際のno_image画像をrailsのimagesフォルダから参照するためです。

```javascript:app/assets/javascripts/reviews.js.erb
$(document).on('turbolinks:load', function() {
    //画像のHTMLを生成する。
    function buildImage(book) {
        var no_image = '<div class="book_image"><img "width="200" height="200" src="<%= image_path('no_image.jpg')%>"></div>';
        var image = '<div class="book_image img-thumbnail"><img "width="250" height="250" + src="' + book[0].summary.cover + '"></div>';
        if (!book[0].summary.cover){
            var image = no_image; //画像がなかった場合の処理
        }else{
            image;
        }
        return image;
    }
    //画像URLを生成する。
    function imageUrl(book){
        var no_image_url = '<%= image_path('/assets/no_image.jpg')%>';
        var image_url = book[0].summary.cover;
        //".jpg"に"_0"を加える。最大サイズの画像を取得できるようになる。
        var image_url = image_url.replace(".jpg", "_0.jpg");        
        if (!book[0].summary.cover){
            var image_url = no_image_url; //画像がなかった場合の処理
        }else{
            image_url
        }
        return image_url;
    }
    function bookDetail (book){
        var bookDetail = $.isEmptyObject(book[0].onix.CollateralDetail);
        if (bookDetail != true){
            $('#introduction').val(book[0].onix.CollateralDetail.TextContent[0].Text);
        }else{
            $('#introduction').val("情報がありません");
        }
    }
    //著者の情報がなかった場合の処理
    function authorName (book) {
        var bookAuthor = $.isEmptyObject(book[0].summary.author);
        if (bookAuthor != true){
            $('#author-name').val(book[0].summary.author);
        }else{
            $('#author-name').val("情報がありません");
        }
    }
    //本の情報がなかった場合のalert
    function noAppendBook(){
        var book = `<div class="alert alert-warning">
        <strong>本情報を取得できませんでした。</strong>
        </div>`
        return book
    }
    //本の情報取得に成功した時のalert
    function appendBook(){
        var book = `<div class="alert alert-primary">
        <strong>本情報の取得に成功しました。</strong>
        </div>`
        return book
    }
    //情報取得後、再度検索バーに入力が開始されたらフォームに入力されている取得済み情報を削除する。
    $('#get-book').on("keyup",function(){
        $('#submit').prop('disabled', false);
        $('.alert').remove();
        $('#book-name').val("");
        $('#author-name').val("");
        $('#image_url').val("");
        $('#introduction').val("");
        $('.book_image').empty();
    });
    //submitタグをクリックするとajaxで処理が開始される。
    $('#submit').on("click",function(e) {
        e.preventDefault();
        var bookName = $('#get-book').find('#isbn').prop('value');
        var requestUrl = 'https://api.openbd.jp/v1/get?isbn=';
        requestUrl += bookName + '&pretty';
        $.ajax({
            type:"GET",
            url:requestUrl,
            dataType:"json"
        })
            //通信が成功したときの処理
            .done(function(data) {
                if (data[0] != null){
                    var image = buildImage(data);
                    // resultに成功失敗のalart表示
                    $('.result').append(appendBook);
                    // 本のタイトル表示
                    $('#book-name').val(data[0].summary.title);
                    //hiddenタグであるimage_urlに取得した画像urlを格納
                    $('#image_url').val(imageUrl(data));
                    // 取得した本の画像を表示
                    $('.result-image').append(image);
                    // 本の著者表示
                    authorName(data);
                    // 本の紹介文表示
                    bookDetail(data);
                    // 本情報取得に成功後 submitタグを押せないようにする。
                    $('#submit').prop('disabled', true);
                } else {
                    // 本情報取得に失敗した際のalert表示
                    $('.result').append(noAppendBook);
                    // 本情報取得に成功後 submitタグを押せないようにする。
                    $('#submit').prop('disabled', true);
                }
            })
            // 通信に失敗した際のalert表示
            .fail(function() {
                alert('情報の取得に失敗しました');
            });
    });
});
```
本の画像が取得できなかった場合に代わりの画像を表示するため、以下のパスに以下の名前で代替画像を配置してください。

パス: app/assets/images/
ファイル名: no_image.jpg

ちなみに私は以下のフリー素材サイトからとりました。
http://design-ec.com/?p=55

これで、ブラウザで`localhost:3000/reviews/new`にアクセスすると本情報入力フォームが現れ、
「本情報取得フォーム」にISBN番号を入力する事で本の情報を取得する事ができるようになりました。

もし、フォームにISBNを入力しても反応がなかった場合、coffeeファイルが呼び出されてしまっている恐れがあるので、以下のファイルを削除してください。

```javascript:app/assets/javascripts/reviews.coffee
```
## 6.投稿したレビューを表示
上記の作業で、review投稿ページおよび本の情報を取得するためのjsファイルを作成しましたが、
まだレビューを表示するためのviewがないので作成します。

```ruby:app/views/reviews/show.html.haml
%section.book-images-frame.col-md-3.col-xs-12
  - if @review.image_url?
    = image_tag "#{@review.image_url}",class: "book-images-cover",style:"height:200px;width:150px;"
  - else
    = image_tag "no_image.jpg",class: "book-images-cover",style:"height:200px;width:150px;"
%section.book-content.col-md-6.col-xs-12
  .book-title-frame
    .book-title-block
      = @review.name
  .book-attribute-frame
    %h2 著者
    .book-author
      = @review.author
  .book-content
    %h2 紹介
    = simple_format (@review.introduction)
    %h2 レビュー
    = simple_format (@review.review)
```

## 7.railsサーバを起動
```
rails s #サーバ起動
```
##　完成
これで本のレビューを投稿後、自動的にレビューページに遷移してくれる簡単なアプリが作成できました。

が、
まだまだ未完成なので修正していって頂ければと思います。

この記事は随時修正していこうと思いますが、
記事の間違いや、もっとこうしたほうがいいよ等ございましたら、コメント頂ければ幸いです。


# 参考にさせて頂いた記事
- https://qiita.com/slangsoft/items/c62023d227a9a9f43f8c
- https://qiita.com/s-yank/items/cf7cadbb6c6996d67cf7