目的
sinatraからのDB利用時、最初によく使いそうなActiverecordの基本的な操作と知識をまとめる
詳細は公式ドキュメントで把握した方が早い
前提
- 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を追加
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 テーブル名;
を ファイルの頭に設定して置いてください
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などを出力してくれます
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、age3つの項目を持っている場合を想定し例示します
class User < ActiveRecord::Base
end����������������������������������������������������������������������������������������
データの追加
- 以下のようにcreateメソッドで、カラム名をシンボルで指定して作成できる
User.create(name: "testuser", age: 22)
- オブジェクトを作り、値を代入する方法も可能
- この場合は、saveメソッドを使い永続化する必要がある
user = User.new
user.name = 'testuser'
user.age = 27
user.save
データの表示
User.find_all
- で全件の取得が可能
- select句のように、取得する項目を選択する場合は、selectメソッドを先に使う
User.select("id, name").find_all
- find(数値) で検索をすると、自動的に主キーで検索が行われる
- 以下のようにfindメソッドは、項目を取ることもできる
User.find(3)
User.find_by_id(3)
User.find_by(name: "testuser")
User.find_by name: "testuser"
User.find_by_name("testuser")
- firstメソッド、lastメソッドで、最初のデータや最後のデータから取得することができる
- 引数に値を持つことができ、その場合「何番目」のデータであるかを指定できる
User.first
User.last
User.first(3)
- 存在しない場合
- find_byメソッドに結果がない場合はnilが返される
- find_byメソッドに
!
マークを付与した場合、検索なしはエラーとしてエラーが返却される
User.find_by_id 100
User.find_by_id! 100
- where句による検索
- カラム名のシンボルに値を渡すことで、絞り込みができる
- 複数の値を与えるばあいは、配列を渡すことができる
[21,22,23]
- 範囲を与えることもできる
21..23
User.where(age: 21)
- AND条件
- where条件は、複数組み合わせることで、AND条件として絞り込むことができる
- 以上は以下のように
"age <= 22"
定義することができる
User.where("age <= 22").where(name: "testuser")
- OR条件で検索
- whereメソッドは、orメソッドでor条件を追加することができる
- この時、orメソッドの引数には、モデル.where、の構文が必要になる
User.where(name: "ni").or(User.where(age: 22)).select("id, name")
- 以外(NOT)を検索する
- whereメソッドは、notメソッドで、除外するデータを指定できる
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インジェクションを防ぐため、プレースホルダを利用するべき
- また、プレースホルダは、シンボルで定義することも可能
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メソッドの場合は、関連テーブルも含めて削除を行うことができる(ただしモデルに事前に定義が必要)
- 引数には、条件を文字列で引数に取ることもできる
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 '%ん'
となる
User.where("name like ?", "%ん")
もしいなければ新規作成
- find_or_create_byメソッドを利用すると、存在しなければ作成、という処理を行うことができる
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操作を共通化することができます
- モデルに以下のように実装します
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条件を追加していく、という方法が取れるようです。
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,
が外部キーです
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
で紐付くテーブルを指定します
class User < ActiveRecord::Base
# 関連する小テーブルのデータも一緒に削除する
has_many :posts, dependent: :destroy
end
class Posts < ActiveRecord::Base
belongs_to :user
end
- マイグレーションファイル上では、以下のように定義します
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オブジェクトを作る時に、外部キーを指定して紐づける場合
user=User.find(1)
Post.create(user_id: 2, message: "testetst")
- また、userオブジェクトに紐付くpostsオブジェクトのcreateを実行することで追加を行う方法もある
user.posts.create(message: "add text2")
- 上記は、以下のように書くこともできる。
user.posts << Post.create(message: "add text <<")
- 検索
- 親モデルにincludesメソッドを利用し、関連テーブルも込で検索することができる
## userモデルと紐づくpostsを取得する
user = User.includes(:posts).find(1)
pp user
pp user.posts
- 親テーブルに紐付くデータをループして取得する場合、以下のように書く
user.posts.each do |post|
pp "#{user.name} : #{post.message}"
end
- 逆に、以下のように、子テーブル側から親テーブルのカラムを取得することもできる
# 多側から取得する
post = Post.includes(:user).all
post.each do |p|
# 多のデータを全てループ取得する
# 親テーブルは、モデル名で呼び出すことができる
# p "#{p.message} : #{p.user.name}"
end
# 親データが削除されると、modelにdependent destroy設定が定義されていれば、同時に小テーブルのデータも削除できる
User.find(1).destroy
- 引き続き、便利だと思った操作、よく使う操作を加筆していきます。
その他
PostgreSQLの操作
- 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に権限が付与されました
参考
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