【Rails】【Controllerから外部APIを叩く】 OpenWeatherMap APIで天気情報を取得

# はじめに

外部APIを叩くという経験が初めてだったので、どんな大変な作業が待っているのだろうと心構えをしていたのですが、驚いたことに
`実は難易度が低くて、高機能が実装できるハイコスパな方法`でした。
今回実装した機能の概要は以下の通りです。メモ程度ですが参考程度に載せておきます。

![API概要.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688968/14f69d43-0d88-5126-639e-7eb09975725f.jpeg)

# 前提
- deviseでユーザーに関する機能作成済み
- food_record（料理記録）モデルを作成済み
- new, createなどの基本的な機能は既に実装済み

# 環境構築
- Ruby2.7
- Ruby on Rails6

# API_KEYの取得
[OpenWeatherMap](https://openweathermap.org/)で会員登録して、API_KEYを取得します。こちらは、多くの記事が出回っているので、割愛させて頂きます。
https://qiita.com/matsubishi5/items/fcd77eacb0ed111299e2
↑私はこちらを参照しました。

`http://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=`
末尾に取得したAPI_KEYを貼り付けて、ブラウザで実行してみてください。東京の今の天気が返ってくるはずです。これだけで、APIってこんなものかと実感できますし、疎通確認の意味でもやっておいた方が無難です。
ちなみに、会員登録からAPI_KEYが使えるようになるまで数時間程度かかります。(いくぞうは、3時間くらいでした)

# 必要なライブラリをインストール & API_KEYを格納


``` Gemfile.
#API_KEYを環境変数として管理する（Keyを外部流出させないための措置）
gem 'dotenv-rails'
#アプリケーション内でHTTPリクエストを投げたい場合に使うクラス
gem 'httpclient'
```

Gemfile記載後、bundle installします。

<br>
``` ..env.
OPEN_WEATHER_MAP_API = 'API_KEY貼り付け'
URI = 'https://api.openweathermap.org/data/2.5/weather'
```

.envには、前もって取得したAPI_KEYをコピペします。

``` ..gitignore.
/.env
```
<strong>ここで絶対に忘れずに.gitignoreを作ってください。</strong>.envをgithubにpushした場合、全世界にAPI_KEYを晒すことになってしまいます。（私は１回やらかしました、、、）

# APIを叩く処理

controllerでAPIを叩きます。
- `Api::OpenWeatherMap::Request`という処理がありますが、こちらで、下記モジュール(request.rb)を呼び出しています。
- コードがブサイクでごめんなさい！！！！でもちゃんと動きます。


``` food_records_controller.rb
def create

    #料理記録
    @food_record = current_user.food_records.build(food_record_params)
    @food_record.food_date = Time.zone.today

    #天気API
    #リクエストを出す
    open_weather = Api::OpenWeatherMap::Request.new(current_user.location_id)
    #戻り値を受け取る
    response = open_weather.request
    if @food_record.valid?
      #APIが正常に動作した場合
      if response['cod'] == 200
        params_weather = Api::OpenWeatherMap::Request.attributes_for(response)
        @food_record.update(params_weather)
        flash[:notice] = "登録に成功しました"
      else
        #APIが正常に動作しない場合も料理情報は記録する
        flash[:notice] = "天気情報の取得に失敗しましたが、登録に成功しました"
      end
      redirect_to root_url
    else
      render 'new'
    end
  end
``` 
<br>
- 処理本体は、モジュールに書きました。
- 今回、`取得する天気の地域`としてlocation_idを使用していますが、'id: location_id'を`q: 'Tokyo'`というように変えても上手くいきます。

```lib/api/open_weather_map/request.rb
module Api
  module OpenWeatherMap
    class Request
      attr_accessor :query

      def initialize(location_id)
        @query = {
          id: location_id,
          units: 'metric',
          appid:
          'OPEN_WEATHER_MAP_API']
        }
      end

      def request
        client = HTTPClient.new
        request = client.get(ENV['URI'], query)
        JSON.parse(request.body)
      end

      # 取得したjsonをparamsに変換
      def self.attributes_for(attrs)
        {
          weather_main: attrs['weather'][0]['main'],
          weather_description: attrs['weather'][0]['description'],
          weather_icon: attrs['weather'][0]['icon'],
          weather_id: attrs['weather'][0]['id'],
          temp: attrs['main']['temp'],
          temp_max: attrs['main']['temp_max'],
          temp_min: attrs['main']['temp_min'],
          humidity: attrs['main']['humidity'],
          pressure: attrs['main']['pressure']
        }
      end
    end
  end
end

```
<br>
lib以下はデフォルトではtask以外読み込まれないので、config/application.rbに、以下の設定が必要です

``` config/application.rb
config.paths.add 'lib', eager_load: true
```

# 完成

![スクリーンショット 2021-01-21 21.32.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688968/96d69306-a71b-cebb-9344-e6ffda635b44.png)

`<%= image_tag "http://openweathermap.org/img/wn/#{@food_record.weather_icon.chop}d@2x.png" %>`
アイコンはこのように表示させています。chop処理をしているのは、夜に天気を取得すると、太陽が黒色になってしまうからです。
`{@food_record.weather_icon.chop}d`　このようにchopで末尾のnを削って、d「昼間(おそらくdaytime)」を付け足しています。

# 補足
- 今回はlocation_idを使用しました。私は、下記のCSVを読み込ませて、cityテーブルに初期データとして投入して使用しています。
- 都市名, location_id, 緯度, 経度 の順になっています。
- 説明は割愛しますが、下記のようにすれば簡単に実装できます。

``` db/csv/cities.csv
札幌,2128295,141.346939,43.064171
青森,2130658,140.740005,40.82444
盛岡,2111834,141.152496,39.703609
仙台,2111149,140.871933,38.26889
秋田,2113126,140.116669,39.716671
山形,2110556,140.363327,38.240559
福島,2112923,140.467773,37.75
水戸,2111901,140.446671,36.341389
宇都宮,1849053,139.883606,36.56583
前橋,1857843,139.060837,36.391109
さいたま,1853226,35.857208
千葉,2113015,140.123337,35.604721
東京,1850147,139.691711,35.689499
横浜,1848354,139.642502,35.447781
新潟,1855431,139.023605,37.902222
富山,1849876,137.211395,36.695278
金沢,1860243,136.625565,36.59444
福井,1863983,136.225174,35.850101
山梨,1848649,138.608002,35.61602
長野,1856210,138.040771,36.13464
岐阜,1863640,137.053986,35.78091
静岡,1851715,138.325424,35.025219
名古屋,1856057,136.906403,35.181469
津,1849796,136.508606,34.730282
大津,1853574,135.868332,35.00444
京都,1857910,135.753845,35.021069
大阪,1853909,135.502182,34.693741
神戸,1859171,135.182999,34.691299
奈良,1855612,135.804855,34.685051
和歌山,1926004,135.167496,34.226109
鳥取,1849890,133.850815,35.367859
松江,1857550,133.050568,35.472221
岡山,1854383,133.934998,34.661671
広島,1862415,132.459366,34.396271
山口,1848689,131.47139,34.185829
徳島,1850158,134.559433,34.06583
高松,1851100,134.043335,34.340279
松山,1926099,132.765747,33.839161
高知,1859146,133.531113,33.559719
福岡,1863967,130.41806,33.606392
佐賀,1853303,130.298798,33.249321
長崎,1856177,129.873611,32.74472
熊本,1858421,130.741669,32.789719
大分,1854487,131.612503,33.23806
宮崎,1856717,131.423889,31.91111
鹿児島,1860827,130.558136,31.560181
那覇,1856035,127.681107,26.2125

```

``` db/seeds.rb
require "csv"
CSV.foreach('db/csv/cities.csv') do |row|
  City.create(
    name: row[0],
    location_id: row[1],
    lon: row[2],
    lat: row[3]
  )
end
```
<br>
上記ファイルを作って、cityテーブルも作成済みならば、下記コマンドを実行します。

```
rails db:seed
```

# 最後に
少しでも参考になればLGMTよろしくお願いします。
また、説明不十分な点や、説明が間違っている箇所が御座いましたら、コメントいただけると幸いです。

Twitterもやっているので、よかったらフォローお願いします！！
https://twitter.com/engineer_ikuzou