Skip to content

Instantly share code, notes, and snippets.

@hakatashi hakatashi/sig-web-05.md
Last active Jun 22, 2016

Embed
What would you like to do?

TSG 第5回Web分科会 カンペ

データベースを介したRailsアプリケーションを制作し、ActiveRecordとO/Rマッパーの基礎を学ぶ。

事前準備

ActiveRecordとは

前回RailsアプリケーションはMVCと呼ばれるデザインパターンで設計される問ことを解説したが、Modelに関しては深い部分に触れなかった。

復讐すると、Modelとはアプリケーションが扱う「モノ」や「コト」と、それに対する操作を表現したもの。典型的な設計ではデータベースのテーブルと同じ数だけ存在するものである。

今回はより単純に、データベース上のテーブルという形で表現された「モノ」「コト」に一対一で関連付けられ、テーブルをくるむように実装されるクラスであると考えることにする。

RailsではこのModelの部分を、O/Rマッパーの一種である ActiveRecord と呼ばれるライブラリを用いて表現する。

O/Rマッパーとは

前々回取り扱ったように、MySQLをはじめとするSQL型のデータベースでは、SQL文と呼ばれる言語によってデータベースを操作し、値を取り出したり計算したりする。

このような手法は取り出したい情報に応じた柔軟なインターフェイスを提供するが、最適化が進むとビジネスロジックの部分を過度にSQL部で持つようになり、アプリケーション側のコードと噛み合うようになる。

また、プログラムによって文字列から言語を組み立てそれによってデータベースにアクセスするという手法は、SQLインジェクションを始めとした脆弱性につながりやすいと主張されることもある。

このような問題に対して、SQL文などのデータベースとの生のインターフェイスを隠蔽し、オブジェクト指向的なインターフェイスによってデータベースにアクセスできるようにするのが O/Rマッパー (Object/Relation Mapper) である。

なお、データベースにアクセスするためにO/Rマッパーを使用するのは邪道であり、生のSQL文を書くべきだとする勢力も多く、O/Rマッパー派とは20年来の対立が今なお続いていることは特筆に値する。

実際に使ってみる

前回作成したRailsアプリケーションは放っておいて(何なら削除して構わない)、新しいRailsアプリケーションを作成する。適当なフォルダに行き、

rails new memo -d mysql

とするとmemoフォルダが作成され、MySQLを使用する設定で新しいRailsアプリケーションを作成することができる。XAMPPなどでインストールした場合はrootのパスワードなどは設定されていないはずなので、デフォルトの設定で開発を始めることができる。

まずはこのアプリケーションで使用するテーブルを格納するためのデータベースを作成する。このような場合もRailsではデーターベースを直接触る必要はなく、Rakeと呼ばれるMakeのパチモンみたいな(失礼)コマンドからデータベースを操作することができるようになっている。

次のコマンドを実行する。

cd memo
rake db:create

何もエラーが表示されなければ成功。

MySQLコマンドでサーバーにアクセスして、データベースが作成されたか確認してみる。

mysql -u root

などと打ち込むとSQL文を打ち込めるようになる。

ここで、

SHOW DATABASES;

と打つと

+--------------------+
| Database           |
+--------------------+
| information_schema |
| memo_development   |
| memo_test          |
| mysql              |
| performance_schema |
+--------------------+

みたいな結果が返ってくるはず。memo_developmentとmemo_testが今回作成されたデータベースである。

一回mysqlコマンドを終了し、(exitと打つと終了する)今度はTextというModelを作成する。

rails generate scaffold Text title:text body:text user_id:integer

Modelの定義ファイルやテストやルーティングなど大量のファイルが作成される。Railsではこの時点ではデータベースにテーブルは作成されず、migrationと呼ばれるデータベースのスキーマの変更の歴史に追記されるだけである。実際にテーブルを作成するには、

rake db:migrate

と打ち、migrationに書いてある記述を実際のデータベースに反映させないといけない。

== 20160622072735 CreateTexts: migrating ======================================
-- create_table(:texts)
   -> 0.0308s
== 20160622072735 CreateTexts: migrated (0.0317s) =============================

みたいな表示が出れば成功である。

MySQL上でmemo_developmentデータベースの中を見てみるとtextsテーブルが作成されているのが分かる。(コマンド省略)

まずは何も考えずにこのテーブルに適当にデータを突っ込んでみる。db/seeds.rbというファイルを開き、

100.times do |i|
  Text.create title: "#{i.ordinalize} Text", body: "これは#{i}番目のメモです。", user_id: i % 3 + 1
end

と記述する。この状態で、

rake db:seed

を実行すると、初期データとしてこのデータが投入される。

MySQLコマンドから投入されたデータを見てみる。

$ mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 36
Server version: 5.7.11 MySQL Community Server (GPL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT * from memo_development.texts;
+-----+-----------+--------------------------+---------+---------------------+---------------------+
|   1 | 0th Text  | これは0番目のメモです。  |       1 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   2 | 1st Text  | これは1番目のメモです。  |       2 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   3 | 2nd Text  | これは2番目のメモです。  |       3 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   4 | 3rd Text  | これは3番目のメモです。  |       1 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   5 | 4th Text  | これは4番目のメモです。  |       2 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   6 | 5th Text  | これは5番目のメモです。  |       3 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   7 | 6th Text  | これは6番目のメモです。  |       1 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   8 | 7th Text  | これは7番目のメモです。  |       2 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|   9 | 8th Text  | これは8番目のメモです。  |       3 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|  10 | 9th Text  | これは9番目のメモです。  |       1 | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
(省略)
|  99 | 98th Text | これは98番目のメモです。 |       3 | 2016-06-22 08:05:36 | 2016-06-22 08:05:36 |
| 100 | 99th Text | これは99番目のメモです。 |       1 | 2016-06-22 08:05:36 | 2016-06-22 08:05:36 |
+-----+-----------+--------------------------+---------+---------------------+---------------------+
100 rows in set (0.01 sec)

データベースの準備ができたので、ようやくRailsアプリケーションを立ち上げてみる。

rails server

でサーバーを立ち上げ、 http://localhost:3000/texts/ にアクセスする。

すると、突っ込んだTextのデータが表示されるはずである。Show や Edit や New Text でデータをいじれることを確認したら、このページを生成しているコードを確認する。

app/controllers/texts_controller.rbを開くと、

# GET /texts
# GET /texts.json
def index
  @texts = Text.all
end

と書いてある部分がある。これが今回のコードである。

Textは先ほど作成したTextというモデルであり、同時にActiveRecordで定義されたクラスでもある。

このコードは要するにデータベースから全てのTextを取得してtextsというプロパティに格納しているのだが、見て分かる通りSQL文はどこにも書かれていない。このようにSQL文ではなくクラスのメソッドを呼び出すことによってデータを取得できるのがO/Rマッパーの特徴である。

実際にデータベースに対して発行されているSQL文を確認するには、先ほどサーバーを実行したコンソールを確認し、出力ログを見てみる。すると、

Started GET "/texts/" for ::1 at 2016-06-22 17:25:58 +0900
Processing by TextsController#index as HTML
  Text Load (1.0ms)  SELECT `texts`.* FROM `texts`
  Rendered texts/index.html.erb within layouts/application (42.0ms)
Completed 200 OK in 255ms (Views: 243.6ms | ActiveRecord: 2.0ms)

などと書かれているはずである。このSELECT ~が実際に発行されるSQL文である。

もう少し応用してみよう。このTextを全て取得するコードを書き換えて、

def index
  @texts = Text.where(id: 20..50).order(:id).reverse_order.offset(10).limit(20)
end

などとしてみる。これで先程のText一覧ページを読み込んでみると、39番目から20番目のメモが順番に表示され、

SELECT  `texts`.* FROM `texts` WHERE (`texts`.`id` BETWEEN 20 AND 50)  ORDER BY `texts`.`id` DESC LIMIT 20 OFFSET 10

みたいなSQL文が走るのがわかるはずである。

このように、O/Rマッパーはデータベースの操作を抽象化し、クラスのメソッドを介してデータベースにアクセスできるようにする。

Userモデルを作る

同じようにUserモデルを作ってみよう。一度サーバーを止めて、

rails generate scaffold User name:text

と打ち込む。そして再びdb/seed.rbを編集し、

User.create name: 'hakatashi'
User.create name: 'satos'
User.create name: 'TSG'

100.times do |i|
  Text.create title: "#{i.ordinalize} Text", body: "これは#{i}番目のメモです。", user_id: i % 3 + 1
end

としてみる。(名前は適当でよい)

色々いじったので一度データベースをリセットして再び初期データを入れなおす。

rake db:drop db:create db:migrate db:seed

MySQLでusersテーブルを確認してみると

mysql> SELECT * from memo_development.users;
+----+-----------+---------------------+---------------------+
| id | name      | created_at          | updated_at          |
+----+-----------+---------------------+---------------------+
|  1 | hakatashi | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|  2 | satos     | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
|  3 | TSG       | 2016-06-22 08:43:56 | 2016-06-22 08:43:56 |
+----+-----------+---------------------+---------------------+
3 rows in set (0.00 sec)

みたいになっているはずである。

Railsサーバーを立ち上げると、http://localhost:3000/users/からUserの内容を確認することができるようになっているはずである。

ここで、モデル同士の関連付けをしてみる。MySQLなど多数のデータベースは関連データベースなので、テーブル同士を互いに関連付けることができる。モデル同士の関連付けはこれを抽象化したもので、モデルの定義に特殊なメソッドを記述することで関連付けを定義することができる。

app/models/text.rbを開き、

class Text < ActiveRecord::Base
  belongs_to :user
end

とする。さらにapp/models/user.rbを開き、

class User < ActiveRecord::Base
  has_many :texts
end

とする。こうすることで、Textクラスにuserメソッドが定義され、user_idが指定するユーザーのモデルを参照することができる。

課題

ここまでで作成したモデルを用いて、サイトのトップページにメモ管理機能を実装しよう。

まずはTextコントローラーにrootというメソッドを定義し、トップページからここを参照するようにする。

app/controllers/texts_controller.rbを開いて、

def root
  @texts = Text.all
end

というメソッドを追加する。そしてconfig/routes.rbの2行目に

root 'texts#root'

を追加する。

最後にこのファイルをダウンロードし、app/views/text/root.html.erbというファイルで保存する。

サーバーを立ち上げてhttp://localhost:3000/を開くとTSG Memo Storageなるサイトが表示されるはずである。

実装する機能

いろいろ考えられるが、まずは以下の機能を順番に実装してみよう。

  • トップページに表示するメモをidが大きい順に10個に制限する
  • メモを書いたユーザーの名前を表示する
  • ユーザー名をクリックしたらユーザー情報編集ページに飛べるようにする

余力があったら

  • ページングを実装する
    • GETパラメータにpage=1みたいなのを追加するのが楽
  • メモ投稿機能を付ける
    • ユーザー認証とか実装している時間はないので、リストされているユーザーから1つ選んでその名前で投稿できるようにするとかでOK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.