Active Recordの基本的な操作メモ

# 目的
sinatraからのDB利用時、最初によく使いそうなActiverecordの基本的な操作と知識をまとめる

詳細は公式ドキュメントで把握した方が早い
> [Raisl Guide](https://railsguides.jp/active_record_basics.html)

# 前提
- Mac High Sierra
 - windowsでやる場合はvagrant上で
- ruby 2.4.1 (rubyが入っていない場合は、下部参照を確認) (バージョンが変わらない場合は、下部参照を確認)
- sqlite3 3.19.3

> Sinatraの導入 
    https://qiita.com/yukihigasi/items/ffce19609ad8d935b7b0
> sinatraでアプリを作る
https://qiita.com/yukihigasi/items/284418046b8aac55d05b

- 続き、という訳ではないので、新規にプロジェクトを作って行います。

# 手順
## 手軽に試せる環境の準備
### Activerecordの準備

```
bundle init
```

- activerecordとsqlite3のgemを追加

```ruby:Gemfile
source 'https://rubygems.org'
gem 'activerecord'
gem 'sqlite3'

```

- bundle installでインストールします
- `bundle install`

### 練習用インメモリDBの準備
- sqlite3データベースと、スキーマを作ります
- とりあえずテスト用として、`seeds.sql`にDDLを書き、以下のようにdbファイルを作成します

```
$ sqlite3 db1.db < seeds.sql
```

- テーブル
 - 単数のusersと多数のposts、という一対多構造のテーブルは以下のように書きます
 - 外部キーを、多側は　{テーブル名}_id　`user_id integer` のように持たせる必要があります
 - id をprimary key;　制約をつけると、明示的に指定しない場合、自動的にキーを付与してくれます
 - created_at、updated_at　というカラムを設定すると、自動的にタイムスタンプを保持してくれます
 - 一応、サンプルとして、以下のようなDBとします
 - なんども初期化して実行を試すのであれば`
drop table if exists テーブル名;`を　ファイルの頭に設定して置いてください

```sql:seeds.sql
create table users (
  id integer primary key,
  name text,
  age integer,
  created_at,
  updated_at
);

create table posts (
  id integer primary key,
  user_id integer,
  message text,
  created_at,
  updated_at
);
``` 

- activerecordの接続設定は以下のように行います
 - pp は、ActiveRecordのモデルクラスを、そのままではオブジェクトIDが出てしまうので、読みやすいように出力してくれます
 - loggerは実行されたSQLなどを出力してくれます

```ruby:
require 'active_record'
require 'pp'
require 'logger'

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.default_timezone = :local

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

- ここから、activerecordの操作の内容になりますが、
- 特に手順、という訳ではなく、こうしたい時、こうする、というのを列記していきます


---

## CLUD
- 今回、Userというモデルが、id、name、age３つの項目を持っている場合を想定し例示します

```ruby
class User < ActiveRecord::Base
end
```
### データの追加
- 以下のようにcreateメソッドで、カラム名をシンボルで指定して作成できる

```ruby
User.create(name: "testuser", age: 22)
```
- オブジェクトを作り、値を代入する方法も可能
- この場合は、saveメソッドを使い永続化する必要がある

```ruby
user = User.new
user.name = 'testuser'
user.age = 27
user.save
```
### データの表示

```ruby

User.find_all
```
- で全件の取得が可能
- select句のように、取得する項目を選択する場合は、selectメソッドを先に使う

```ruby
User.select("id, name").find_all
```
- find(数値) で検索をすると、自動的に主キーで検索が行われる
- 以下のようにfindメソッドは、項目を取ることもできる

```ruby
User.find(3)
User.find_by_id(3)
User.find_by(name: "testuser")
User.find_by name: "testuser"
User.find_by_name("testuser")
```
> 参考　http://railsdoc.com/references/find

- firstメソッド、lastメソッドで、最初のデータや最後のデータから取得することができる
- 引数に値を持つことができ、その場合「何番目」のデータであるかを指定できる

```ruby
User.first
User.last
User.first(3)
```

-  存在しない場合
 - find_byメソッドに結果がない場合はnilが返される
 - find_byメソッドに`!`マークを付与した場合、検索なしはエラーとしてエラーが返却される

```ruby
User.find_by_id 100
User.find_by_id! 100
```

- where句による検索
 - カラム名のシンボルに値を渡すことで、絞り込みができる
 - 複数の値を与えるばあいは、配列を渡すことができる `[21,22,23]`
 - 範囲を与えることもできる`21..23`
 
```ruby
User.where(age: 21)
```

- AND条件
 - where条件は、複数組み合わせることで、AND条件として絞り込むことができる
 - 以上は以下のように`"age <= 22"` 定義することができる

```ruby
User.where("age <= 22").where(name: "testuser")
```
- OR条件で検索
 - whereメソッドは、orメソッドでor条件を追加することができる
 - この時、orメソッドの引数には、モデル.where、の構文が必要になる

```ruby
User.where(name: "ni").or(User.where(age: 22)).select("id, name")
```

- 以外（NOT）を検索する
 - whereメソッドは、notメソッドで、除外するデータを指定できる

```ruby
User.select("id, name, age").where.not(name: "yaki")
```
### プレースホルダ
- `User.where(name: "ni").or(User.where(age: 22))`
- 上のwhereは、プレースホルダを利用して、以下のように表記ができる
- `User.where("name  == ?", {変数}).or(User.where("age == ?", {変数}))`
- SQLインジェクションを防ぐため、プレースホルダを利用するべき
- また、プレースホルダは、シンボルで定義することも可能

```ruby
User.where("name  == :name", {name: name}).or(User.where("age == :age", {age: age})).select("id, name")
```

### データの編集
- 第一引数に検索キーを指定し、カラム名のシンボルと値を指定して更新する
- 以上を更新する、といった場合は "カラム名<=値" をwhereに引数で渡し、updateすることで実現できる

```
User.update(1, age: 30)
User.where("age <= 21").update(age: 23)
```

- 特定の属性を変更する場合は、attributesメソッドを利用する

```
user.attributes = { name: "testuser3", age: 10 }
```

> update_attribute とupdate_attribtues の違い
> http://d.hatena.ne.jp/LukeSilvia/20080816/p2

### データの削除
- deleteメソッド、またはdestroyメソッドが利用できる
 - destroyメソッドの場合は、関連テーブルも含めて削除を行うことができる（ただしモデルに事前に定義が必要）
 - 引数には、条件を文字列で引数に取ることもできる

```rubu
User.delete(1)
User.destroy_all("updated_at < '2004-04-04'")
```

## 使いそうな応用
### ソート
- orderメソッドを利用する
 - 与えたシンボルのカラムを軸にソートできる
 - シンボルで:desc　を取ると降順にすることができる
 - また、.offsetメソッドで、最初のデータをいくつ飛ばすか指定することができる
 - limitを指定すると、何軒までを取得するかを定義することができる
```
User.order(:name)
User.order(name: :desc)
User.limit(20).order("updated_at DESC")
```


### 曖昧検索（部分一致）
- `where("カラム名 like '%検索テキスト%'")` を指定し、部分一致を定義することができる
- `%`の部分に任意の値が入っているデータも検索される
- `ん`で終わる文字列を検索、といった場合は、`カラム名 like '%ん'`となる

```ruby
User.where("name like ?", "%ん")
```

### もしいなければ新規作成
- find_or_create_byメソッドを利用すると、存在しなければ作成、という処理を行うことができる

```ruby
user= User.find_or_create_by(name: "drip") do |u|
end
```

### バリデーション
- 空の文字が登録されたり、というようなことを防ぐため、バリデーションを追加することができる
- また入力文字の最大長やフォーマットを制限することができる
- Modelクラスで、以下のように定義する
- `validates(検証するフィールド名, :length => 検証パラメータ)`

```
class User < ActiveRecord::Base
  # 必須条件の設定
  validates :name,:age, presence: true
```

> Railsドキュメント　validates
> http://railsdoc.com/references/validates


### 共通化
- 抽出条件を切り出す
 - スコープ化し、よく使うDB操作を共通化することができます
 - モデルに以下のように実装します

```ruby
scope :select_top, -> (num){ User.order(:name).limit(num) }
```
- scopeの次にシンボルでスコープ名を定義します。この場合は`select_top`
 - 使う側からは、`User.select_top 引数`のように呼び出せます
- ,以降は、（num）は仮引数です、次のブロックに定義された、`User.order(:name).limit(num)` を実行します。ブロック内は、通常のactiverecordによる検索処理の書き方と全く同じです。
- このようによく使う処理を、scopeに切り出してくることができます

### 可変where句　
- 複数の検索軸で and 検索 
 - スペース区切りの文字列を分解して、繰り返しながらwhere条件を追加していく、という方法が取れるようです。

```ruby
user = User.all
if {検索対象の文字列}.present?
    {検索対象の文字列}.split(" ").each do |n|
    user= User.where("name like :q", q: "%#{n}%")
  end 
end
```

> ActiveRecordで動的なAND検索する
> http://d.hatena.ne.jp/jiikko/touch/20130717/1374068054

### 1対多テーブル
- Userモデルが、複数のPostを持つ、という想定でリレーションを作ってみます。

- 設定
 - テーブルが、設定できている必要があります。小テーブルの方は、親テーブルの主キーを外部キーとして持ちます
 - 今回の例では`user_id integer,`が外部キーです

```sql
create table users (
  id integer primary key,
  name text,
  age integer,
  created_at,
  updated_at
);

create table posts (
  id integer primary key,
  user_id integer,
  messsage text,
  created_at,
  updated_at
);
```

- モデル上では以下のように定義します
 - 親テーブルは、has_menyで子テーブルを定義します
 - dependentでdestroyを指定すると、子テーブルの変更が外部テーブルにも反映されるようになります。子テーブルを削除すると、外部テーブルも一緒に削除されます
 - 子テーブル側は、`belongs_to`で紐付くテーブルを指定します

```ruby
class User < ActiveRecord::Base
  # 関連する小テーブルのデータも一緒に削除する
  has_many :posts, dependent: :destroy
end

class Posts < ActiveRecord::Base
  belongs_to :user
end
```

- マイグレーションファイル上では、以下のように定義します

```ruby
class CreateUser < ActiveRecord::Migration[5.0]
  def change
    create_table :user do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :posts do |t|
      t.belongs_to :user, index: true
      t.string :message
      t.timestamps
    end
  end
end
```

- 実際の使用
- 挿入
 - userに紐付くPostオブジェクトを追加する
 - オブジェクトの名前は、多側の方は複数形の名称となっているので注意する
 - Postオブジェクトを作る時に、外部キーを指定して紐づける場合

```ruby
user=User.find(1)
Post.create(user_id: 2, message: "testetst")
```
- また、userオブジェクトに紐付くpostsオブジェクトのcreateを実行することで追加を行う方法もある

```ruby
user.posts.create(message: "add text2")
```
- 上記は、以下のように書くこともできる。


```ruby
user.posts << Post.create(message: "add text <<")
```



- 検索
 - 親モデルにincludesメソッドを利用し、関連テーブルも込で検索することができる

```ruby
## userモデルと紐づくpostsを取得する
user = User.includes(:posts).find(1)
pp user
pp user.posts
```
- 親テーブルに紐付くデータをループして取得する場合、以下のように書く

```ruby
user.posts.each do |post|
  pp "#{user.name} : #{post.message}"
end
```
- 逆に、以下のように、子テーブル側から親テーブルのカラムを取得することもできる

```ruby
# 多側から取得する
post = Post.includes(:user).all
post.each do |p|
  # 多のデータを全てループ取得する
  # 親テーブルは、モデル名で呼び出すことができる
#  p "#{p.message} : #{p.user.name}"
end

# 親データが削除されると、modelにdependent destroy設定が定義されていれば、同時に小テーブルのデータも削除できる
User.find(1).destroy
```

- 引き続き、便利だと思った操作、よく使う操作を加筆していきます。

----


### その他
<details><summary>PostgreSQLの操作</summary><div>
## ローカルでpostgresqlサーバを準備
- 起動・停止・DB確認・ユーザ確認・ユーザー作成と権限付与のメモ

- DBの確認
 - PostgreSQLの起動
 
```
postgres -D /usr/local/var/postgres
```

- pg_ctlによる起動、停止は以下
 - 起動`$ pg_ctl -D /usr/local/pgsql/data -l /var/log/postgres start`
 - 停止`pg_ctl -D /usr/local/pgsql/data stop`

- `$psql -l` でテーブルを確認できる

- Databaseの準備

```
$ psql -q -c'select * from pg_user' postgres
 usename  | usesysid | usecreatedb | usesuper | userepl | usebypassrls |  passwd  | valuntil | useconfig
----------+----------+-------------+----------+---------+--------------+----------+----------+-----------
 katoyuki |       10 | t           | t        | t       | t            | ******** |          |
(1 row)

$ createuser sinatra_user
$ psql -q -c'select * from pg_user' postgres
   usename    | usesysid | usecreatedb | usesuper | userepl | usebypassrls |  passwd  | valuntil | useconfig
--------------+----------+-------------+----------+---------+--------------+----------+----------+-----------
  ...      |       10 | t           | t        | t       | t            | ******** |          |
 sinatra_user |    16384 | f           | f        | f       | f            | ******** |          |
(2 rows)

i$ createdb sindb -O sinatra_user
$ psql -l
                                    List of databases
   Name    |    Owner     | Encoding |   Collate   |    Ctype    |   Access privileges
-----------+--------------+----------+-------------+-------------+-----------------------
 postgres  | ...     | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 sindb     | sinatra_user | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 template0 |  ...      | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/ ...           +
           |              |          |             |             |  ... =CTc/ ... 
 template1 |  ...      | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/...          +
           |              |          |             |             |  ... =CTc/ ... 
(4 rows)

```

- アプリ用ユーザsinatra_userにSuperuser権限を与えます

```
$psql -U {オーナーのユーザー} sindb
psql (10.4)
Type "help" for help.

sindb=# ALTER ROLE sinatra_user WITH Superuser;
ALTER ROLE
sindb=# \du
                                     List of roles
  Role name   |                         Attributes                         | Member of
--------------+------------------------------------------------------------+-----------
 ...     | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
 sinatra_user | Superuser                                                  | {}
```

- アプリ用のsinatra_userに権限が付与されました

</div></details>

# 参考
> ActiveRecord入門
> https://qiita.com/gcfuji/items/80625a75959591c2b7cd
> Railsガイド
> https://railsguides.jp/active_record_basics.html
> Sinatra+ActiveRecord+MySQLで、簡単APIサーバ構築
> https://qiita.com/u1_fukui/items/88c10d4d530ec6fbaaa1
> ドットインストール
> https://dotinstall.com/lessons/basic_activerecord_v2
> ActiveRecord入門
> https://qiita.com/gcfuji/items/80625a75959591c2b7cd
> MacにPostgreSQLをインストール
> https://qiita.com/_daisuke/items/13996621cf51f835494b
> MacのRailsアプリでPostgreSQLを使う方法
> https://qiita.com/yh2020/items/8be3087004d100fe752b
> Herokuにデプロイできない非エンジニアです。
> https://qiita.com/Taak15/items/8a7325c302d0d1b019f5