Rails Scaffoldを使用して簡単にCRUD機能を作成する
はじめに
scaffold
を使用して、RailsのプロジェクトにCRUD機能を追加してみます。
ビューの一部は生成されたものから、少し修正しています。
Scaffold
scaffold
はRailsに備わっているコマンドの1つです。使用すると、指定されたモデルに基づいてCRUD操作を行うための一連のファイル(モデル、ビュー、コントローラー、マイグレーション、テストなど)が自動的に生成されます。
bundle exec rails generate scaffold <モデル名> <カラム名:型> <カラム名:型>
今回はこちらを実行します。
bundle exec rails generate scaffold User name:string email:string
生成されたファイルは以下の通りです。
今回ビューは、html2slim
をインストールしているのでslim
形式でファイルが作成されています。
app/
├── controllers/
│ └── users_controller.rb # CRUDアクションが定義
├── models/
│ └── user.rb
├── helpers/
│ └── users_helper.rb
├── views/
│ └── users/
│ ├── index.html.slim # 全ユーザーのリストを表示
│ ├── index.json.jbuilder # 全ユーザーのリストをJSON形式で表示
│ ├── edit.html.slim # ユーザーの編集フォーム
│ ├── show.html.slim # ユーザーの詳細情報を表示
│ ├── show.json.jbuilder # ユーザーの詳細情報をJSON形式で表示
│ ├── _user.json.jbuilder # 個別ユーザーのデータをJSON形式で部分テンプレート
│ ├── new.html.slim # 新しいユーザーを作成するフォーム
│ └── _form.html.slim # 新規作成と編集のための共通フォームのテンプレート
└── assets/
└── stylesheets/
├── users.scss
└── scaffolds.scss
config/
└── routes.rb # 「resources :users」が追記されます
db/
├── migrate/
│ └── [timestamp]_create_users.rb
test/
├── models/
│ └── user_test.rb
├── controllers/
│ └── users_controller_test.rb
├── system/
│ └── users_test.rb
└── fixtures/
└── users.yml
scaffold
は、足場という意味みたいです。
_
)で始まるファイル
名前がアンダースコア(_test.html.slim
のように名前がアンダースコア(_
)で始まるファイルは部分テンプレートとして扱われます。これは他のビューファイルから簡単に再利用できるようにするための慣習で、render
メソッドを使用すると部分テンプレートを呼び出すことができます。
resources :users
UsersController
に対するルーティングを定義しています。これにより、以下のようなルーティングが自動的に設定されます。(LaravelのRoute::resource
と似ていますね)
メソッド | URL | ルート名 | HTTPメソッド |
---|---|---|---|
index | /users | users#index | GET |
new | /users/new | users#new | GET |
create | /users | users#create | POST |
show | /users/{id} | users#show | GET |
edit | /users/{id}/edit | users#edit | GET |
update | /users/{id} | users#update | PUT/PATCH |
destroy | /users/{id} | users#destroy | DELETE |
ビュー自動レンダリングについて
Railsでは、HTTPメソッドとルーティングが指定されたアクションに一致する場合、そのアクション名に基づいて対応する同名のビューファイルが自動的にレンダリングされます。
特定のアクションに対するビューレンダリングの明示的な記述がコントローラーになくても、自動的に関連付けられたビューがレスポンスされます。
ビューファイルの内容
h1 Listing users
table
thead
tr
th Name
th Email
th
th
th
tbody
- @users.each do |user|
tr
td = user.name
td = user.email
td = link_to "Show", user
td = link_to "Edit", edit_user_path(user)
td = link_to "Destroy", user, data: {confirm: "本当に削除しますか?"}, method: :delete
br
= link_to "New User", new_user_path
link_to ... user
show
メソッドへのリンクが生成されます。URLは、/users/{id(user.id)}
です。
edit_user_path(user)
名前付きルートです。users#edit
のことです。
また今回は引数にuser
があるため、URLは/users/{id}/edit
です。
new_user_path
名前付きルートです。users#new
のことです。URLは、/users/new
です。
users_path
名前付きルートです。users#index
のことです。URLは、/users
です。
= form_for @user do |f|
- if @user.errors.any?
#error_explanation
h2 = "#{pluralize(@user.errors.count, "error")} prohibited this user from being saved:"
ul
- @user.errors.full_messages.each do |message|
li = message
.field
= f.label :name
= f.text_field :name
.field
= f.label :email
= f.text_field :email
.actions = f.submit
form_for @user do |f|: @user
form_forは
<form>を生成しています。
f`はフォームビルダーオブジェクトと呼ばれ、このオブジェクトを通じてフォームの入力フィールドを簡単に作成することができます。
pluralize
最初の引数に整数があると、それに基づいて2番目の引数である英単語が複数形に変化したものが渡されます。
singlarize
pluralize
の他に、singlarize
は指定した文字列を複数形に変換します。
@user.errors.full_messages.each do |message|
each
メソッドは配列の各要素を取り出してmessage
に格納しています。
ブロック変数
Rubyのブロック(do...end
や {...} で囲まれたコード)では、ブロック内で使う変数をパイプ (| |
) で囲んで宣言します。これをブロック変数と呼びます。
p#notice = notice
p
strong Name:
= @user.name
p
strong Email:
= @user.email
= link_to "Edit", edit_user_path(@user)
'|
= link_to "Back", users_path
= notice
flash[:notice]
に格納された内容を直接参照し、それを出力してフラッシュメッセージを表示しています。
alert
もあります。
h1 Editing user
= render "form"
= link_to "Show", @user
'|
= link_to "Back", users_path
render
他のテンプレートを呼び出してその場所に内容を埋め込むことができます。
コントローラーの内容
長いので一部省略して記述します。
Railsでは、コントローラのアクション内でインスタンス変数(@で始まる変数)を設定することにより、自動的に変数をビューで利用できるようになります。
# POST /users or /users.json
def create
@user = User.new(user_params) # user_paramsから取得したデータを元にモデルをインスタンス化
respond_to do |format|
if @user.save # ここでバリデーションが実行される
format.html { redirect_to user_url(@user), notice: "User was successfully created." }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
respond_to
同一のアクションから条件に応じて異なるフォーマット(HTML、JSONなど)を同時にレスポンス処理することが可能です。
render
単一フォーマットのレスポンスしか必要としない場合に使用します。
コントローラー内で明示的に指定されたrender
メソッドがある場合、明示的に指定されたrender
メソッドが優先されてビューの自動レンダリングはオーバーライドされます。
# app/controllers/⚪︎⚪︎_controller.rb
def show
@user = User.find(params[:id])
if @user.active?
flash.now[:notice] = "アクティブなユーザーです!"
render :show_active
else
flash.now[:alert] = "非アクティブなユーザーです。"
render :show_inactive
end
end
flash[:notice]
フラッシュメッセージ機能を利用して、任意のキーに対する値を取得することができます。
今回はflash
ハッシュの:notice
キーを直接参照していますが、flash[:error]
やflash[:success]
などように適用できます。
- if flash[:notice]
p = flash[:notice]
format.html
HTMLビューをクライアントに送信します。
notice
一時的なメッセージをユーザーに表示するために使用されるフラッシュメッセージです。
format.json
JSONレスポンスを指定します。
下記のファイルが自動で追加されます。
db/
└ development.sqlite3 # 開発環境で使用されるSQLite3データベースファイル
└ schema.rb # データベースのスキーマ情報を記述されているファイル
users
テーブルが追加され、name、emailとtimestampsが自動でマイギュレーションファイルに追記されるのでcreated_at
とupdated_at
のカラムも自動的に作成されます。
テーブル名はモデルの複数形になります。
モデルの設定
バリデーションなどを追加してください。
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end
バリデーション
presence: true
空でないこと(必須項目)
uniqueness: true
ユニークであること
with: URI::MailTo::EMAIL_REGEXP
Rubyの標準ライブラリに含まれているメールアドレスの形式を確認するための正規表現です。
ブラウザで確認
サーバーを起動してください。
bundle exec rails server
http://localhost:3000/users にアクセスして動作を確認してください。
削除が正しく機能せず、詳細が表示されると思います。
destroyではなくshowが実行される
link_to
をbutton_to
に変更すると、正しく機能するようですが、これだとdata: { confirm: "本当に削除しますか?" }
の確認ダイアログが表示されませんでした。
td = button_to "Destroy", user, method: :delete, data: { confirm: "本当に削除しますか?" }
コンソールを確認すると、下記のエラーが出ていました。
Error: Module build failed (from ./node_modules/babel-loader/lib/index.js): Error: Cannot find package '@babel/plugin-proposal-private-methods' imported from /Users/ユーザー名/.../babel-virtual-resolve-base.js
RailsがWebpackerを導入した際に、デフォルトでyarn
を使用する設定になっているので今回はyarn
を使用します。(プロジェクトにpackage.json
はあるがpackage-lock.json
がなく、yarn.lock
が存在する場合は、そのプロジェクトがyarn
を使用して依存関係を管理していることを示しています。)
yarn add @babel/plugin-proposal-private-property-in-object
これで動くようになるかと思い確認すると、次はコンソールに下記のエラーが出ていました。
Uncaught Error: Module build failed (from ./node_modules/babel-loader/lib/index.js): Error: [BABEL]: --- PLACEHOLDER PACKAGE --- This @babel/plugin-proposal-private-property-in-object version is not meant to be imported. Something is importing @babel/plugin-proposal-private-property-in-object without declaring it in its dependencies (or devDependencies) in the package.json file.
こちらも追加してみます。
yarn add @babel/plugin-proposal-private-property-in-object
無事に動作するようになりました。
JavaScriptのコードを正しく変換するために必要なBabelのプラグインが不足していたために、Rails UJSが正常に動作していなかったことが原因でした。
プラグインをインストールすることで、JavaScriptが正しく変換されるようになり、Rails UJSも正常に機能するようになったため、method: :delete
を使ったリンクが正しく動作するようになって問題が解決したと思います。
Babel
ECMAScript2015以降のコードを現在のブラウザーや環境、古いブラウザーや環境で下位互換性のあるバージョンのJavaScriptに変換するために主に使用されるツールです。
Rails UJS
RailsでJavaScriptを使用する際に、HTML要素に特定のdata
属性を指定することで、Ajax操作やその他のJavaScript関連の動作を簡単に実装できるようサポートするライブラリです。
このライブラリはWebpackerとともに動作し、Railsアプリケーションの標準的な機能としてデフォルトで含まれています。特に、フォームの非同期送信やリンクでの特定のHTTPメソッドに使われます。https://www.npmjs.com/package/@rails/ujs
"@rails/ujs": "^6.0.0",
"@rails/webpacker": "5.4.4",
method: :delete
HTMLフォームの生成時に使用するヘルパーメソッドです。これにより、通常のHTTPリクエスト(GETやPOST)とは異なるDELETEリクエストを生成することができます。
Railsコンソール
モデルを介してデータベースにクエリを発行したり、アプリケーションのメソッドを試したり、新しいデータをデータベースに追加したりすることができます。
Railsコンソールが起動してみてください。exit
または、quit
で終了することができます。
bundle exec rails console
# 省略できます
bundle exec rails c
データがある状態で、最初のユーザーを取得してみてください。
User.first
取得できているのがわかると思います。
SQLiteデータベースに接続
下記コマンドでSQLiteデータベースに接続することができます。.quit
で終了することができます。
sqlite3 db/development.sqlite3
テーブル一覧を取得してみてください。
.tables
取得できていることがわかると思います。
SQLiteのGUIツール
テストの実行
テストを実行して、全ての変更が正しく機能するかを確認してください。
bundle exec rails test
今回は2 failures
、1 errors
となっているので、ログを確認して修正します。
日本語化
rails-i18n
というRailsアプリケーションを多言語対応させるためのライブラリをインストールしてください。
gem 'rails-i18n'
インストールしてください。
bundle install
config.i18n.default_locale = :ja
を設定しない場合、デフォルトの言語は英語(:en
)のままです。
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local
config.i18n.default_locale = :ja
# 国際化に使用する追加のロケールファイルのパスを設定
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
再起動して確認すると、エラーメッセージが日本語になっていると思います。
エラー文の表示も日本語化します。
config/locales/en.yml
しかないと思ます。日本語用のja.yml
を作成して
touch config/locales/ja.yml
エラーメッセージと属性名を記述してください。
ja:
errors:
template:
header:
one: '1つのエラーがあります。'
other: '%{count}つのエラーがあります。'
activerecord:
models:
user: 'ユーザー'
attributes:
user:
name: '名前'
email: 'メールアドレス'
messages:
record_invalid: '保存できませんでした:'
ビューファイルを修正してください。
= form_for @user do |f|
- if @user.errors.any?
#error_explanation
h2= t('errors.template.header', count: @user.errors.count)
ul
- @user.errors.full_messages.each do |message|
li = message
.field
= f.label :name
= f.text_field :name
.field
= f.label :email
= f.text_field :email
.actions
= f.submit
t
translate
メソッドのエイリアスです。多言語対応させることができます。
errors.template.header
翻訳ファイル(ja.yml
)に定義されている以下の箇所を指しています。
ja:
errors:
template:
header:
one: '1つのエラーがあります。'
other: '%{count}つのエラーがあります。'
count: @user.errors.count
t
メソッドを使用して翻訳ファイル(ja.yml
)のcount
に@user.errors.count
(エラーの数)が渡されています。
下記のように表示されるか確認してください。
DB設定
config/database.yml
ファイルでデータベース接続情報を設定しています。デフォルトではSQLiteが設定されていますが、MySQL、PostgreSQLなどに変更することもできます。
モデル作成
下記コマンドで新しいモデルを作成することができます。
bundle exec rails generate model User name:string email:string
モデル、マイギュレーションファイルなどが作成されます。
├── app
│ ├── models
│ │ └── user.rb
├── test
│ ├── models
│ │ └── user_test.rb
│ └── fixtures
│ └── users.yml
└── db
└── migrate
└── [timestamp]_create_users.rb
コントローラーの作成
アクション名を指定すると、ビューファイルなどが追加されます。
bundle exec rails generate controller Pages
下記のファイルが作成されます。
app/
├── controllers/
│ └── pages_controller.rb
├── helpers/
│ └── pages_helper.rb
├── assets/
│ └── stylesheets/
│ └── pages.scss
└── test/
└── controllers/
└── pages_controller_test.rb
ルーティングの設定
Rails.application.routes.draw do
get 'about/index'
get '/about', to: 'pages#about'
end
get 'about/index'
/slim/index
というパスにGETリクエストが来たときにSlimController
のindex
アクションを明示的に呼び出すよう指定しています。そのため、URLは/about/index
になります。
get '/about', to: 'pages#about'
/slim
というパスにGETリクエストが来たときにSlimController
のindex
アクションを明示的に呼び出すよう指定しています。そのため、URLは/about
になります。
do ... end
複数行にわたる場合
Rails.application.routes.draw do
root "top#index"
resources :users
end
{ ... }
一行で簡潔に書く小さなブロックに適しています。
Rails.application.routes.draw { root "top#index" }
ActiveRecord::PendingMigrationError
マイグレーションファイルが実行されていない(適用されていない)状態を検出したときに発生するエラーです。
今回の場合は、表示されているRun pending migrations
を押すか、表示されているようにbin/rails db:migrate RAILS_ENV=development
を実行すると、解決します。
ActionController::UnknownFormat in UsersController#index
サーバーを起動して、http://localhost:3000/users にアクセスした際にブラウザで表示されました。
UsersController#index is missing a template for this request format and variant.
request.formats: ["text/html"]
request.variant: []
サーバーを再起動すると問題なく表示されました。
終わりに
何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉
Discussion