Rails8の続き、今回はDB周りです。

Railsを知ったときの驚きと言えばなんといってもActiveRecord、いわゆるORMとして、DBの構造をクラスにマッピングする(しかもおおよそ自動で)ということで動的言語の醍醐味を見せつけてくれるものでした。 内部的な違いはもちろん当時と今で異なるでしょうが、それでも基本は変わっていないようです。

1
2
3
4
5
6
7
% ./bin/rails generate model Product name:string
      invoke  active_record
      create    db/migrate/20250227023240_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml

ということで、モデルProductに相当するクラスProductを作成しました、現時点では必須となるID要素以外ではname(文字列)を持つように設定差rています。

  • マイグレーション向けのコードが db/migrate ディレクトリ以下に作られる
  • DB上のProductテーブルに対応するモデルクラスが app/models/product.rb に作られる
  • テスト用のコードも test/models/product_test.rb に作られる

各商品(Product)に対応するレコードのため、モデル名は単数形でないといけないよと言う注意書きも忘れずに、と。

マイグレーション用のコードを確認すると、以下のようになっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# db/migrate/2025XXXXXXXX_create_products.rb

class CreateProducts < ActiveRecord::Migration[8.0]
  def change
    create_table :products do |t|
      t.string :name

      t.timestamps
    end
  end
end

内部的に必要となるカラムは裏で自動で作られるみたいなのでほとんど記述は無し、その上で必要となるnameカラムが作成時の引数から準備されています。

  • テーブル名(=products)が複数形なのは要注目、商品(Product)の集合ですから複数形ですよね、と
  • t.timestampsにより、created_atupdated_atが自動で作られます、ともにdatetime型とのことです

とりあえずこれで実際にマイグレーション実行、と。

1
2
3
4
5
% ./bin/rails db:migrate
== 2025XXXX023240 CreateProducts: migrating ===================================
-- create_table(:products)
   -> 0.0112s
== 2025XXXX023240 CreateProducts: migrated (0.0114s) ==========================

しれっとデータベースが準備されているようです。これはSQLiteですね。

1
2
3
4
5
6
7
8
9
% ls storage
development.sqlite3
% sqlite3 storage/development.sqlite3
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> .table
ar_internal_metadata  products              schema_migrations
sqlite>   # <- Ctrl-D
%

Rails8での大きな変更点のひとつが「SQLiteで十分にデプロイして本番に持って行ける」ということだそうです、これは便利そうですね。

つづいて"Rails Console"、昔はあったのかよくわかりませんが、Laravelにおけるartisan上で使うコンソールのようなものでしょうか。

1
2
3
4
% ./bin/rails console
Loading development environment (Rails 8.0.1)
store(dev)> Rails.version  # すごい、補完機能が動く
=> "8.0.1"

ここではこれしか使わないみたいなので、いったん抜けておきましょう(Ctrl-D)。

つづいてモデル周り、ActiveRecordですね。

1
2
3
# app/models/product.rb
class Product < ApplicationRecord
end

たったこれだけで、以下のことが伝わってしまうのが恐ろしい。

  • Productクラスが基底クラスApplicationRecordを継承している
  • Productクラスにより、その集合となるテーブルproductsを参照/更新することがはっきりする
  • 基本の操作は(カスタマイズしなければ)自動で行われるし、フィールド(カラム)の情報は勝手にテーブル構造から吸い上げて準備してくれる

ほんと、ActiveRecordの衝撃は今も変わらずですね。 コンソールを使って確認してみましょう。

1
2
3
4
% ./bin/rails console
Loading development environment (Rails 8.0.1)
store(dev)> Product.column_names
=> ["id", "name", "created_at", "updated_at"]

idフィールドは自動だし、nameはマイグレーションで追加されています。 その上でcreate_at/updated/atがもマイグレーション側で自動で追加されています。 こいつらがテーブルのチェックから自動で取得されているわけです。

テーブルに対するCRUD操作もこのままコンソールでチェックです。

C(reate)は、Productクラスからインスタンスを生成することになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
% ./bin/rails console
Loading development environment (Rails 8.0.1)
store(dev)> product = Product.new(name: "Tシャツ")
=> #<Product:0x00000001328ac7c0
...
store(dev)> product.save
  TRANSACTION (0.4ms)  BEGIN immediate TRANSACTION /*application='Store'*/
  Product Create (13.3ms)  INSERT INTO "products" ("name", "created_at", "updated_at") VALUES ('Tシャツ', '2025-02-27 03:04:16.507146', '2025-02-27 03:04:16.507146') RETURNING "id" /*application='Store'*/
  TRANSACTION (1.4ms)  COMMIT TRANSACTION /*application='Store'*/
=> true

さすが、SQLがバックで生成されて実行されました。 今回はここまで。