Help us understand the problem. What is going on with this article?

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

はじめに

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

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

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

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

ruby '2.6.3'
rails '5.2.4'

作るもの

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

PokeAPIについて

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

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

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

rails new をします。

rails new pokeapi_app

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

  • Faradayのざっくり説明
    • HTTPクライアントライブラリ。
    • Rubyの標準ライブラリのnet/httpでもHTTPリクエストはできますが、Faradayはより簡単にHTTPリクエストを行うことができる。
Gemfile
gem 'faraday'
bundle install

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

rails g controller pokemons new

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

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

Faradayを使ってHTTPリクエストを行います。
Faraday.get "URL"でリクエストできるので、とても簡単です。
※他にもできることはたくさんあるので、気になる方はドキュメントを参照ください。

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

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に表示します。

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をブラウザで開いてください。

rails s

 
ピカチュウが表示されましたね!はい、可愛い〜。

スクリーンショット 2019-12-07 9.46.43.png

2.ポケモンを検索する

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

mkdir app/views/shared

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

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

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>
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に入れます。
また、リクエストが失敗した時の処理も追加します。

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で表示します。

layouts/application.html.erb
<body>
  <% if flash[:notice] %>
    <div><%= flash[:notice] %></div>
  <% end %>
  <%= yield %>
</body>

 
これでポケモンの検索ができるようになりました。

poke_search.gif

3.ポケモンを保存する

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

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

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

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
rails db:migrate

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

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

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

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を使って、検索したポケモンを保存できるようにします。

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アクションを追加。送られてきたパラメータを処理して、レコードに保存できるようにします。

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にレコードに保存されているポケモンを表示できるようにします。

pokemons_controller.rb
def index
  @pokemons = Pokemon.all
end
touch app/views/pokemons/index.html.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>

 
また、バリデーションエラーが起きた場合に、エラーメッセージを表示できるようにします。

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>
touch app/shared/_error_messages.html.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
デザイン無しなので、見栄えはないですね…気になる方はBootstrapなど使っていただければと思います。
今後は元気があれば、CRUDの実装、他のデータ(itemなど)の取り込み、Api Clientの作成、例外処理を丁寧にしたいです。(たくさんある…)
これを読んだ初学者の方々がプログラミング学習をさらに楽しめるきっかけになれば幸いです。

参考

harashoo
Webエンジニア1年目です。プログラミングとスパイスカレー作りにハマっています。
pepabo
「いるだけで成長できる環境」を標榜し、エンジニアが楽しく開発できるWebサービス企業を目指しています。
https://pepabo.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした