【超簡単】Railsで料理や食材のカロリー検索してみた〜

# やりたいこと
*1. 食材データをデータベースに保存
(料理名、食材名とその成分が一覧になってるexcel)

https://drive.google.com/drive/folders/1ZvJx-wa5JVzKoT0Rmcbv6Nc9fv8zdgo7?usp=sharing

参考：[日本食品標準成分表2020年版（八訂）　第2章（データ） (Excel:1.9MB)](https://www.mext.go.jp/a_menu/syokuhinseibun/mext_01110.html)

*2. カロリー検索によって近いカロリーの料理を表示

# rails アプリの作成
```terminal:terminal
rails 6.1.5 new rails_food_app
cd rails_food_app
```
# Excelのダウンロード
[このドライブ](https://drive.google.com/drive/folders/1ZvJx-wa5JVzKoT0Rmcbv6Nc9fv8zdgo7?usp=sharing)内にあるfood_list.xlsxファイルをダウンロードし、railsアプリ内の「assets/Excel/food_list.xlsx」に移す
（Excelフォルダは自分で作成）
![Screenshot 2023-02-28 18.28.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2586059/c9668456-f3b3-607a-fea0-a58f9a004fe0.png)
こんな感じになってればおけです！

ちなみにこのExcelの1列目がそのままカラム名になります。
詳細↓↓↓
|意味|カラム名|
|---|---|
|食品名|food_name|
|カロリー|kcal|
|水分|water|
|タンパク質|protein|
|脂質|lipid|
|食物繊維総量|fibre|
|炭水化物|carbohydrate|
|ナトリウム|Na|
|カルシウム|Ca|
|マグネシウム|Mg|
|鉄|Fe|
|ビタミンC|vitaminC|
|食塩相当量|salt|

# Excelファイルからデータベースへ書き込み
今回はrooというgemを使ってExcel内の情報を取得しデータベースへ書き込んでいきます

Gemfileの一番下に追記
```ruby:Gemfile
gem "roo", "~> 2.9.0"
```
```terminal:terminal
bundle install
```
読み込むための下準備
controller、table作成、カラム準備
```terminal:terminal
rails g controller foods
rails g model Food
```
```ruby:db/migrate/~~~~~~~~~~_create_foods.rb
class CreateFoods < ActiveRecord::Migration[6.1]
  def change
    create_table :foods do |t|
# 追記
      t.string :food_name
      t.integer :kcal
      t.float :water
      t.float :protein
      t.float :lipid
      t.float :fibre
      t.float :carbohydrate
      t.float :Na
      t.float :Ca
      t.float :Mg
      t.float :Fe
      t.float :vitaminC
      t.float :salt
# 追記終わり

      t.timestamps
    end
  end
end
```
`development.sqlite3`ファイルを削除
```terminal:terminal
rails db:migrate
```
`schema.rb`でfoodsテーブルができてたらok ↓↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2586059/8d40c5c7-3ddc-be2e-8a31-331a3f71b6a2.png)

controller
```ruby:foods_controller.rb
class FoodsController < ApplicationController
  def index
  end
end
```
`view/foods/`の下にindex.html.erbを作成
```html:index.html.erb
<%= button_to '食材登録', {controller: 'foods', action: 'read'} %>
```
ここではのちに作るreadアクションを呼び出しています
つまりこのボタンが押されるとreadアクションでexcelから情報の読み取り、データベースへの書き込みをしているということです

```ruby:config/routes.rb
  get "/foods/index", to: "foods#index"
  post "/foods/read", to: "foods#read"
```
readアクションを定義していきます
```ruby:foods_controller.rb
  def read
    # Excelファイルの指定
    excel = Roo::Excelx.new('app/assets/Excel/food_list.xlsx')
    # Excelファイル内のシートを指定
    sheet = excel.sheet('Sheet1')
    # シートを1列ごとにハッシュの形にして取得
    # <指定したいkey>: '<Excelの1列目の名前>'
    # っていう感じになってます
    rows = sheet.parse(
      food_name: 'food_name',
      kcal: 'kcal',
      water: 'water',
      protein: 'protein',
      lipid:'lipid',
      fibre:'fibre',
      carbohydrate:'carbohydrate',
      Na:'Na',
      Ca:'Ca',
      Mg:'Mg',
      Fe:'Fe',
      vitaminC:'vitaminC',
      salt:'salt'
    )
    # 取得したハッシュを順にデータベースへ書き込む
    rows.each do |row|
      Food.create(row)
    end
  end
```
ここまでで書くとこは終了です
一旦localhostを立ち上げてみましょう
```terminal:terminal
rails s
```
indexページ
http://localhost:3000/foods/index
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2586059/9da85c7d-fc63-318a-f75f-620b36b56d02.png)
こんなボタンが出てればオッケーです
押すと長めのロードが入ると思います
理由はこのボタンが押されるとfoodsコントローラのreadアクションが呼び出されます
つまり、Excelのすべての行をデータベースに書き込んでいる途中って感じです
1分くらいで僕は終わりました
終わったらデータベースにちゃんと値が保存されているか確認しましょう
```terminal:terminal
rails c
```
```
Food.all
```
```
Food.find(1)
```
感な感じで適当にやったらデータ保存できていることが確認できると思います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2586059/a6617499-fb79-b10a-38b8-20166fb31ac2.png)
ここまで行けたらassets/Excel/food_list.xlsxファイルは削除しても構いません
index.html.erbのボタンは本番環境でも同様にデータを書き込むためにコメントアウトしておくことをおすすめします

# 料理の表示
今回は食品一覧の中からランダムで1つとっていきたいと思います
```ruby:foods_controller.rb
  def index
    @rand_food = Food.where( 'id >= ?', rand(Food.first.id..Food.last.id) ).first
  end
```
これはFoodの中の最初のidから最後のidまでのランダムな1つのidをrand関数で生成し、取得しています
```html:views/foods/index.html.erb
<%# <%= button_to '食材登録', {controller: 'foods', action: 'read'} %>

<h1>今日はこれだ</h1>

<h2><%= @rand_food.food_name %></h2>

<p><%= link_to 'もう一回', foods_index_path %></p>
```
![Screenshot 2023-02-28 19.25.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2586059/5a55dad4-2fe0-6de6-4e57-f0c03e68ae26.png)
こんな感じになりました〜

# カロリー検索
index.html.erbの一番下に追記
```html:views/foods/index.html.erb
<h1>カロリー検索</h1>

<%= form_tag({controller:"foods",action:"index"}, method: :get) do %>
  <%= number_field_tag :calorie_search %>kcal <br>
  <%= submit_tag '検索する'  %>
<% end %>

<div class="calories-result">
  <% @near_calories.each do |food| %>
    <h2><%= food.food_name %> <%= food.kcal %></h2>
  <% end %>
</div>
```
```ruby:foods_controller.rb
  def index
    @rand_food = Food.where( 'id >= ?', rand(Food.first.id..Food.last.id) ).first

# 追記
    @near_calories = []
    if params[:calorie_search].blank?
      @near_calories = []
    else
      @near_calorie_id_list = Food.pluck(:id, :kcal).min_by(5) { |x, y| (params[:calorie_search].to_i - y.to_i).abs }
      @near_calories = @near_calorie_id_list.map!{|x, y| Food.find(x)}
    end
# 追記終わり
  end
```
#### 解説
```
@near_calorie_id_list = Food.pluck(:id, :kcal).min_by(5) { |x, y| (params[:calorie_search].to_i - y.to_i).abs }
```
`Food.pluck(:id, :kcal)`
Foodの中のid、kcalのカラムだけ取得して2次元配列にする
`min_by(5)`
次に書く条件の中で最小の5つを取得する
`{ |x, y| (params[:calorie_search].to_i - y.to_i).abs }`
min_byの条件で、(検索した値) - (データの各カロリー)の結果順にしている


今日のコードたち

https://github.com/naoya25/rails_food_app