[Rails]Arelは簡単or難関？使い方大全

# はじめに
あるプロジェクトの開発中、「Arel」というSQL文的なものが出てきました。
初めて見た僕は、「なにこれSQL文？」と疑問でした。調べてみると、Railsの簡単にSQLを使えるという機能の一つということがわかりました。
そこで、今後も使う可能性がありそうだったので、記事にまとめようと思った次第です。。
備忘録的にまとめてますが、誰かの為になれたらと思います。

## Arelとは
Arelとは，「⁠Relational Algebra」または「Active Relation」の略で、
その名前の通り、ActiveRecordから派生した、
「⁠関係代数」をRubyのオブジェクトで取り扱うライブラリのことです。

公式ドキュメントによると、、
1.複雑なSQLクエリを簡単に生成可能
2.色んなRDBMSに対応可能
3.フレームワーク(特にORM)のフレームワークとして開発された

つまり、Arelを使うことで、DBの互換性やSQL文字列の生成などに時間を取られることなく、
大事な設計やモデリングに注力してO/Rマッピング処理を実装することができるようになっています。

## 基本的な操作方法一覧

### arel_table
`モデル名.arel_table`とすることで指定したモデルのテーブルを検索する準備が出来る。
オプションとして、カラム名を追加したり、条件を追加して検索できる。

```ruby

Staff.where( Staff.arel_table[:age] )
```

### Arel.sql(〇〇〇〇)
`Arel.sql(〇〇〇〇)`とすることでSQL文を生成できる。
※〇〇にはSQL文を入れる。

```ruby

Arel.sql('〇〇〇〇')
```

### Arel::Nodes

#### Arel::Nodes#build_quoted
文字列などの値をラップして、`Arel::Nodes::NamedFunction`などの引数に渡せるようにします。

```ruby

Arel::Nodes.build_quoted(" ")
```

#### Arel::Nodes::NamedFunction
任意のSQL関数の呼び出しを表現するために使う。
第１引数に関数名、第２引数に関数に渡す引数（配列）、第３引数にエイリアス名（任意）を渡す。

```ruby

staffs_table = Staff.arel_table
date_format = Arel::Nodes.build_quoted("%Y-%m-%d")

Staff.select(
  Arel::Nodes::NamedFunction.new(
    "DATE_FORMAT", # 日時を指定のフォーマットに整形してくれる関数
    [staffs_table[:created_at], date_format], # created_atを指定したフォーマットに変換
    "registration_date"
  )
)
```

#### Arel::Nodes::SqlLiteral
生のSQL文字列を生成出来る。
SQLの構文中に必要な文字列をラップするのに使われる。

- utf8_general_ci
    - UTF-8文字コードで半角や全角、濁点を区別し、英語の大文字小文字を区別しない指定。

```ruby

Arel::Nodes::SqlLiteral.new("utf8_general_ci")
```

#### Arel::Nodes::InfixOperation
「infix operation」は二項演算という意。
２つの値・変数の計算を行うことができます。
第１引数に演算子、第２引数に演算子の左側の値、第３引数に演算子の右側の値が入る。

```ruby

Staff.where(
  Arel::Nodes::InfixOperation.new(
    "COLLATE", # 検索条件で照合順序を指定
    Staff.arel_table[:name], # 演算子の左側が、staffsテーブルのnameカラム
    Arel::Nodes::SqlLiteral.new("utf8_general_ci") # 演算子の右側が、utf8_genera_ci
  )
  .matches(Arel::Nodes.build_quoted("さとう%")) # nameが「さとう」から始まるStaffを取得
)
```

#### Arel::Nodes::OuterJoin
外部結合（OUTER JOIN）する際に使用する。
JOIN句の第２引数に `Arel::Nodes::OuterJoin` を指定し、
最後にjoin_sources を呼び出すことで結合できる。

```ruby

comments_table = Comment.arel_table
staffs_table = Staff.arel_table

Comment.joins(
  comments_table
    .join(staffs_table, Arel::Nodes::OuterJoin)
    .on(
      comments_table[:staff_id].eq(staffs_table[:id])
      .and(staffs_table[:is_active].eq(true))
    )
    .join_sources
)
```
※commentsテーブルのstaff_idとStaffテーブルのidが等しい、
かつstaffsテーブルの is_activeがtrueのものという条件。

#### Arel::Nodes::Descending, Arel::Nodes::Ascending
`Arel::Nodes::Descending`は ORDER 句の DESC （降順）の部分を担当。
`Arel::Nodes::Ascending`は ASC （昇順）です。引数には、ORDER BY の右側の値が入ります。

```ruby

Staff.order(
  Arel::Nodes::Descending.new(
    Staff.arel_table[:staff_type]) # staff_typeの降順
  )
)
```

#### Arel::Nodes::Case
SQL の CASE 構文を表現することができます。

- gt
    - 左項の値が右項の値より大きいときに真になる。(「>」と同意)

```ruby

staffs_table = Staff.arel_table
new_cond = staffs_table[:created_at].gt(Time.zone.now - 3.days)

Staff.order(
  Arel::Nodes::Ascending.new(
    Arel::Nodes::Case.new
      .when(new_cond).then(1)
      .else(9)
  )
)
```

Staffが作成されて3日以内であれば 1、そうでなければ 9 という値とする。
3日以内に作成されたStaffを昇順で並び替えている。

#### Arel::Nodes::Grouping
括弧()で囲む。四則計算等で括弧を使いたい場合に使う。

```ruby

Arel::Nodes::Grouping( object )
```

#### Arel::Nodes::As
AS による別名定義の文 `〇 AS ×` を作る。

```ruby

Staff.select( Staff.arel_table[:id].as('no') ) 
```

## Tips集
使えるTipsをご紹介。

### IS NOT NULL

```ruby

Staff.where(
  Staff.arel_table[:age].not_eq(20)
)
```

### AS

```ruby

Staff.where(
  Staff.arel_table[:name].as('admin')
)

admin = Staff.arel_table.alias('admin')
Comment.joins(admin).select(admin[:name])
```

### サブクエリ

#### FROM句へのサブクエリ

```ruby

new_table = Arel::Table.new(nil)
sql = new_table.from(
    Staff.where(created_at: start_at...end_at).to_sql
  )
  .project(Arel.sql('*'))
  .to_sql
```

#### JOIN句へのサブクエリ

```ruby

sub_query = Staff.where(created_at: start_at...end_at).to_sql
Comment.joins("INNER JOIN (#{sub_query})")
```

### SUM

```ruby

Staff.where(created_at: start_at...end_at)
  .select(Staff.arel_table[:name].sum().as('admin'))
```

### Where

```ruby

staffs = Staff.arel_table
staffs.arel_table.project(Arel.sql('*')).where(staffs[:id].eq(1)).to_sql
```

### order by

```ruby

staffs = Staff.arel_table
staffs.project(Arel.sql('*')).order(staffs[:id].asc).to_sql
```

### 集計関数とgroup by

```ruby

staffs = Staff.arel_table
staffs.project(staffs[:id].count, staffs[:name]).group(staffs[:age]).to_sql
```

### COUNTで利用するDISTINCT

```ruby

Staff.select(Staff.arel_table[:id].count())
  .select(Staff.arel_table[:id].count('distinct'))
```


# 終わりに
Arelでは色んな使い方があるのだなと調べて驚きました。
使いこなすには実践あるのみ！ですね。。
しかし、使うことに難を示す人たちもいるようで、賛否両論ではありますが、
チーム開発において、誰かが使って自分が理解出来ない。。ってことが起きると開発にブレーキをかけてしまうので、一応理解しているだけ得だと思いました！

# 参考
[Arel::Nodes を使って Arel で複雑な SQL文を作っちゃおう]
(https://qiita.com/Ping/items/87148251fe9bec2a847e)

[Arel::Nodesを最低限読めるようになりたい]
(https://qiita.com/akitoshi-n/items/00ea99a685863de60683)

[RailsのArelのTips]
(https://qiita.com/yamagen0915/items/b1721a9d1ea076f8cdc5)

[RailsのArelを調査してみた]
(https://qiita.com/tetz-akaneya/items/9ac200acbccc46b2ea44)

[Arelで色んなSQLを組み立ててみる]
(https://ryopeko.hatenablog.com/entry/20101215/1292373612)

[ActiveRecordのarel_tableから作れる条件式まとめ]
(https://qiita.com/akicho8/items/c3bfd39213d6e9843e4f)

[RailsでArel、早見表]
(https://qiita.com/k-fujino/items/09f8baf790abd49308f4)