前回の続き
ゲーム登録画面の入力を簡単にするため、
IGDB APIを導入し タイトル 画像 ゲームジャンルなどを簡単に
呼び出せるようにした
IGDBとはなんぞやという方はこちらの記事をご参考にしてください。
さて
前回、とりあえずは検索、ヒット、取得ができたので
よしとしていましたが、
今回は三本の柱でより簡単に登録
できるようにしていきます。
IGDB三本の柱大作戦
1:日本語対応
→ 現在日本語に対応していない
API利用で切り抜ける
2:検索三種盛り作戦
→曖昧検索だけだったものを
部分一致 分割キーワード検索 スペース除去
この三つを盛り込んでよりヒットするように
3:重複を無くしていっぱい表示作戦
→ 50件の制限があるためそこをなるべく重複を避けた上で
表示させる
結論こうなりました
search("ダークソウル")
↓
contains_japanese? → true なので日本語ルートへ
↓
① search_by_alternative_names("ダークソウル") # 日本語のまま検索
② translate_to_english("ダークソウル") → "Dark Souls"
③ search_games_directly("Dark Souls") # 英語に変換して検索
↓
① + ③ を uniq でマージして返す
内容解説
作戦1 ゲーム検索機能
ゲームを検索する(日本語・英語を自動判定して振り分け)
日本語の場合は alternative_names 検索 + 英語翻訳して英語検索も実行する
def self.search(query)
token = get_token
if contains_japanese?(query)
results = search_by_alternative_names(query, token)
日本語→英語に変換して英語検索も実行
(IGDBに日本語alternative_nameが登録されていないゲームを拾うための補完)
english_query = translate_to_english(query)
results += search_games_directly(english_query, token) if english_query
results.uniq { |g| g["id"] }
else
search_games_directly(query, token)
end
end
日本語対応に関しては今回、
DeepLのAPIを利用します。
選定理由は2点
1:無料で1か月に500,000文字まで翻訳
という脅威の性能をしていた点
2:emailの登録だけで簡単に利用可能
翻訳系のロジックはここら辺に
DeepL APIで日本語クエリを英語に変換する
→ 変換失敗時は nil を返し、呼び出し元でスキップさせる
→ .env に DEEPL_API_KEY の設定が必要
private_class_method def self.translate_to_english(query)
uri = URI("https://api-free.deepl.com/v2/translate")
http = Net::HTTP.new(uri.host, 443)
http.use_ssl = true
req = Net::HTTP::Post.new(uri).tap do |r|
r["Authorization"] = "DeepL-Auth-Key #{ENV['DEEPL_API_KEY']}"
r["Content-Type"] = "application/json"
r.body = { text: [ query ], source_lang: "JA", target_lang: "EN" }.to_json
end
res = http.request(req)
unless res.code == "200"
return nil
end
JSON.parse(res.body).dig("translations", 0, "text")
rescue => e
nil
end
作戦2 検索三種盛り
1:部分一致
部分一致 (name ~ *"query"*)
→ 入力がタイトルの一部でもヒット。
例: "DARK SOULS" → "DARK SOULS III", "DARK SOULS: REMASTERED" 等にヒット。
部分一致検索(name ~ *"query"*)
private_class_method def self.partial_match_search(query, token)
body = <<~BODY
fields name, platforms.name, genres.name, cover.image_id;
where name ~ *"#{query}"*;
limit 50;
BODY
igdb_request("/games", body, token) || []
end
2:単語分割 AND条件に対応
単語分割 AND 条件検索("DARK" & "SOULS" 両方を含むゲームを取得)
→ "DARK SOULS", "DARK SOULS III" などがヒットする
private_class_method def self.word_and_search(words, token)
conditions = words.map { |w| "name ~ *\"#{w}\"*" }.join(" & ")
body = <<~BODY
fields name, platforms.name, genres.name, cover.image_id;
where #{conditions};
limit 50;
BODY
igdb_request("/games", body, token) || []
end
3:空白の削除
スペース除去版で部分一致検索(逆パターン補完)
no_space = sanitized.delete(" ")
results += partial_match_search(no_space, token) if no_space != sanitized
作戦3 重複除去
検索結果の重複除去 & カバーURL付加をまとめて行う
private_class_method def self.build_results(results)
results
.uniq { |g| g["id"] }
.map { |g| g.merge("cover_url" => build_cover_url(g.dig("cover", "image_id"))) }
end
中身についてはだいぶ端折ったのですが
大まかに動かしているロジックは上記の通りです。
実際にやってみた
これにより、今までよりも快適に検索、登録ができるようになりました。
最後に
このダークソウルというタイトルを選んだことで
「Dark Souls」
間に空白が入っている
DarkSoulsで検索しても出てこない
ダークソウルと日本語で入力しても出てこない
Darkのみでも検索件数が多すぎて出てこない
などいろんな「引っかからない問題」を発見できました。
ロジックを考えるのは大変でしたが、
しっかりと機能してくれてよかったです。


