こんにちは。
この記事はRailsを触り始めたので何かWebアプリを作ってみた軌跡を残してみたというものです。
手探りで進めたものなのでこのやり方だと効率が悪い、などあると思いますが同じく初心者のための助けとなれば幸いです。
環境構築手順はこちらに記載しています
現在の状況
- Vagrant + VirtualBoxでとりあえず仮想環境作ってみた
- Rails(5.1)インストール完了、画面も表示できた
作りたいもの
単純なCRUDアプリを作成してみるということで、タスク管理をするアプリを作成します。
ざっくり要件としては以下のようなものです。
- 一覧
- タスクの一覧を実施する期限とともに表示
- タスクを終えたら「実施済み」に変更できる
- 「削除」で未実施/実施済みに関わらず一覧から表示されなくなる(論理削除する)
- 作成
- 編集(更新)
- タスク名と実施期限のみ設定可能(実施済みへの変更は一覧からのみ行える)
- 削除
- 一覧画面より「削除」から実施できる
この記事のゴール
- Railsの環境にMySQLをインストールする
- migration機能を使ってみる
- DB接続してデータの一覧を表示する
1. MySQLをインストール
RailsのデフォルトのDBはSQLiteですが、今回はMySQLを使用することにします。
そのため構築したRailsの環境にMySQLをインストールします。
- 新規アプリケーション作成時にオプションでDBを指定する
$ rails new project --database=mysql
project
の部分はアプリケーション名を指定してください。
なお新規アプリケーション作成時と記載していますが、私の場合はDBを指定する前にすでにrails new
していたので
その時と同じアプリ名で上書きして作成し直しました。
その際は以下のように上書きの警告が途中出てくるかと思います。
$ rails new project --database=mysql
exist
identical README.md
identical Rakefile
identical config.ru
conflict .gitignore
Overwrite /***/.gitignore? (enter "h" for help) [Ynaqdh] y
force .gitignore
conflict Gemfile
Overwrite /***/Gemfile? (enter "h" for help) [Ynaqdh] y
force Gemfile
run git init from "."
Reinitialized existing Git repository in /***/.git/
exist app
identical app/assets/config/manifest.js
identical app/assets/javascripts/application.js
アプリケーションが作成されると、Gemfile
に以下のように記述が増え
gem 'mysql2', '>= 0.3.18', '< 0.5'
またconfig/database.yml
が以下のように記載されていると思います。
# MySQL. Versions 5.1.10 and up are supported.
#
# Install the MySQL driver
# gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
# gem 'mysql2'
#
# And be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.7/en/old-client.html
#
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:
host: localhost
development:
<<: *default
database: project_development
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: project_test
defaultのadapterがmysql2
になっています。
- MySQLをサーバにインストール
VagrantにSSH接続した状態で、以下を実行。
$ sudo rpm -ivh http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm
$ sudo yum update
$ sudo yum install mysql-community-server
続けて起動
// 注:CentOS7の場合
$ sudo systemctl start mysqld.service
// 自動起動設定も
$ sudo systemctl enable mysqld.service
MySQLにログインしてみます。
$ mysql -u root
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
エラー出力。
調べるとログファイルにパスワードが吐かれているため、それを使用して一時ログインするのが良さそう
$ cat /var/log/mysqld.log | grep 'password is generated'
2018-04-06T05:39:08.571786Z 1 [Note] A temporary password is generated for root@localhost: ********
var/log/mysqld.log
に出力されていたためそれを使用して再ログイン。
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 6
Server version: 5.7.21
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
成功。set password
でパスワードは新規に設定し直しておきます。
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.21 |
+-----------+
1 row in set (0.02 sec)
サーバにインストールされたバージョンは5.7。
ログインまで完了したため次の工程に進みます。
2. DB, table作成
- DB作成、マイグレーション実行
1でMySQLログイン時のパスワードを設定したため、Gemfile
のpassword
に記載します。
# And be sure to use new-style password hashing:
# http://dev.mysql.com/doc/refman/5.7/en/old-client.html
#
default: &default
adapter: mysql2
...
password: *******(こちらに記載)
...
次にGemfile
が配置されているディレクトリで以下のコマンドを実行します
$ rails db:create
$ rails db:migrate
上記、1行目ではDBを新規作成しています。
2行目のmigrate
とは何かというと、Railsのマイグレーション機能を使用するということを指します。
マイグレーションとは、マイグレーションスクリプトと呼ばれるスクリプトファイルを作成し実行することで、SQL文を直接実行することができるというものです。
この「スクリプトファイル」は決められたRailsnの記法で記述しますが、使用しているDBがMySQLであってもSQLiteであっても、同じ記述で対応が可能とのこと。
つまり今回はアプリケーション作成時からMySQLを指定して進めていますが、途中でSQLiteに切り替えることも、逆にSQLiteからMySQLへの切り替えも容易にできるというメリットがありますね。
- テーブル作成
以下、rails db
コマンドを実行することによってMySQLにログインし対話モードに入ります。
$ rails db
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
...
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
このコマンドではRailsが現在接続しているDBを適切に選び、対話式モードへ接続してくれます。
このままSQL構文を書いてもいいのですが、railsコマンドを駆使して進めていくことにします。
今回、以下のようなcreate文で作成されるテーブルを作成したいと思います。
CREATE TABLE `tasks` (
`id` bigint(20) NOT NULL auto_increment,
`task_name` varchar(255) NOT NULL,
`term_at` datetime NOT NULL,
`created_at` datetime NOT NULL default current_timestamp,
`updated_at` datetime NOT NULL default current_timestamp on update current_timestamp,
`disp_flag` tinyint(4) NOT NULL default '0',
`delete_flag` tinyint(4) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
rails generate
コマンドを実行します。
$ rails g model tasks
Running via Spring preloader in process 6639
[WARNING] The model name 'tasks' was recognized as a plural, using the singular 'task' instead. Override with --force-plural or setup custom inflection rules for this noun before running the generator.
invoke active_record
create db/migrate/20180406084039_create_tasks.rb
create app/models/task.rb
invoke test_unit
create test/models/task_test.rb
create test/fixtures/tasks.yml
「モデル名は単数形で指定しろ」という旨のWARNINGが出ており、Modelのファイル名はtaskで生成されています。なるほど。
これによりモデルを作成すると同時にモデルに対応するテーブルを作成するためのマイグレーションスクリプトが自動的に作成され、
db/migrate
下に以下のファイルが生成されています。
class CreateTasks < ActiveRecord::Migration[5.1]
def change
create_table :tasks do |t|
t.timestamps
end
end
end
ここにデフォルトで生成されているカラム以外の情報を記載します。
こちらの各制約などの記述方法はここでは割愛。
class CreateTasks < ActiveRecord::Migration[5.1]
def change
create_table :tasks do |t|
t.string :task_name, :null => false
t.datetime :term_at, :null => false
t.datetime :created_at, default: -> { 'NOW()' }
t.datetime :updated_at, default: -> { 'NOW()' }
t.boolean :disp_flag, default:false
t.boolean :delete_flag, default:false
end
end
end
試行錯誤しこのように落ち着きました。
さて再度migrate
コマンドを実行します。
$ rails db:migrate
== 20180406084039 CreateTasks: migrating ======================================
-- create_table(:tasks)
-> 0.0476s
== 20180406084039 CreateTasks: migrated (0.0477s) =============================
migrateは正常に実行されたため、
実際にテーブルがどのように作成されたか確認してみます。
$ rails db
mysql> show databases;
+---------------------+
| Database |
+---------------------+
| information_schema |
| mysql |
| performance_schema |
| project_development |
| project_test |
| sys |
+---------------------+
6 rows in set (0.00 sec)
mysql> use project_development;
Database changed
mysql> show tables;
+-------------------------------+
| Tables_in_project_development |
+-------------------------------+
| ar_internal_metadata |
| schema_migrations |
| tasks |
+-------------------------------+
3 rows in set (0.00 sec)
DBがproject_development
とproject_test
が作成されています。
これはdatabase.yml
中の
development:
<<: *default
database: project_development
こちらの記述に基づいてということになります。
続けて確認していくと、
mysql> desc tasks;
+-------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| task_name | varchar(255) | NO | | NULL | |
| term_at | datetime | NO | | NULL | |
| created_at | datetime | YES | | CURRENT_TIMESTAMP | |
| updated_at | datetime | YES | | CURRENT_TIMESTAMP | |
| disp_flag | tinyint(1) | YES | | 0 | |
| delete_flag | tinyint(1) | YES | | 0 | |
+-------------+--------------+------+-----+-------------------+----------------+
7 rows in set (0.01 sec)
mysql> show create table tasks\G
*************************** 1. row ***************************
Table: tasks
Create Table: CREATE TABLE `tasks` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`task_name` varchar(255) NOT NULL,
`term_at` datetime NOT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP,
`disp_flag` tinyint(1) DEFAULT '0',
`delete_flag` tinyint(1) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
show create tableで確認しても、上で記載したcreate文とほぼ同じ構成で作成されているのが確認できます。
( ゚∀゚ )キタ━━
★注意点
idカラムを勝手に追加してはいけない
idカラムは自動生成されるため、追加しちゃうと以下のようにエラーが出力されるので注意。
StandardError: An error has occurred, all later migrations canceled:
you can't redefine the primary key column 'id'. To define a custom primary key, pass { id: false } to create_table.
migrationの変更が反映されない
マイグレーションスクリプトを変更してrails db:migrate
を行なってもエラーや実行結果が出力されず、DBへの反映もされない場合があります。
これは一度実行したmigrationを書き換えても、すでに処理済みとみなされて、再度処理対象とはならないためです。
その場合は以下のようにrollback
後再度migrate
を実行するか、
$ rails db:rollback
== 20180406084039 CreateTasks: reverting ======================================
-- drop_table(:tasks)
-> 0.0125s
== 20180406084039 CreateTasks: reverted (0.0174s) =============================
$ rails db:migrate
== 20180406084039 CreateTasks: migrating ======================================
-- create_table(:tasks)
-> 0.0148s
== 20180406084039 CreateTasks: migrated (0.0148s) =============================
または変更/追加を行うカラムの分だけ別にマイグレーションを行うことで回避できます。
その方法は今回は割愛します。
3. レコード作成
一覧画面でデータを扱うための用意をしておきます。
上で作成したテーブルにinsertを行うための手段はymlファイルを使うとか色々ありますが、今回は Active Record を使用します。
$ rails console
上記のようにrails console
で実行していきます。
これはRailsの環境を読み込んだ状態でrubyコードを実行できるコンソールツールで、読み込んでいるgemも実行可能だそう。
コンソールから以下を実行します。
task = Task.new({task_name: '新札を用意', term_at: '2018-05-01 00:00:00'})
task.save
$ rails console
Running via Spring preloader in process 9232
Loading development environment (Rails 5.1.5)
irb(main):001:0>
irb(main):002:0> task = Task.new({task_name: '新札を用意', term_at: '2018-05-01 00:00:00'})
(0.5ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<Task id: nil, task_name: "新札を用意", term_at: "2018-05-01 00:00:00", created_at: nil, updated_at: nil, disp_flag: false, delete_flag: false>
irb(main):003:0> task.save
(0.6ms) BEGIN
SQL (15.7ms) INSERT INTO `tasks` (`task_name`, `term_at`, `created_at`, `updated_at`) VALUES ('新札を用意', '2018-05-01 00:00:00', '2018-04-09 06:36:57', '2018-04-09 06:36:57')
(3.4ms) COMMIT
=> true
irb(main):004:0>
commitまで完了していますね。
再度MySQLに入ってみるとレコードが追加されているのを確認できます。
4. 一覧画面を作成
上でmigrationを実行しているため、generate
コマンドにはcontroller
を指定して作成します
$ rails g controller Task list
Running via Spring preloader in process 9327
create app/controllers/task_controller.rb
route get 'task/list'
invoke erb
create app/views/task
create app/views/task/list.html.erb
invoke test_unit
create test/controllers/task_controller_test.rb
invoke helper
create app/helpers/task_helper.rb
invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/task.coffee
invoke scss
create app/assets/stylesheets/task.scss
Controller, Model, Viewを以下のように修正。
- Controller
class TaskController < ApplicationController
def list
@task = Task.all
end
end
- Model
class Task < ApplicationRecord
def disp_term_at
# 期限日+残り日数を設定
term_string = '期限日まであと' + ((Date.parse(term_at.to_s) - Date.today).to_i).to_s + '日'
Date.today > Date.parse(term_at.to_s) ? '期限切れ' : term_at.to_s + term_string
end
end
- View
<h1>タスクリスト</h1>
<table border="0">
<tr>
<th>ID</th>
<th>タスク名</th>
<th>期限</th>
<th></th>
<th></th>
</tr>
<% @task.each do |item| %>
<% if item.delete_flag == false %>
<tr>
<td><%= item.id %></td>
<td><%= item.task_name %></td>
<td><%= item.disp_term_at %></td>
<td>
<% if item.disp_flag == false %>
<%= link_to "完了!" %>
<% else %>
実施済み
<% end %>
</td>
<td>
<%= link_to "削除する" %>
</td>
</tr>
<% else %>
<% end %>
<% end %>
</table>
- initializers
またdatetime型のカラムをそのまま表示しようとすると
2018-04-10 23:00:00 UTC
このような形式で表示されるため(Railsのデフォルト)、日時フォーマットのためのイニシャライザを
config/initializers/time_formats.rb
に以下のように作成しました。
# 日時フォーマット定義
Time::DATE_FORMATS[:default] = '%Y/%m/%d %H:%M'
Time::DATE_FORMATS[:datetime] = '%Y/%m/%d %H:%M'
5. 作成した画面を確認
http://192.168.33.10:3000/task/list にアクセスしてみると以下のようなタスク一覧が表示されます。
※レコードはいくつか追加しています
見せ方が今一つ&まだ改良の余地はありますが一覧の作成が完了しました