この記事について…
普段はRuby on Railsと無縁なプログラマなんですが、今更ながら面白そうという単純な理由からRuby on Rails Tutorialをやってみようと思い立ちました。今回は前回の記事の続きで第14章をやっていきます。
尚、Rails 5.1に対応した第4版を用いて、かつHyper-V上にインストールしたUbuntu 18.04の環境で進めていきます。
第14章 ユーザーをフォローする
この章では、自分以外のユーザーをフォローする/フォローされる機能を追加します。
本章を終えればチュートリアル1週目完走です!
SQLを別の書き方で書けないだろうか
14.3.3にて、SQLの最適化を行いました。
class User < ApplicationRecord
.
.
# ユーザーのステータスフィードを返す
def feed
# 1. 最初に実装したコード
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
# 2. 名前付きプレースホルダを使用するよう改善
# Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
# following_ids: following_ids, user_id: id)
# 3. 効率的にデータを取得するよう改善
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id)
end
.
.
end
チュートリアルでは[3.]の書き方で終了しましたが、もう少し綺麗に書けないかな…と思って色々試行錯誤してみました。
まず、[3.]で実際に生成されるSQLを再度確認します。
SELECT "microposts".*
FROM "microposts"
WHERE (user_id IN (SELECT followed_id
FROM relationships
WHERE follower_id = 1)
OR user_id = 1)
これと等しいSQLが生成される別の書き方を探してみます。
まずWHERE (user_id IN (SELECT followed_id FROM relationships WHERE follower_id = 1) ...
について考えてみます。
ActiveRecordではSQLのIN式を使用してレコードを検索したい場合、
Client.where(orders_count: [1,3,5])
のように書くことができ、このときSELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
というSQLが生成されます。
参考
Active Record クエリインターフェイス
2.3.3 サブセット条件
https://railsguides.jp/active_record_querying.html#%E3%82%B5%E3%83%96%E3%82%BB%E3%83%83%E3%83%88%E6%9D%A1%E4%BB%B6
SELECT followed_id FROM relationships WHERE follower_id
というSQLを生成するにはactive_relationships.select(:followed_id)
とすればいいので、組み合わせると、
Micropost.where(user_id: active_relationships.select(:followed_id))
とすると良さそうです。
次に、... OR user_id = 1)
の部分ですが、Rails 5からはOR条件を使用することが可能です。
Client.where(locked: true).or(Client.where(orders_count: [1,3,5]))
のように書くことができ、このときSELECT * FROM clients WHERE (clients.locked = 1 OR clients.orders_count IN (1,3,5))
というSQLが生成されます。
参考
Active Record クエリインターフェイス
2.5 OR条件
https://railsguides.jp/active_record_querying.html#or%E6%9D%A1%E4%BB%B6
今回の場合は.or(Micropost.where(user_id: id))
としてやれば良さそうです。
これで別の書き方が完成しました!
class User < ApplicationRecord
.
.
# ユーザーのステータスフィードを返す
def feed
# 4. 別の書き方
Micropost.where(user_id: active_relationships.select(:followed_id))
.or(Micropost.where(user_id: id))
end
.
.
end
[4.]で実際に生成されるSQLを確認すると…
SELECT "microposts".*
FROM "microposts"
WHERE ("microposts"."user_id" IN (SELECT "relationships"."followed_id"
FROM "relationships"
WHERE "relationships"."follower_id" = 1)
OR "microposts"."user_id" = 1)
[3.]の時とちょっと違うのは、ActiveRecordが「テーブル名.列名」の規則でSQLを生成してくれているところでしょうか。
こちらの方が列名が一意になっていいですね。
1週目完走!
ということで、ひとまずRuby on Rails Tutorialを1週することができました!
一通りのRails開発を学ぶことができ、実務でも役に立つと思います。
ただ、まだまだ理解しきれていない部分があるのも事実なので、2週目もそのうちやってみようかなぁと思います。
その時は次のことをやってみたいです。
テストはRSpecで書く!
(Everyday Rails - RSpecによるRailsテスト入門を購入させていただいたので、ちょこちょこ読んで学習中。)ERBではなくSlimを使ってみる!
Rubocopを使ってRuby Style GuideやRails Style Guideを守ってコーディングする!
Herokuではなくオンプレサーバー上にproduction環境を作ってみる!
尚、今回は開発環境としてAWS Cloud9ではなくVagrantやHyper-V上のUbuntuを使用しましたが、全く問題なくチュートリアルを進めることができました。(Vagrantは共有フォルダの部分でエラーが出たりしましたが、ググればすぐに解決策が出てくるので問題なかったです。)
もし今後チュートリアル2週目に入る方が読んでいらっしゃったら、是非ローカルの開発環境を自分でセットアップすることにもチャレンジしてみてください!