やりたいこと
*1. 食材データをデータベースに保存
(料理名、食材名とその成分が一覧になってるexcel)
参考:日本食品標準成分表2020年版(八訂) 第2章(データ) (Excel:1.9MB)
*2. カロリー検索によって近いカロリーの料理を表示
rails アプリの作成
rails 6.1.5 new rails_food_app
cd rails_food_app
Excelのダウンロード
このドライブ内にあるfood_list.xlsxファイルをダウンロードし、railsアプリ内の「assets/Excel/food_list.xlsx」に移す
(Excelフォルダは自分で作成)

こんな感じになってればおけです!
ちなみにこのExcelの1列目がそのままカラム名になります。
詳細↓↓↓
| 意味 | カラム名 | 
|---|---|
| 食品名 | food_name | 
| カロリー | kcal | 
| 水分 | water | 
| タンパク質 | protein | 
| 脂質 | lipid | 
| 食物繊維総量 | fibre | 
| 炭水化物 | carbohydrate | 
| ナトリウム | Na | 
| カルシウム | Ca | 
| マグネシウム | Mg | 
| 鉄 | Fe | 
| ビタミンC | vitaminC | 
| 食塩相当量 | salt | 
Excelファイルからデータベースへ書き込み
今回はrooというgemを使ってExcel内の情報を取得しデータベースへ書き込んでいきます
Gemfileの一番下に追記
gem "roo", "~> 2.9.0"
bundle install
読み込むための下準備
controller、table作成、カラム準備
rails g controller foods
rails g model Food
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ファイルを削除
rails db:migrate
schema.rbでfoodsテーブルができてたらok ↓↓

controller
class FoodsController < ApplicationController
  def index
  end
end
view/foods/の下にindex.html.erbを作成
<%= button_to '食材登録', {controller: 'foods', action: 'read'} %>
ここではのちに作るreadアクションを呼び出しています
つまりこのボタンが押されるとreadアクションでexcelから情報の読み取り、データベースへの書き込みをしているということです
  get "/foods/index", to: "foods#index"
  post "/foods/read", to: "foods#read"
readアクションを定義していきます
  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を立ち上げてみましょう
rails s
indexページ
http://localhost:3000/foods/index

こんなボタンが出てればオッケーです
押すと長めのロードが入ると思います
理由はこのボタンが押されるとfoodsコントローラのreadアクションが呼び出されます
つまり、Excelのすべての行をデータベースに書き込んでいる途中って感じです
1分くらいで僕は終わりました
終わったらデータベースにちゃんと値が保存されているか確認しましょう
rails c
Food.all
Food.find(1)
感な感じで適当にやったらデータ保存できていることが確認できると思います

ここまで行けたらassets/Excel/food_list.xlsxファイルは削除しても構いません
index.html.erbのボタンは本番環境でも同様にデータを書き込むためにコメントアウトしておくことをおすすめします
料理の表示
今回は食品一覧の中からランダムで1つとっていきたいと思います
  def index
    @rand_food = Food.where( 'id >= ?', rand(Food.first.id..Food.last.id) ).first
  end
これはFoodの中の最初のidから最後のidまでのランダムな1つのidをrand関数で生成し、取得しています
<%# <%= button_to '食材登録', {controller: 'foods', action: 'read'} %>
<h1>今日はこれだ</h1>
<h2><%= @rand_food.food_name %></h2>
<p><%= link_to 'もう一回', foods_index_path %></p>
カロリー検索
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>
  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の条件で、(検索した値) - (データの各カロリー)の結果順にしている
今日のコードたち
