今更ながら Ruby on Rails Tutorial をやってみた (その14)

## この記事について…

普段はRuby on Railsと無縁なプログラマなんですが、今更ながら**面白そう**という単純な理由から[**Ruby on Rails Tutorial**](https://railstutorial.jp/)をやってみようと思い立ちました。今回は[前回の記事](https://qiita.com/hideki0145/items/bef1df6c42e310f86d8d)の続きで第14章をやっていきます。
尚、Rails 5.1に対応した第4版を用いて、かつHyper-V上にインストールしたUbuntu 18.04の環境で進めていきます。

## 第14章 ユーザーをフォローする

この章では、自分以外のユーザーをフォローする/フォローされる機能を追加します。
本章を終えればチュートリアル1週目完走です！

### SQLを別の書き方で書けないだろうか

14.3.3にて、SQLの最適化を行いました。

```ruby:app/models/user.rb
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を再度確認します。

```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)`とすればいいので、組み合わせると、

```ruby
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))`としてやれば良さそうです。
これで別の書き方が完成しました！

```ruby:app/models/user.rb
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を確認すると…

```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週目もそのうちやってみようかなぁと思います。
その時は次のことをやってみたいです。

1. テストは[RSpec](https://github.com/rspec/rspec-rails)で書く！
   ([Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp/)を購入させていただいたので、ちょこちょこ読んで学習中。)

2. ERBではなく[Slim](https://github.com/slim-template/slim-rails)を使ってみる！
3. [Rubocop](https://github.com/rubocop-hq/rubocop)を使って[Ruby Style Guide](https://github.com/fortissimo1997/ruby-style-guide/blob/japanese/README.ja.md)や[Rails Style Guide](https://github.com/satour/rails-style-guide/blob/master/README-jaJA.md)を守ってコーディングする！
4. Herokuではなくオンプレサーバー上にproduction環境を作ってみる！

尚、今回は開発環境としてAWS Cloud9ではなくVagrantやHyper-V上のUbuntuを使用しましたが、全く問題なくチュートリアルを進めることができました。(Vagrantは共有フォルダの部分でエラーが出たりしましたが、ググればすぐに解決策が出てくるので問題なかったです。)
もし今後チュートリアル2週目に入る方が読んでいらっしゃったら、是非ローカルの開発環境を自分でセットアップすることにもチャレンジしてみてください！