目標
- RailsのModelの中核を担うActiveRecordのメソッドと発行されるSQLの関係を理解すること
- includesやpluckといったメソッドを理解することActiveRecord中級者レベルにステップアップすること
目次
- 用語
- 基本編
- 基本編解説
- 応用編
- 応用編解説
- まとめ
- さらに発展的な内容
用語
RDBMS
Relational Data Base Management Systemの略。その名の通りリレーショナルデータベースを管理するソフトウェアの総称で、データをカラム(列)と、レコード(行)の中にまとめ、それらを二次元のテーブル(表)の中に並べて表現するものです。データの取得や作成といった手続きにSQL言語が用いられます。代表的なものにはPostgresqlやMysqlなどがあります。
ORM
ORMとはオブジェクトリレーショナルマッピングの略で、Rubyなどのオブジェクト指向言語では直接扱うことのできないRDBMS上のテーブルやデータを、クラスなどにマッピングすることで、扱えるようにするプログラミングの技法のこと。
ActiveRecord
Railsで用いられているActiveRecordとは、MVCのMつまりModelの中核を担う階層で、主にデータベースとのやり取りを行います。これは、ORMシステムに記述されている「Active Recordパターン」を実装したライブラリであり、このパターンと同じ名前が付けられています。 Railsのモデルとして作成されたクラスにはApplicationRecordクラスがもれなく継承されていますが、これはActiveRecord::Baseを継承したものなので、Railsで生成される全てのクラスでこのActiveRecordのメソッドを使用することができるというわけです。
準備
以下に用いる例では、UserモデルとArticleモデルが出てきます。それぞれ以下のようなカラムを持っているものとします。
Userモデル
カラム | 型 |
---|---|
id | integer |
name | string |
age | integer |
Articleモデル
カラム | 型 |
---|---|
id | integer |
user_id | integer |
title | string |
body | string |
コンソールやRDBMSににアクセスして直接実行して確認したい方は、以下のリンクで解説と同じ環境を用意する手順を示しているので参考にしてみてください。
1.ActiveRecordのメソッドと発行されるSQL文(基礎編)
ActiveRecordのメソッドとそれに対応してデータ取得のために発行されるSQLの様子は以下のようになります。(SQL文はrails console上に出てくるものと少し異なりますが、実際のRDBMSで実行できる形で示しています。)
# ①
User.all
# SELECT * FROM users;
# ②
User.find(1)
# SELECT * FROM users WHERE id = 1 LIMIT 1;
# ③
User.find_by(name: "太郎")
# SELECT * FROM users WHERE name = '太郎' LIMIT 1;
# ④
User.create(name: 'John', age: 20)
# INSERT INTO users (name, age) values ('John', 20);
# ⑤
User.first.update(name: "Mathias")
# SELECT * FROM users ORDER BY id ASC LIMIT 1;
# UPDATE users SET name = 'Mathias' updated_at = NOW() WHERE id = 1;
基礎編解説
①all
SELECT * FROM users;
コントローラーのindexでよく実行されるメソッドです。ご存知の通り、allは、全てのユーザーを取得するというActiveRecordのメソッドです。*
はワイルドーカードで、全て(=ここではusersテーブルに定義された全てのカラム)という意味です。つまり、このSQLは「usersテーブルに定義されている全てのレコードを取得する」という意味になります。取得されたデータはUserクラス(の配列)にマッピングされ、繰り返し処理や、カラムの値を表示する際には、Rubyのメソッドとして実行できるようになるという仕組みが提供されます。
②find
SELECT * FROM users WHERE id = 1 LIMIT 1;
findメソッドを実行すると途中まではallと一緒で最後に WHERE id = 1 LIMIT 1
というのがつきます。このSQLは「usersテーブルからidが1のuserの一つ目のデータの全てのカラムを取得する」という意味です。all
に取得する条件と、取得数の指定がついたものです。
③find_by
SELECT * FROM users WHERE name = '太郎' LIMIT 1;
idで条件指定をしてユーザーを取得する際には、単純に1を指定すればよしなにやってくれましたが、それ以外のカラムで条件指定をしたい場合にはfind_byメソッドを用いてカラムと条件の指定を同時にします。
しかし、発行されたSQLを見るとWHERE id = 1
の部分がWHERE name = '太郎'
に変わっただけですね。ActiveRecordに限らず、このようにid検索の場合には簡略化されたメソッドを提供しているORMシステムは多く存在します。
④create
INSERT INTO users (name, age) values ('John', 20);
カラムと挿入したい値を指定し、このように書きます。sqlではデータを列に挿入するという感覚なので、insertという句が用いられているのだ思います。また、''
と""
の使い分けは厳密で、文字列を使用する場合には''
でくくる必要があります。
⑤update
SELECT * FROM users ORDER BY id ASC LIMIT 1;
UPDATE users SET name = 'Mathias' updated_at = NOW() WHERE id = 1;
updateとではまず、更新すべきデータの列を取得して、それを書き換えるという動作を行うため、二つのSQL文が発行されます。ここで用いられているfirstメソッドは、データベース上でidがもっとも小さいもののデータを取得するメソッドですが、SQLではその条件を「データをidの昇順に並べてその最初のデータを取ってくる」という指定の仕方をします。また、udpateのやり方は見ての通りですが、insertとは少しカラムと値の指定の仕方が異なるのです。
2.ActiveRecordのメソッドと発行されるSQL(応用編)
# ①
Article.pluck(:title)
# SELECT title FROM articles;
# ②
User.find(1).articles
# SELECT * FROM users WHERE id = 1 LIMIT 1;
# SELECT * FROM articles WHERE user_id = 1;
# ③
Article.all.includes(:user)
# SELECT * FROM articles;
# SELECT * FROM users WHERE id in (1,2,3...);
応用編解説
①pluck
今までは、*
で指定した列の全てのデータを取得していましたが、pluck
メソッドを使うことによってカラムを指定してデータを取得することが可能になります。取得するデータの規模が大きくなるほど実行速度は遅くなるので、適宜このようなメソッドを使用することがアプリケーションの性能改善に役立ちます。
②子モデルのAssociation
なんのことはない、ユーザーを見つけてそのユーザーのidを持ったarticleを取得しているだけです。しかし、それをrubyで簡単にかけるようなインターフェースを実装してくれているので、我々は非常に便利に使うことができるというわけです。
③includes
記事を全て取得してその記事のuser_idが含まれるユーザーを取得しているだけで、アソシエーションによる関連モデルの取得とどう違うのかと思われるかもしれません。しかし、この場合はあくまで@articles
にはarticleモデルの配列が入っています。これは例えばhtmlで
<% @articles.each do |a| %>
<%= a.title %>
<%= a.user.name %>
<% end %>
のように表示をしたい場合に必要となります。このテンプレートを表示するには、コントローラで@articles = Article.all
と指定すれば問題なく表示されるのですが、実際には3行目のa.user.name
が呼ばれるたびに、裏でSELECT * FROM users WHERE id = (a.user_id)
というSQLが毎回発行されてしまい動作が非常に重たくなってしまうのです。
これは俗にN+1問題と言われ、articlesを取得(1回)+articlesの数(=N回)分userを取得するSQLが発行されるため、そのように呼ばれています。それを解決するメソッドがincludes
で、あらかじめ必要となるテーブルのデータを取得しておき、SQLの発行を2回で済ませます。
まとめ
ActiveRecordやデータベース、そしてその関係について説明しました。Railsでの開発はそもそもアプリケーションとデータベースが別れているということすら意識せずに使用できるため便利な反面、ちょっとしたエラーが起きるととたんに理解の浅さが浮き彫りになってしまうことがあります。これを機会にぜひ勉強してみてください。
発展的な内容
- join, preload, eage_loadなどのさらに発展的な内容も理解したい方向け
- データベースや設計を学びたい方向け