Active Record クエリインターフェイスの自己結合を✅

引き続きアルコリズム(アルコール✖︎アルゴリズム)作成に苦戦中です。

今回作成したいアルコリズムは、

「ユーザーの酒の強さに応じたお酒の順番を提供すること」

です。

特に、「応じたお酒の順番」を作成することに苦戦していて、順番を格納するテーブルを作る?アルコールに順番番号を振ってソートで取り出す?など考えてみたのですが、うまくいってません。

路頭に迷い、RailsガイドやJavascriptのMDNを読み漁っていたら、自己結合型関連付けなるものを見つけ、もしかしたら解決の糸口になりそう、、?と思ったので学習していきます。

自己結合とは?

1つのテーブルを自分自身と結合する処理のこと。

(https://railsguides.jp/association_basics.html#自己結合)

同一テーブル内のカラム同士を紐づけることを指している。

一つのテーブルの中でM:Nの関連付けをする際に用いられている表現。

 

また、中間テーブルを作成して自己結合関連付けを行うこともできる。

TweetモデルにFollowとUnfollowのモデルを作成し、Relationshipsテーブルに外部キーをおいてM対Nを作るイメージ。

自己結合の考え方

1つのテーブルで外部結合や内部結合またはクロス結合を実施をしているため、結合の種類は問わない。

実際に使ってみる

冒頭にあげた「ユーザーの酒の強さに応じたお酒の順番を提供すること」を実装していく。

全体像

  1. Alcoholテーブルは居酒屋で提供されるお酒の情報が入っている。
  2. お酒はボックス(複数)売りと単品売りがある。

使用するTable

Alcohols: 下戸ほろ酔いボックス、酒豪酩酊ボックス 、酒豪ほろ酔いボックス、ビール、メガハイボール、焼酎、レモンサワー、日本酒、水

  • Alcohols テーブル

id 名前 酒の量(ml) 酒の度数(%)

1 下戸ほろ酔いボックス 1000  0

2 酒豪酩酊ボックス 12500 0

3 酒豪ほろ酔いボックス 5000 0

4 ビール  350 5

5 メガハイボール  700 7

6 焼酎  90 25

7 レモンサワー 350 22

8 日本酒  180 15

9 水 0 0

10 烏龍茶 0 0

11 ビール一口 20 5

Relationshipsテーブル: liquor_id, liquor_box_id

liquor_idは単品のお酒に振られる

liquor_box_idはボックス売りされているお酒に振られている。

id liquor_box_id liquor_id

1  1 11

2 1 10

3 1 9

4 1 9

5 2 4

6 2 5

7 2 8

8 2 6

9 3 4

10 3 7

11 3 10

12 3 9

Relationshipsテーブルを解説

自己結合の考え方を使って、それぞれのセットと内容を紐づけている。

下戸ほろ酔いボックス(liquor_box_id=1)に紐づけられたお酒

⇨ビール一口、烏龍茶、水、水

酒豪酩酊ボックス(liquor_box_id=2)に紐づけられたお酒

⇨ ビール、メガハイボール、日本酒、いも焼酎

酒豪ほろ酔いボックス(liquor_box_id=3)に紐づけられたお酒

⇨ ビール、レモンサワー、烏龍茶、水

Migrationファイルの構成

ActiveRecord::Schema.define(version: 2021_11_23_054020) do

 create_table "alcohols", charset: "utf8mb4", force: :cascade do |t|
    t.string "name"
    t.float "alcohol_percentage"
    t.integer "alcohol_amount"
    t.text "description"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

 create_table "relationships", charset: "utf8mb4", force: :cascade do |t|
    t.integer "liquor_box_id", null: false
    t.integer "liquor_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["liquor_box_id"], name: "index_relationships_on_liquor_box_id"
    t.index ["liquor_id"], name: "index_relationships_on_liquor_id"
  end

次にAlcoholモデルとRelationshipモデルに関してで、以下のとおり実装することができる。

Alcoholモデル

class Alcohol < ApplicationRecord

# まとめ売りボックス(liquor_box_id)に属するお酒(liquor_id)を取得するassociation
# 自分のテーブル内に外部キー"liquor_box_id"置くことで、各商品と紐付けるフックを作成できてる。
  has_many :liquor_relationships, class_name: 'Relationship', foreign_key: 'liquor_box_id', dependent: :destroy

# 各お酒が属するliquor_box_idを取得するassociation
# 自分のテーブル内に外部キー"liquor_id"を置くことで、各メニューと紐付けるフックを作成できてる。
  has_many :liquor_box_relationships, class_name: 'Relationship', foreign_key: 'liquor_id', dependent: :destroy

# 酩酊酒豪ボックス(liquor_box)からメガハイボール、ビール等(liquor)を取得する
# has_many :紐付け対先のテーブル名, through: :中間テーブル名

  has_many :liquors, through: :liquor_relationships

# ビール(liquor)から酩酊酒豪ボックス(liquor_box)を取得する
  has_many :liquor_boxes, through: :liquor_boxes_relationships
end
class Relationship < ApplicationRecord
    belongs_to :liquor, class_name: 'Alcohol'
    belongs_to :liquor_box, class_name: 'Alcohol'
end

SQL文を走らせてみる。

欲しいデータ3個(下戸ほろ酔いボックス、酒豪酩酊ボックス 、酒豪ほろ酔いボックス)ある。

しかし知っているクエリーインターフェイスはfind,find_by,firstくらいしか知らない。

Rails のループ処理(each, find_each, find_in_batches)

ActiveModelで複数の値を取得するクエリーインターフェイスは幾つか存在するそう。

今回はサーバーにクエリーを発行する回数を制限できるfind_eachを使ってみることにした。

find_each

find_eachメソッドは、複数のレコードを一括で取り出し、続いて  レコードを1つのブロックにyieldします。以下の例では、find_eachでバッチから1000件のレコードを一括で取り出し、各レコードをブロックにyieldします。

以下のように、括弧内のメソッドに該当するデータを全て取得する。

User.limit(2).find_each{|a| p  a.nickname}
  User Load (3.9ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 2
"テス太郎"
"subaru"

limit(2)をつけると引数分のデータのみを取得する事ができる。

今回は、userのnicknameのみを取得している。

Active Record クエリインターフェイス - Railsガイド

今回作成したコントローラー。

dデータを表示するだけなのでindexアクションに記述している。

module Api
  module V1
    class AlcoholsController < ApplicationController
			def index
				@alcohols = Alcohol.limit(23).find_each{|a| p a.liquors }
			        render json: @alcohols
			end
		end
	end
end

実際に発行されたSQL

Image from Gyazo

紐づけたデータ達がこんな感じでコンソール上に表示されている。

課題

データを紐づける事ができたのはいいが、find_eachではデータをviewに渡す事ができないようで。

どうやってviewに渡すか考えないといけない。

自己結合って結局何

1つのテーブルが分身して合体するテーブル結合のやり方

意気込み

なんとかアルゴリズムを完成させたい!!

自己結合とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

自己結合を使って、カレーセットとカレーを関係付けてみた(フォローのassociationの勉強になるかも) | TechEssentials

Active Record の関連付け - Railsガイド