ActiveRecordを使った感想とメモ

# ActiveRecordを使った感想とメモ

ActiveRecordを久々に使用したが、結構忘れていたのでこの機会にメモ。
(ついでに今回使ってみた感想を記載)

## サンプルで使用するDB初期化用SQL

```sql:hoge.sqlite3.sql
DROP TABLE IF EXISTS books;
DROP TABLE IF EXISTS authors;

CREATE TABLE books (
  id integer NOT NULL,
  author_id integer,
  name text,
  PRIMARY KEY(id)
);
CREATE TABLE authors (
  id integer NOT NULL,
  name text,
  PRIMARY KEY(id)
);

INSERT INTO authors(name) VALUES('太郎');
INSERT INTO authors(name) VALUES('二郎');
INSERT INTO authors(name) VALUES('三郎');
INSERT INTO authors(name) VALUES('四郎');
INSERT INTO books(author_id, name) VALUES((SELECT id FROM authors WHERE name = '太郎'), 'hoge');
INSERT INTO books(author_id, name) VALUES((SELECT id FROM authors WHERE name = '太郎'), 'fuga');
INSERT INTO books(author_id, name) VALUES((SELECT id FROM authors WHERE name = '二郎'), 'piyo');
INSERT INTO books(author_id, name) VALUES((SELECT id FROM authors WHERE name = '二郎'), 'foo');
INSERT INTO books(author_id, name) VALUES((SELECT id FROM authors WHERE name = '四郎'), 'bar');
```

## 使い方メモ

### 事前準備

https://railsguides.jp/active_record_basics.html

ActiveRecordを使う場合、以下のようにして接続とモデルを記載する。

```ruby
require 'active_record'

ActiveRecord::Base.establish_connection(adapter:'sqlite3', database: './hoge.db')

class Book < ActiveRecord::Base
end

class Author < ActiveRecord::Base
end
```

ちなみにRubyのrequireは何度実行しても1回しか読み込まれないので、いろんな箇所でrequireしてもコネクションを過度に使用されることはない。

### 単一要素取得(PK指定)

```ruby
p Book.find(1)
#<Book id: 1, author_id: 1, name: "hoge">
```
レコードがない場合はActiveRecord::RecordNotFoundの例外がスローされる。

### 単一要素取得(カラム指定)

```ruby
p Book.find_by(name:'hoge')
#<Book id: 1, author_id: 1, name: "hoge">
p Book.find_by(name:'hoge', author_id:1)
#<Book id: 1, author_id: 1, name: "hoge">
```
* レコードがない場合はnil。
* 要素は「v.author_id」のような感じでカラムを取得。

### 要素取得(カラム指定)

```ruby
p Book.where(author_id:1)
#<ActiveRecord::Relation [#<Book id: 1, author_id: 1, name: "hoge">, #<Book id: 2, author_id: 1, name: "fuga">]>
p Book.where(author_id:[1,2])
#<ActiveRecord::Relation [#<Book id: 1, author_id: 1, name: "hoge">, #<Book id: 2, author_id: 1, name: "fuga">, #<Book id: 3, author_id: 2, name: "piyo">, #<Book id: 4, author_id: 2, name: "foo">]>
```

find_by↓と違い、複数要素を取得できる。

```ruby
Book.find_by(author_id:1)
#<Book id: 1, author_id: 1, name: "hoge">
```

### 全要素取得

```ruby
Book.all
#<ActiveRecord::Relation [#<Book id: 1, author_id: 1, name: "hoge">, #<Book id: 2, author_id: 1, name: "fuga">, #<Book id: 3, author_id: 2, name: "piyo">, #<Book id: 4, author_id: 2, name: "foo">, #<Book id: 5, author_id: 4, name: "bar">]>
```

### 関連要素を取得

モデルを定義する時にbelongs_toやhas_one、has_manyを指定することで以下のように関連要素を取得可能( https://railsguides.jp/association_basics.html )。

```ruby
class Book < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_many :book
end

Author.where(id:1).each{|v| v.book.each{|b| p b}}
#<Book id: 1, author_id: 1, name: "hoge">
#<Book id: 2, author_id: 1, name: "fuga">
```

上記は著者(1)に紐づく本(多)の例。逆の場合は「Book.find(1).author.name」のように指定する。
上記の機能は便利だが、N+1問題と呼ばれる問題を抱えている(N+1問題はここでは省略)。
N+1問題を回避するにはincludes()を使用する。

### 要素削除

```ruby
v =  Book.find(1)
v.destroy
```
### 要素追加

要素追加は以下のように実施する。

```ruby
v =  Book.new({name:'baz', author_id:1})
v.save
```

```ruby
v =  Book.new
v.attributes = {name:'baz', author_id:1}
v.save
```

```ruby
v =  Book.new
v.name = 'baz'
v.author_id = 1
v.save
```

上記の3パターンは全て同じ結果である。


### 要素更新

```ruby
v =  Book.find_by(name:'baz')
v.author_id = 3
v.save
```

### トランザクション

```ruby
ActiveRecord::Base.transaction do
  v =  Book.find_by(name:'foo')
  v.destroy
end
```

## 感想

以下、多少まともに使ってみた感想。

### 命名規則

ActiveRecord特有のテーブル名やカラム名が面倒だった。
JavaのJPAに慣れていると要素を1つ1つ定義する手間がないのは良いが(普通はIDEとかで自動生成するとは思うが)、その分ActiveRecord特有のテーブル名に設定しておかないと手間が増えるのは不便だった。
例えばデフォルトでは、クラス名のBookがテーブル名booksに相当したり、Book#author_idがAuthor#idに関連付けられたりする。
別途指定できなくもないが(テーブル名を指定する例↓)、その分複雑度が増すので、結局のところActiveRecordを使用するなら、ActiveRecord特有の命名規則に従う方が良さそうだ。

```ruby

class Book < ActiveRecord::Base
  self.table_name = 'book'
end
```

### リポジトリ層(パターン)が微妙

RDB以外のDBを使用する場合、リポジトリ層を導入することで、DBの種類を抽象化することができる。
……が、Rubyのコードが元々抽象化されていてシンプルであることや、ActiveRecordの癖が強いため、実際やってみると微妙だった。

サービス層で業務ロジックを実装しようとした場合、当然トランザクションを扱う。
そのため、ActiveRecordのメソッドがサービスとリポジトリ、モデルに分かれる。
しかし、Rubyは基本的にコードが抽象化されてシンプルであるため、下手にコードを分散しても逆に可読性が低下する。
(しかもリポジトリ層はモデルの処理をただ委譲してるだけのことがほとんどだった)

結局私の場合はRDB以外の処理をActiveRecordのfindやfind_byに似せることにした。