本記事の内容
モデルの作成
モデルの作成はコマンドから。
項目(カラム)の定義に必要な型の一覧は下記の通り。
指定できる型 | 概要 | 値の範囲 |
---|---|---|
primary_key | 主キー制約 | – |
string | 文字列型 | 255 |
text | 文字列型 | 65,535(?) |
integer | 整数型 | -2147483648 ~ 2147483647 |
float | 小数点型 | 8 bytes |
decimal | 浮動小数点型 | 8 bytes(?) |
datetime | デートタイム型(?) | – |
timestamp | タイムスタンプ型 | – |
time | 時刻型 | – |
date | 日付型 | – |
binary | バイナリ型 | – |
boolean | 真偽型 | true or false |
ちょっとまだわかっていない部分があるので、その辺は実装の中で判明した時に追記します。
今回作成するモデルに必要な項目をまとめます。(後ほど変わります)
項目名 | カラム名 | データ型 |
---|---|---|
作成日時 | created_time | time |
期限日時 | limit_time | time |
メモ(内容) | memo | string |
優先度 | priority | integer |
日時の項目に関しては、以下の記事を参考にTime型を選びました。(後ほど変わります)
それに合わせてカラム名も「○○_time」にしています。(Dateだったら_date)
モデル作成用コマンドは下記のとおりです。
$ rails g model ToDo \
created_time:time \
limit_time:time \
memo:string \
priority:integer
実行後ログは以下の通り。
invoke active_record
create db/migrate/20241209045344_create_to_dos.rb
create app/models/to_do.rb
invoke test_unit
create test/models/to_do_test.rb
create test/fixtures/to_dos.yml
モデルファイルの中身は以下の通り。
class ToDo < ApplicationRecord
end
項目が追加されるものだと思っていたんですが、どうやら違うようです。
次項で使用するマイグレーションファイルの中身は以下の通り。
class CreateToDos < ActiveRecord::Migration[8.0]
def change
create_table :to_dos do |t|
t.time :created_time
t.time :limit_time
t.string :memo
t.integer :priority
t.timestamps
end
end
end
想定通りのDDLらしきものが出来上がりました。
最後のタイムスタンプは何なんだろう…。
テーブルの作成
テーブルの作成には、前項で作成したマイグレーションファイルを使用します。
マイグレーションを実行する前に、DBの状態を確認しておきます。
$ rails db
SQLite version 3.47.1 2024-11-25 12:07:48
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata schema_migrations
テーブル一覧に、ToDoテーブルはありませんね。
それではマイグレーションの実行です。
$ rails db:migrate
== 20241209045344 CreateToDos: migrating ======================================
-- create_table(:to_dos)
-> 0.0047s
== 20241209045344 CreateToDos: migrated (0.0049s) =============================
ToDoテーブルのCREATEログが流れているのが確認できます。
さっそくDBの状態を確認。
$ rails db
SQLite version 3.47.1 2024-11-25 12:07:48
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata schema_migrations to_dos
ToDoテーブルが作成されていますね。
せっかくなので、カラムも確認してみます。
sqlite> .schema to_dos
CREATE TABLE IF NOT EXISTS "to_dos" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "created_time" time, "limit_time" time, "memo" varchar, "priority" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
んー?
自動採番のIDは良いとして、意図しない作成日時(created_at)と更新日時(updated_at)があります。
しかもDateTime型…だと!?
ちょっと調整が必要そうです。
テーブル定義変更
コマンドによるモデル・テーブル作成における仕様が分かったところで、その動きに合わせて変更を加えていきます。
まずはモデルの項目から、変更後は以下の通りです。
項目名 | カラム名 | データ型 |
---|---|---|
期限日時 | limit_time | datetime |
メモ(内容) | memo | string |
優先度 | priority | integer |
作成日時がデフォルトで入るので不要になりました。
また、デフォルトで入る項目の型がDateTimeとなっていたので、そちらに合わせました。(Timeの方が良いらしいですけどね!)
次にテーブルの修正ですが、まずはマイグレーションの状態を確認しておきます。
$ rails db:migrate:status
database: storage/development.sqlite3
Status Migration ID Migration Name
--------------------------------------------------
up 20241209045344 Create to dos
ToDoテーブルの作成が適用されていることが分かりましたので、ロールバックします。
$ rails db:rollback
== 20241209045344 CreateToDos: reverting ======================================
-- drop_table(:to_dos)
-> 0.0047s
== 20241209045344 CreateToDos: reverted (0.0237s) =============================
次にマイグレーションファイルを修正します。
class CreateToDos < ActiveRecord::Migration[8.0]
def change
create_table :to_dos do |t|
t.datetime :limit_time
t.string :memo
t.integer :priority
t.timestamps
end
end
end
最後にマイグレーション実行。
$ rails db:migrate
== 20241209045344 CreateToDos: migrating ======================================
-- create_table(:to_dos)
-> 0.0047s
== 20241209045344 CreateToDos: migrated (0.0051s) =============================
念のため、DBの状態も確認しておきます。
$ rails db
SQLite version 3.47.1 2024-11-25 12:07:48
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata schema_migrations to_dos
sqlite> .schema to_dos
CREATE TABLE IF NOT EXISTS "to_dos" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "limit_time" datetime(6), "memo" varchar, "priority" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
無事、テーブル定義の変更が完了しました。
サンプルデータ投入
テーブル定義が固まったところで、表示用のサンプルデータを投入したいと思います。
Railsでデータを投入するには、db/seed.rbに処理を追加するようです。
レコードごとにインサート処理を書くこともできるみたいですが、今回はCSVファイルにデータを定義していきます。
CSVファイルインポート用のコード(db/seed.rb)は下記の通り。
require "csv"
CSV.foreach('db/data/to_dos.csv') do |data|
ToDo.create(limit_time: data[0], memo: data[1], priority: data[2])
end
CSVファイルの中身は以下の通り。
"2024-12-25 21:00","memo1",5
"2024-12-25 21:00","memo2",4
"2024-12-25 21:00","memo3",3
"2024-12-25 21:00","memo4",2
"2024-12-25 21:00","memo5",1
データ投入前に、データベースを綺麗にしておきます。
Windows環境では下記のコマンドが使えないみたいなので注意。
$ rails db:migrate:reset
Permission denied @ apply2files - C:/work/Ruby/ToDoListApp/storage/development.sqlite3
Couldn't drop database 'storage/development.sqlite3'
bin/rails aborted!
Errno::EACCES: Permission denied @ apply2files - C:/work/Ruby/ToDoListApp/storage/development.sqlite3 (Errno::EACCES)
Tasks: TOP => db:drop:_unsafe
(See full trace by running task with --trace)
代わりに以下のコマンドで、DBをクリーンアップします。(やや強引ですが…)
$ rm storage/development.sqlite3
$ rails db:migrate
クリーンアップが完了したので、いよいよデータ投入。コマンド一発です。
$ rails db:seed
ToDoテーブルを確認してみます。
$ rails db
SQLite version 3.47.1 2024-11-25 12:07:48
Enter ".help" for usage hints.
sqlite> select * from to_dos;
1|2024-12-25 21:00:00|memo1|5|2024-12-11 05:31:13.374140|2024-12-11 05:31:13.374140
2|2024-12-25 21:00:00|memo2|4|2024-12-11 05:31:13.405766|2024-12-11 05:31:13.405766
3|2024-12-25 21:00:00|memo3|3|2024-12-11 05:31:13.412657|2024-12-11 05:31:13.412657
4|2024-12-25 21:00:00|memo4|2|2024-12-11 05:31:13.428899|2024-12-11 05:31:13.428899
5|2024-12-25 21:00:00|memo5|1|2024-12-11 05:31:13.435013|2024-12-11 05:31:13.435013
無事にデータ投入が完了しました。
表示ページの作成
データ投入まで完了したところで、今度はそれらを表示する画面を作ってみます。
Railsでは画面(View)を作るにはまずControllerを作成する模様。
Controllerの作成コマンドにはオプションが存在するが、まずは最小で作成してみる。
$ rails g controller to_dos
create app/controllers/to_dos_controller.rb
invoke erb
create app/views/to_dos
invoke test_unit
create test/controllers/to_dos_controller_test.rb
invoke helper
create app/helpers/to_dos_helper.rb
invoke test_unit
作成されたControllerファイルを見てみる。
class ToDosController < ApplicationController
end
う~ん、シンプル。
作成時のログから分かる様に、Viewのフォルダは作成されたがViewファイルは未作成のまま。
よって次はViewファイルを作成する。
Viewファイルの命名規則は以下の通り。
アクション名.html.拡張子
アクション名とそれぞれの動作については下記を参考にした。
今回は表示の機能なので、indexが妥当。
よって、「views/to_dos/index.html.erb」を作成。
ページ遷移の動作確認時に分かるように、文字だけ入れておく。
<p>ページ遷移できたよ!</p>
Viewファイルを作成したところで、次に設定するのはルーティング。
Railsのルーティングは「config/routes.rb」に設定される。
アプリ作成直後のファイルの中身は以下の通り。
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
# Defines the root path route ("/")
# root "posts#index"
end
これをパッと見て、アプリのフォルダを眺めて、「起動時にはいったいどんな動きであのページが表示されているんだ?」となった。
起動時のログは以下の通り。
Started GET "/" for 127.0.0.1 at 2024-12-16 10:35:48 +0900
ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC /*application='ToDoListApp'*/
Processing by Rails::WelcomeController#index as HTML
Rendering C:/Ruby33-x64/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/templates/rails/welcome/index.html.erb
Rendered C:/Ruby33-x64/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/templates/rails/welcome/index.html.erb (Duration: 1.4ms | GC: 0.0ms)
Completed 200 OK in 1094ms (Views: 9.3ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
Rubyに組み込まれているControllerが作動しているらしい?
ひとまず、この初期起動時の画面を作成したViewファイルに差し替えたい。
以下の通り、ルーティングの設定を変更する。
Rails.application.routes.draw do
root to: "to_dos#index"
end
起動時のログは以下の通り。
Processing by ToDosController#index as HTML
Rendering layout layouts/application.html.erb
Rendering to_dos/index.html.erb within layouts/application
Rendered to_dos/index.html.erb within layouts/application (Duration: 0.7ms | GC: 0.0ms)
Rendered layout layouts/application.html.erb (Duration: 71.7ms | GC: 0.0ms)
Completed 200 OK in 1104ms (Views: 81.3ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
上記を見ると分かるが、「views/layouts/application.html.erb」というのが親にいるらしい。
その親ファイルの中身は以下の通り。
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "To Do List App" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
<link rel="icon" href="/icon.png" type="image/png">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
何となく理解できる!
データ取得⇒表示
MVCモデルのそれぞれのファイルが作成できたところで、いよいよ結合していく。
データ取得
まずはDBに投入したサンプルデータをControllerで取得する。
全件取得はとても簡単で、「モデル.all」で出来ちゃいます。
class ToDosController < ApplicationController
def index
@todo = ToDo.all
Rails.logger.debug @todo.inspect
end
end
仕込んだログ出力の結果がコチラ。
#<ActiveRecord::Relation [#<ToDo id: 1, limit_time: "2024-12-25 21:00:00.000000000 +0000", memo: "memo1", priority: 5, created_at: "2024-12-11 05:31:13.374140000 +0000", updated_at: "2024-12-11 05:31:13.374140000 +0000">, #<ToDo id: 2, limit_time: "2024-12-25 21:00:00.000000000 +0000", memo: "memo2", priority: 4, created_at: "2024-12-11 05:31:13.405766000 +0000", updated_at: "2024-12-11 05:31:13.405766000 +0000">, #<ToDo id: 3, limit_time: "2024-12-25 21:00:00.000000000 +0000", memo: "memo3", priority: 3, created_at: "2024-12-11 05:31:13.412657000 +0000", updated_at: "2024-12-11 05:31:13.412657000 +0000">, #<ToDo id: 4, limit_time: "2024-12-25 21:00:00.000000000 +0000", memo: "memo4", priority: 2, created_at: "2024-12-11 05:31:13.428899000 +0000", updated_at: "2024-12-11 05:31:13.428899000 +0000">, #<ToDo id: 5, limit_time: "2024-12-25 21:00:00.000000000 +0000", memo: "memo5", priority: 1, created_at: "2024-12-11 05:31:13.435013000 +0000", updated_at: "2024-12-11 05:31:13.435013000 +0000">]>
少し見づらいですが、取れてますね。
データ表示(一覧表示)
データを取得できたところで、画面に表示してみましょう。
ひとまず表示するのはToDoの内容(memo)だけ。
Viewファイルを以下のように変更します。
<% @todo.each do |todo| %>
<p><%= todo.memo %></p>
<% end %>
実行結果がコチラ。
さすがに味気なさすぎるので、少し見栄えを良くしていきましょう。
ViewファイルとCSS(assets/stylesheets/application.css)をそれぞれ次のように修正します。
<table>
<tr>
<th colspan="2">ToDo</th>
</tr>
<tr>
<th>優先度</th>
<th>内容</th>
</tr>
<% @todo.each do |todo| %>
<tr>
<td class="priority">
<% if todo.priority == 5 %>
最高
<% elsif todo.priority == 4 %>
高い
<% elsif todo.priority == 3 %>
普通
<% elsif todo.priority == 2 %>
低い
<% elsif todo.priority == 1 %>
最低
<% end %>
</td>
<td><%= todo.memo %></td>
</tr>
<% end %>
</table>
table {
width: 100%;
}
th {
border: solid 1px #aaaaaa;
}
td {
border: solid 1px #aaaaaa;
}
.priority {
width: 20%;
text-align: center;
}
実行結果がコチラ。
ひとまず、最低限の表示機能を持たせることが出来ました。
今回はここまで。次回以降、処理を追加していきます。