Ruby on RailsでMySQL
Ruby on Railsでデータベースを指定する場合についてです。
プロジェクト作成時に指定できます。
プロジェクト作成時に指定
> rails new アプケーション名 -d データベース
データベースに指定できるのは以下です。
mysql
oracle
postgresql
sqlite3
frontbase
ibm_db
sqlserver
jdbcmysql
jdbcsqlite3
jdbcpostgresql
jdbc
これで作成した場合はdatabase.ymlに接続先情報を書き込めば終了です。
以降は-dオプションなしでデフォルトのsqlite3になっているものをMySQLに書き換える場合です。
Gemfile
Gemfileの以下の部分を、
gem "sqlite3", "~> 1.4"
以下に書き換え。
gem "mysql2", "~> 0.5"
bundle install します。
database.yml
データベースの接続設定を ./config/database.yml に追加します。
接続先情報が以下の場合です。
項目 | 情報 |
---|---|
ホスト | localhost |
ポート | 3306 |
データベース | testdb |
ユーザー | root |
パスワード | passw0rd |
sqlite3の設定を全て削除して以下に書き換え。
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: localhost
port: 3306
username: root
password: passw0rd
development:
<<: *default
database: testdb
test:
<<: *default
database: testdb
production:
<<: *default
database: testdb
テーブル作成と利用
db:migrateは使わずにテーブル作成して利用します。
Rails側でDBテーブルの作成などもできるのですが今一使い難いので、ドロ臭くなりますが普通にDB作ってRailsで利用するまでの手順です。
データベース作成
A5:SQL なんかでER図作って、DDL作成してデータベースに反映します。
自分は普段SpringBoot + DBFluteで開発してたりするので、A5:SQLが出力したDDLを最初にペタっと張ってマイグレートするのが手順として染み付いているので、Railsのように generate modelで1テーブルずつカラム指定して作成するのがどうしても慣れないんです。
簡単に以下のようなテーブルを作成しました。
ユーザーとユーザー属性は 1:1 です。
DDLは以下のようになります。
-- ユーザー属性
-- * RestoreFromTempTable
create table user_attributes (
id bigint not null auto_increment comment 'ID'
, user_id bigint not null comment 'ユーザーID'
, phone varchar(11) comment '電話番号'
, memo text comment 'メモ'
, created_at datetime(6) not null comment '作成日'
, updated_at datetime(6) not null comment '更新日'
, constraint user_attributes_PKC primary key (id)
) comment 'ユーザー属性' ;
-- ユーザー
-- * RestoreFromTempTable
create table users (
id bigint not null auto_increment comment 'ID:user_id'
, name varchar(255) not null comment '名前'
, created_at datetime(6) not null comment '作成日'
, updated_at datetime(6) not null comment '更新日'
, constraint users_PKC primary key (id)
) comment 'ユーザー' ;
alter table user_attributes
add constraint user_attributes_FK1 foreign key (user_id) references users(id)
on delete cascade
on update no action;
この既に出来上がってるDBをRailsから利用します。
Railsの場合テーブルの物理名は必ず複数形にするのが推奨されています。
以下でDBアクセス用のモデルを作成した場合、
> rails g model User
Railsは User = users に関連付けされるためです。
ただmodelとテーブルの関連付けは self.table_name を定義すれば指定できます。
テーブル名が userxxxxx になっている場合なら、
class User < ApplicationRecord
self.table_name = "userxxxxx"
end
これで関連付けできます。
他にもRailsではテーブルに細かく規約的なものがあります。
詳細は Active Record を参照。
大まかには以下な感じです。
- テーブル名は小文字スネークケースで複数形。line_itemsテーブルならLineItemモデルになります。
- 主キーは"id"のbigintで統一。
- 外部キー(foreign key)はテーブル名の単数形_id。line_itemsならline_item_idがfkになります。
- created_atカラムがある場合はレコード作成日時が自動的に設定される。
- updated_atカラムがある場合はレコード更新日時が自動的に設定される。
モデル作成
以下のコマンドでmodelを作成し、テーブルとの関連付けを行う。
> rails g model user --no-migration
> rails g model user_attribute --no-migration
テーブル名は users と user_attributes の複数形ですが、modelをgenerateする場合は単数形で指定します。
–no-migrationはRailsのマイグレーション機能をオミットするオプションです。これでrails db:migrateしなくてもよくなります。というかこれがないとアプリ実行中にdbのチェックが行われてエラーになります。
テーブル間の関係付け
usersとuser_attributesは1:1の親子関係です。
これをRailsで表現する場合はmodelに手を加えます。
テーブル間の関係付け (forreign key)は以下で表します。詳細
キーワード | 関係 |
---|---|
belongs_to | 親子関係の子側に設定 |
has_one | 親子関係(1:1)の親側に設定 |
has_many | 親子関係(1:N)の親側に設定 |
has_many :through | 親子関係の子を介して別テーブルを参照(N:N) |
has_one :through | 親子関係で孫テーブルを参照(1:1) |
has_and_belongs_to_many | N:N、A,Bという二つのテーブルがある場合A,B両方の主キーを持ったA_Bというテーブルがないと成り立たないです |
belongs_to, has_one, has_manyは2テーブル間、has_many :through, has_one :through, has_and_belongs_to_manyは3テーブル間の関係を示します。
usersとuser_attributesの関係ではbelongs_toとhas_oneを使います。
./models/user.rb
class User < ApplicationRecord
has_one :user_attribute
end
./models/user_attribute.rb
class UserAttribute < ApplicationRecord
belongs_to :user
end
コントローラ作成
適当にInfosというコントローラを作成します。
g scaffoldで作るとmodelまで作られてしまうのでg controllerで作成します。
> rails g controller Infos index show create update destroy
以下のパスが作成されます。
./config/routes.rbを変更します。
get 'infos', to: 'infos#index'
get 'infos/show/:id', to: 'infos#show'
post 'infos/create'
put 'infos/update/:id', to: 'infos/update'
delete 'infos/destroy/:id', to: 'infos#destroy'
アクションの実装
usersとuser_attributesテーブルを両方使用するような形で実装します。
index
usersとuser_attributesをjoinして一覧を出力します。
出力は以下のようなjsonにします。
[
{
"id": 1,
"name": "テスト01",
"user_attributes_id": 1,
"phone": "07011112222",
"memo": "テスト"
}
]
Active Recodeでjoinして必要なカラムだけ取得するには以下のようなコードになります。
@Users = User.joins(:user_attribute).select("users.id, users.name, user_attributes.id as user_attributes_id, user_attributes.phone, user_attributes.memo")
puts @Users.to_json()
上記で実行されるSQLが以下になります。
SELECT users.id, users.name, user_attributes.id as user_attributes_id, user_attributes.phone, user_attributes.memo FROM `users` INNER JOIN `user_attributes` ON `user_attributes`.`user_id` = `users`.`id`
joinsやselectなどの詳細は Active Record クエリインターフェイス が詳しいです。
このまま戻り値として使用できるので、indexの実装は以下のようになります。
def index
@Users = User
.joins(:user_attribute)
.select("users.id, users.name, user_attributes.id as user_attributes_id, user_attributes.phone, user_attributes.memo")
render json: @Users
end
show
indexと似た形で引数で渡されたidのレコードのみ返します。
def show
@User = User
.joins(:user_attribute)
.select("users.id, users.name, user_attributes.id as user_attributes_id, user_attributes.phone, user_attributes.memo")
.where("users.id = :id", { id: params[:id] }).first()
render json: @User
end
selectの所もうちょっとうまく書けないものか。
create
createでは以下のjsonを受け付けてusersとuser_attributesに新規レコードを作成ですね。
パラメータは以下にします。
{
"name": "テスト01",
"phone": "07011112222",
"memo": "テスト"
}
createメソッドの中身は以下です。
def create
form = params.permit(:name, :phone, :memo)
user = User.new()
user.transaction do
# usersテーブル追加
user.name = form[:name]
user.save!
# users_attributesテーブル追加
userAttr = UserAttribute.new()
userAttr.user_id = user.id
userAttr.phone = form[:phone]
userAttr.memo = form[:memo]
userAttr.save!
# 応答用のハッシュ作成
result = {
:user_id => user.id,
:name => user.name,
:user_attribute_id => userAttr.id,
:phone => userAttr.phone,
:memo => userAttr.memo
}
render json: result, status: :created
end
rescue => e
render json: { "message": e.message }, status: :internal_server_error
end
冗長ですが二つのテーブルをinsertする関係上途中でなんだかのエラーが発生した場合はロールバックする必要がありますのでこんな感じになってます。
細かく解説すると、
form = params.permit(:name, :phone, :memo)
上記の部分でリクエストのbody部分をハッシュで取得します。
Railsのルールとしてparams[:name]とやって取得するのはセキュリティ上問題があるので必ずpermitを使うようにする必要があります。
user = User.new()
user.transaction do
end
トランザクションはApplicationRecordのtransactionを使用します。
上記のdoブロックの中がトランザクションの有効範囲になります。
ここから例外(raise)が発生した場合はロールバックされます。
例外がなかった場合はコミットです。
手動でロールバック、コミットする方法は・・・見つけられませんでした。
render json: result, status: :created
上記でJSONの応答を生成します。
JSONの内容は以下のようになります。
{
"id": 1,
"name": "テスト01",
"user_attribute_id": 1,
"phone": "07011112222",
"memo": "テスト"
}
statusの部分でHTTPステータスを指定します。
:createdは 201です。他のステータスは HTTPステータスコード に一覧があります。
update
ほぼcreateと同じ内容です。
def update
form = params.permit(:id, :name, :phone, :memo)
user = User.find(form[:id])
userAttr = UserAttribute.where(["user_id = :id", { id: form[:id] }]).first()
user.transaction do
# usersテーブル更新
user.name = form[:name]
user.save!
# users_attributesテーブル更新
userAttr.phone = form[:phone]
userAttr.memo = form[:memo]
userAttr.save!
# 応答用のハッシュ作成
result = {
:id => user.id,
:name => user.name,
:user_attribute_id => userAttr.id,
:phone => userAttr.phone,
:memo => userAttr.memo
}
render json: result, status: :created
end
rescue => e
render json: { "message": e.message }, status: :internal_server_error
end
destroy
usersテーブルだけ削除します。
user_attributesの方はFKの設定で “on delete cascade” を設定しているので親のusersを削除すれば勝手に削除されます。
def destroy
user = User.find(params.permit(:id)[:id])
user.destroy
render status: :ok
end
随分長いなったけど基本はこんな感じになりますか。
もう少し簡潔に記述できる気がします。でもドロ臭い方が分かり易い。
コメント
コメントを投稿