概要
Railsの復習に伴って、ActiveRecordの最低限の知識を整理しました。
ActiveRecordとは
- Ruby on Rails
- ORマッパーにActiveRecordを採用している。
- ActiveRecordはRuby on Railsが開発している。
- OR Mapper (Object <> RDB)
- SQLを意識せずにデータベースを扱う事が可能。
- DBエンジンは原則なんでもOK。
- Railsを利用せずとも、単体でも利用可能
命名規則
- 非常に厳格
- テーブル名は「小文字 + 複数形」
- created_atとupdated_atで作成日時の更新日時を自動保存できる。
ActiveRecordの利用の流れ
- ① DBにテーブルを作成
- ② Ruby上でactive_recordをrequire
- ③ Timezoneを日本に設定
- ④ ActiveRecord::BaseにDB接続情報を記載
- ⑤ テーブル名に基づくクラスを作成
- ⑥ 上記のクラスからインスタンスを作成
- インスタンスが1レコードになる
ActiveRecordの基本文法
INSERT(CREATE)
- インスタンス化
- レコードを作成準備
- .save
- レコードの保存
- .create
- レコードの作成と保存を同時に行う
insert.rb
require 'active_record'
require 'pp'
require 'logger'
# Timezoneの設定
Time.zone_default = Time.find_zone! 'Tokyo'
ActiveRecord::Base.default_timezone = :local
# ロガーの設定
ActiveRecord::Base.logger = Logger.new(STDOUT)
# DB情報
ActiveRecord::Base.establish_connection(
"adapter" => "sqlite3",
"database" => "./myapp.db"
)
# Modelを定義
class User < ActiveRecord::Base
end
# Modelのインスタンスが1レコードとなる
user = User.new(
name: 'fujimoto',
age: 27,
)
user.save
user = User.new
user.name = 'fujimoto'
user.age = 27
user.save
User.create(
name: 'fujimoto',
age: 27,
)
SELECT(READ)
特徴
- 検索条件に引っかからなければnilが返される。
- エラーで返したい場合は、メソッドに!をつける。
メソッド
- .all
- 全件取得
- .select(‘, , …’).all
- カラムを絞って取得
- .first
- 最初のレコードを取得
- 引数を渡すと「最初の○件」の様に取得も可能
- .last
- 最後のレコードを取得
- 引数を渡すと「最後の○件」の様に取得も可能
select1.rb
# 全件取得
pp User.all
# カラムを絞って全件取得
pp User.select('id, name, age').all
# 最初の3件を取得
pp User.select('id, name, age').first(3)
# 最後の2件を取得
pp User.select('id, name, age').last(2)
- .find()
- idで条件取得
- データの取得は1件のみ
- .find_by(: )
- カラム名で検索
- .find_by_()も可
- データの取得は1件のみ
- .where(: )
- 返り値は必ず配列
- .where(‘<条件>’)
- SQLの条件ぽく条件を書くことも可能
- .where(‘<条件>’).where(‘<条件>’)
- AND検索
- .where(‘<条件>’).or(.where(‘<条件>’))
- OR検索
- .where.not(‘条件’)
- NOT検索
select2.rb
# idで条件を指定して検索
pp User.select('id, name, age').find(2)
# 特定のカラムで条件を指定して検索(3パターン)
pp User.select('id, name, age').find_by(name: 'maeda')
pp User.select('id, name, age').find_by name: 'horio'
pp User.select('id, name, age').find_by_name! 'urano'
# AND検索
pp User.select('id, name, age')
.where('age >= 25')
.where('age < 30')
# OR検索
pp User.where(age: 27)
.or(User.where(age: 33))
.select('id, name, age')
# NOT検索
pp User.where.not(id: 3).select('id, name, age')
DELETE(DELETE)
- .destroy
- レコードを削除する。
- 低速
- Validationなどの追加機能が考慮される
- UPDATEのupdate
- .delete
- 単純にレコードを削除する。
- 高速
- Validationなどの追加機能は考慮されない
- UPDATEのupdate_all
- .delete_all
- deleteの全件削除バージョン
destroy.rb
# destroyでidを指定して削除
User
.destroy(3)
# 条件を指定して、条件に合致するレコードを全件削除
User
.where('name like ?', '%a%')
.delete_all
users = User.select('id, name, age').all
pp users
UPDATE(UPDATE)
- .update(, )
- idを指定してカラムを更新する
- id以外の条件でレコードを指定する場合はwhereを利用する
- ValidationなどActiveRecordの追加機能を利用できるが低速。
- .update_all(‘更新内容’)
- SQLのUPDATEっぽくupdateできる。
- ValidationなどActiveRecordの追加機能は利用できないが、高速。
update.rb
# idを指定してレコードを更新
User.update(4, age: 26)
# 条件を指定してレコードを更新
User.where('age = 26').update(age: 27)
# SQLのUPDATEっぽくレコードを更新
User.update_all('age = 26')
users = User.select('id, name, age').all
pp users
Logger
- Rubyの標準ライブラリ
- requireして利用する
- ActiveRecord::Base.loggerに出力場所を代入するとActiveRecordで発行されたSQLを確認する事ができる。
プレースホルダー
- 変数に値を保持して、条件に当てる場合、直接変数を展開してはいけない。
- 悪意のあるコードが代入される可能性がある為。
- 条件に変数を利用したい場合は、プレースホルダーを利用する。
placeholder.rb
min = 25
max = 35
age = {
min: min,
max: max,
}
# プレースホルダーに?を利用した場合
pp User.select('id, name, age')
.where('age >= ? and age < ?', min, max)
# プレースホルダーにシンボルを利用した場合
pp User.select('id, name, age')
.where('age >= :min and age < :max', age)
# like検索でもプレースホルダーを利用する
pp User.select('id, name, age')
.where('name like ?', '%f%')
並び順
- order
- カラムを元に並び替えを行う。
order.rb
# 単純に年齢で並び替える
users = User.select('id, name, age')
.order(:age)
# 逆順で年齢で並び替える
users = User.select('id, name, age')
.order(age: :desc)
# 最初の1件を飛ばして、3件若い順に年齢を取得する
users = User.select('id, name, age')
.order(:age)
.limit(3)
.offset(1)
pp users
- limit
- 取得数を制限して取得する。
- offset
- 何番目から取得するかを指定して取得する。
条件の登録
- 条件の登録はActiveRecord::Baseクラスを継承したModel内にクラスメソッドを定義するか、scopeを定義する事で実現する。
limit.rb
# 条件の登録
class User < ActiveRecord::Base
# クラスメソッドVer.
def self.top(num)
select('id, name, age')
.order(:age)
.limit(num)
end
# scopeVer.
scope :top, -> (num) {
select('id, name, age')
.order(:age)
.limit(num)
}
end
pp User.top(1)
レコードが見つからない場合のみ挿入
- メールアドレスを検索してなければ登録的な処理で使える。
- find_or_create_by
- 条件検索に一致しない場合のみ登録し、条件に合致する内容があれば取得する。
- 条件に一致する内容がない場合に、他のカラムも埋めたい場合は、ブロックを利用する。
find_or_create_by.rb
# find_or_create_by
## 条件に一致する内容がない場合は、ブロックも実行する。
user = User.select('id, name, age')
.find_or_create_by(name: 'nu') do |u|
u.age = 33
end
pp user
Validation
- レコード挿入前にデータのチェックを行う事ができる
- nameとageが必須で、nameは3文字以上のバリデーション
validation.rb
# nameとageがnilのデータを挿入
user = User.new(
name: nil,
age: nil,
)
# データの保存が成功すればok、失敗すればng\\\
if user.save
p 'ok'
else
pp user.errors.messages
end
# データの保存が失敗すればエラーを出す
# user.save!
users = User.select('id, name, age').all
pp users
Callback
- 特定の処理が行われる前後に自動的に処理を入れる。
- 各種CRUD系にbefore_, after_を付与する。
- モデル内に記載する。
callback.rb
class User < ActiveRecord::Base
before_destroy :print_before_msg
after_destroy :print_after_msg
protected
def print_before_msg
puts "#{self.name} will be deleted"
end
def print_after_msg
puts "#{self.name} deleted"
end
end
User.where('age >= 20').destroy_all
pp User.select('id, name, age').all
Association
- テーブル同士を関連付けて、簡単にデータ抽出できる。
association.rb
require 'active_record'
require 'pp'
require 'logger'
Time.zone_default = Time.find_zone! 'Tokyo'
ActiveRecord::Base.default_timezone = :local
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(
"adapter" => "sqlite3",
"database" => "./myapp.db"
)
class User < ActiveRecord::Base
# 複数のcommentsレコードを持つ(複数系)
has_many :comments, dependent: :destroy
end
class Comment < ActiveRecord::Base
# 1つのuserに所属する(単数系)
belongs_to :user
end
User.delete_all
User.create(name: 'user1', age: 27)
User.create(name: 'user2', age: 27)
User.create(name: 'user3', age: 33)
User.create(name: 'user4', age: 20)
Comment.delete_all
Comment.create(body: 'comment1', user_id: 1)
Comment.create(body: 'comment2', user_id: 2)
Comment.create(body: 'comment3', user_id: 1)
# includes(:symbol)でAssociationデータも引き取れる
# Modelでhas_manyで定義した名称
user = User.includes(:comments).find(1)
pp user.comments.each do |c|
puts "#{user.name}: #{c.body}"
end
# includes(:symbol)でAssociationデータも引き取れる
# Modelでbelogs_toで定義した名称
comments = Comment.includes(:user).all
comments.each do |c|
# c.user.nameでAssociationのUserデータを取得できる
puts "#{c.body} by #{c.user.name}"
end
# IDが1のUserを削除(Commentを削除)
User.find(1).destroy
import.sql
drop table if exists users;
create table users (
id integer primary key,
name text,
age integer,
created_at,
updated_at
);
drop table if exists comments;
create table comments (
id integer primary key,
user_id integer,
body text,
created_at,
updated_at
);
最後に
ActiveRecordは自分の中ではかなり分かりやすいO/Rマッパーだと思います。
(柔軟だし、個人的には命名規則がしっかりしている方が好き)
Railsとの相性も抜群なので、理解を深めて柔軟に使いこなしていきたいと思います。