RailsとPokeAPIで作る簡単ポケモンWebアプリ

# はじめに

この記事はRails Tutorialを1周終えたくらいの初学者向けの内容です。

私はRuby on Railsから本格的にプログラミングの勉強をはじめました。そのときに使っていた教材(Progate, Rails Tutorialなど)にはAPIで何かを作るものはなく、APIに触れる機会がありませんでした。

外部APIを使えば、作れるアプリケーションの幅も広がり、プログラミングがより楽しくなるきっかけになるかなと思いましたので、外部APIを使った簡易なアプリケーションのチュートリアルを作ってみます。

本アプリケーション作成時のRuby, Railsのバージョンです。

```sh
ruby '2.6.3'
rails '5.2.4'
```


# 作るもの

ポケモンのデータを扱える`Poke API`を使って、「ポケモンを検索」、「ゲット（保存）」できるアプリケーションを作ります。
なぜ`Poke API`かというと、ポケモン剣盾を買うお金がないので、ポケモンのデータで遊んで、気をまぎらわせたいのです！

### PokeAPIについて
[Poke API](https://pokeapi.co/)にはポケモン関連のデータが大量にあります。
トップページでAPIを簡単に試せるようになっているので、ぜひ触ってみてください。
なお、今回作るアプリケーションでは取得できるデータのほんの一部しか使いません。

# それでは実装していきましょう

### 1.PokeAPIからポケモンのデータを取得して表示する
rails new をします。

```sh
rails new pokeapi_app
```

アプリケーションの土台ができたら、FaradayというGemを入れます。

- [Faraday](https://github.com/lostisland/faraday)のざっくり説明
  - HTTPクライアントライブラリ。
  - Rubyの標準ライブラリのnet/httpでもHTTPリクエストはできますが、Faradayはより簡単にHTTPリクエストを行うことができる。


```ruby:Gemfile
gem 'faraday'
```

```sh
bundle install
```

次に、Pokemonコントローラーを作ります。

```sh
rails g controller pokemons new
```

ルーティングを設定します。（後々の実装を考えて`resources :pokemons`にする）

```ruby:config/routes.rb
Rails.application.routes.draw do
  resources :pokemons
end
```

Faradayを使ってHTTPリクエストを行います。
`Faraday.get "URL"`でリクエストできるので、とても簡単です。
※他にもできることはたくさんあるので、気になる方は[ドキュメント](https://lostisland.github.io/faraday/usage/)を参照ください。

Faraday.getで返ってくるレスポンスは、`response`を`puts`したり、rails consoleを起動して`Faraday.get "https://pokeapi.co/api/v2/pokemon/pikachu"`入力すると確認できます。

```ruby:pokemons_controller.rb
class PokemonsController < ApplicationController
  def new
    raw_response = Faraday.get "https://pokeapi.co/api/v2/pokemon/pikachu"
    @response = JSON.parse(raw_response.body)
    # JSON.parseをすることで、json形式の文字列をRubyオブジェクトに変換することができる。
  end
end
```

APIから取得したデータを`pokemons/new.html.erb`に表示します。

```erb:pokemons/new.html.erb
<h1>Pokemon</h1>
<div>
  <table>
    <tr>
      <td>No.</td>
      <td><%= @response["id"] %></td>
    </tr>
    <tr>
      <td>Name:</td>
      <td><%= @response["name"] %></td>
    </tr>
  </table>
  <%= image_tag(@response["sprites"]["front_default"], size: "200") %>
</div>
```

rails serverを起動して、`http://localhost:3000/pokemons/new`をブラウザで開いてください。

```sh
rails s
```
　
ピカチュウが表示されましたね！はい、可愛い〜。

<img width="182" alt="スクリーンショット 2019-12-07 9.46.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344685/b0737953-06a6-7842-9a78-7ff561c6148f.png">

### 2.ポケモンを検索する

`Faraday.get "https://pokeapi.co/api/v2/pokemon/pikachu"`と固定のURLにリクエストをしているので、いまはピカチュウしか表示できません。
いろいろなポケモンをゲットするために、検索できるようにします。

```sh
mkdir app/views/shared

touch app/views/shared/_search.html.erb
```

　
`form_with`を使って、探したいポケモンをURLパラメータで送ります。

```erb:shared/_search.html.erb
<div style="margin-bottom: 50px;">
  <%= form_with url: path, method: :get, local: true do |form| %>
    <%= form.text_field :search %>
    <%= form.submit '検索', name: nil %>
  <% end %>
</div>
```

```erb:pokemons/new.html.erb
<h1>Pokemon</h1>
<!-- 追加 -->
<%= render "shared/search", { path: new_pokemon_path } %>
<div>
  <% if @response.present? %>
    <table>
      <tr>
        <td>No.</td>
        <td><%= @response["id"] %></td>
      </tr>
      <tr>
        <td>Name:</td>
        <td><%= @response["name"] %></td>
      </tr>
    </table>
    <%= image_tag(@response["sprites"]["front_default"], size: "200") %>
  <% else %>
    <p>検索してね！</p>
  <% end %>
</div>
```

送られてきたパラメータをリクエスト先のURLに入れます。
また、リクエストが失敗した時の処理も追加します。

```ruby:pokemons_controller.rb
class PokemonController < ApplicationController
  def new
    return if params[:search].blank?
    raw_response = Faraday.get "https://pokeapi.co/api/v2/pokemon/#{params[:search]}"
    if raw_response.status == 200
      @response = JSON.parse(raw_response.body)
    else
      redirect_to new_pokemon_path, notice: "#{raw_response.status}エラー！"
    end
  end
end
```

フラッシュメッセージは共通で使うことが多くなるので`erb:layouts/application.html.erb`で表示します。

```erb:layouts/application.html.erb
<body>
  <% if flash[:notice] %>
    <div><%= flash[:notice] %></div>
  <% end %>
  <%= yield %>
</body>
```
　
これでポケモンの検索ができるようになりました。

![poke_search.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344685/0d13f5ba-188f-7014-a8d0-308c2955d607.gif)

### 3.ポケモンを保存する

ポケモンのデータを保存するためにモデルとテーブルを作成します。

```sh
rails g model pokemon order:integer name:string image_url:string
```

生成されたmigrationファイルを開いて、各カラムにNOT NULL制約を追加して、`rails db:migrate`をします。

```ruby:db/migrate/hoge.rb
class CreatePokemons < ActiveRecord::Migration[5.2]
  def change
    create_table :pokemons do |t|
      t.integer :order, null: false
      t.string :name, null: false
      t.string :image_url, null: false

      t.timestamps
    end
  end
end
```

```sh
rails db:migrate
```

　
次に空の値が保存されないように、Pokemonモデルにバリデーションをかけます。

```ruby:pokemon.rb
class Pokemon < ApplicationRecord
  validates :order, presence: true
  validates :name, presence: true
  validates :image_url, presence: true
end
```

コントローラーのnewアクションに変更を加えていきます。

```ruby:pokemons_controller.rb
class PokemonController < ApplicationController
  def new
    return if params[:search].blank?

    raw_response = Faraday.get "https://pokeapi.co/api/v2/pokemon/#{params[:search]}"
    if raw_response.status == 200
      response = JSON.parse(raw_response.body)
      # Pokemonインスタンスを生成するようにします。
      @pokemon = Pokemon.new(order: response["id"], name: response["name"], image_url: response["sprites"]["front_default"])
    else
      redirect_to new_pokemon_path, notice: "#{raw_response.status}エラー！"
    end
  end
end
```

　
`pokemons/new.html.erb`で、`form_with`を使って、検索したポケモンを保存できるようにします。

```erb:pokemons/new.html.erb
<h1>Pokemon</h1>
<%= render "shared/search", { path: new_pokemon_path } %>
<div>
  <% if @pokemon.present? %>
    <table>
      <tr>
        <td>No.</td>
        <td><%= @pokemon.order %></td>
      </tr>
      <tr>
        <td>Name:</td>
        <td><%= @pokemon.name %></td>
      </tr>
    </table>
    <%= image_tag(@pokemon.image_url, size: "200") %>

    <%= form_with model: @pokemon, local: true do |f| %>
      <%= f.hidden_field :order  %>
      <%= f.hidden_field :name %>
      <%= f.hidden_field :image_url %>
      <%= f.submit 'ゲット', name: nil %>
    <% end %>
  <% else %>
    <p>検索してね！</p>
  <% end %>
</div>
```

　
コントローラーのcreateアクションを追加。送られてきたパラメータを処理して、レコードに保存できるようにします。

```ruby:pokemons_controller.rb
  def create
    @pokemon = Pokemon.new(pokemon_params)

    if @pokemon.save
      redirect_to pokemons_path, notice: "「#{@pokemon.name}」をゲットしました。"
    else
      render :new
    end
  end

  private

  def pokemon_params
    params.require(:pokemon).permit(:order, :name, :image_url)
  end
```

`pokemon/index.html.erb`にレコードに保存されているポケモンを表示できるようにします。

```ruby:pokemons_controller.rb
def index
  @pokemons = Pokemon.all
end
```

```sh
touch app/views/pokemons/index.html.erb
```

```erb:pokemons/index.html.erb
<h1>Pokemon</h1>
<%= render "shared/search", { path: new_pokemon_path } %>
<div>
  <% if @pokemons.present? %>
    <% @pokemons.each do |pokemon| %>
      <div style="display: inline-block;">
        No. <%= pokemon.order %>,
        Name: <%= pokemon.name %>
        <div>
          <%= image_tag(pokemon.image_url, size: "200") %>
        </div>
      </div>
    <% end %>
  <% end %>
</div>
```
　
また、バリデーションエラーが起きた場合に、エラーメッセージを表示できるようにします。

```erb:pokemons/new.html.erb
<h1>Pokemon</h1>
<%= render "shared/search", { path: new_pokemon_path } %>
<div>
  <% if @pokemon.present? %>
    <!-- 追加 -->
    <%= render "shared/error_messages", { object: @pokemon } %>
    <table>
      <tr>
        <td>No.</td>
        <td><%= @pokemon.order %></td>
      </tr>
      <tr>
        <td>Name:</td>
        <td><%= @pokemon.name %></td>
      </tr>
    </table>
    <%= image_tag(@pokemon.image_url, size: "200") %>

    <%= form_with model: @pokemon, local: true do |f| %>
      <%= f.hidden_field :order  %>
      <%= f.hidden_field :name %>
      <%= f.hidden_field :image_url %>
      <%= f.submit 'ゲット', name: nil %>
    <% end %>
  <% else %>
    <p>検索してね！</p>
  <% end %>
</div>
```

```sh
touch app/shared/_error_messages.html.erb
```

```erb:shared/_form_error.html.erb
<% if object.errors.present? %>
  <ul>
    <% object.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>
```

# とりあえず完成

![poke.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344685/cb2da9c4-9c3f-b169-3fed-0188f3779bc3.gif)
デザイン無しなので、見栄えはないですね…気になる方はBootstrapなど使っていただければと思います。
今後は元気があれば、CRUDの実装、他のデータ（itemなど）の取り込み、Api Clientの作成、例外処理を丁寧にしたいです。（たくさんある…）
これを読んだ初学者の方々がプログラミング学習をさらに楽しめるきっかけになれば幸いです。

# 参考
- [すばらしきポケモンエコシステム](https://qiita.com/seya/items/47dc0ebae55674d8902f)
- [Faradayドキュメント](https://lostisland.github.io/faraday/usage/)