2006.1.21 に Rubyist九州 Meeting 第1回にて発表した, Ruby on Rails のスライドです。2005年12月に Rails 1.0 が出ました。その前後, 同月に初めて Railsに触ったので、まだほんの初歩的、序の口のところです。
[2021.5] ざっと現代の状況に更新。Ruby 3.0 + Ruby on Rails v6.1. サンプルコード netsphere / rails-examples · GitLab
ほしたら、順番に作っていこう。まずはひな形を生成。
rails generate サブコマンド コマンドでいろいろ自動生成. サブコマンド一覧;
application_record assets benchmark channel controller generator helper integration_test jbuilder job mailbox mailer migration model resource scaffold scaffold_controller system_test task
active_record:application_record
rack_profiler:install
test_unit:channel test_unit:generator test_unit:install test_unit:mailbox test_unit:plugin
Rails では, リソース (オブジェクト) に注目して, アプリケィションを作っていく。
Scaffold は建築現場などの足場という意味で、建てるビルそのものではない。Rails では自動生成させたコードを出発点にして修正して, ちゃんとしたものを作っていく。
Usage: rails generate scaffold NAME [field[:type][:index] field[:type][:index]] [options]
NAME
は小文字の単数形. やってみよう.
次のファイルが作られる. app/models
ディレクトリには単数形のファイルが, app/controllers
には 複数形_controller.rb が, app/views
には複数形のディレクトリが掘られて, コマンドに対応する ERBテンプレートが生成される。
$ rails g scaffold article title:string body:text invoke active_record create db/migrate/20210709151353_create_articles.rb create app/models/article.rb invoke test_unit create test/models/article_test.rb create test/fixtures/articles.yml invoke resource_route route resources :articles invoke scaffold_controller create app/controllers/articles_controller.rb invoke erb create app/views/articles create app/views/articles/index.html.erb create app/views/articles/edit.html.erb create app/views/articles/show.html.erb create app/views/articles/new.html.erb create app/views/articles/_form.html.erb invoke resource_route invoke test_unit create test/controllers/articles_controller_test.rb create test/system/articles_test.rb invoke helper create app/helpers/articles_helper.rb invoke test_unit invoke jbuilder create app/views/articles/index.json.jbuilder create app/views/articles/show.json.jbuilder create app/views/articles/_article.json.jbuilder invoke assets invoke scss create app/assets/stylesheets/articles.scss invoke scss create app/assets/stylesheets/scaffolds.scss
config/routes.rb
ファイルにルーティングが追加される。
上述のとおり、Ruby on Rails における「モデル」は、データベースの表と1:1で対応する Ruby クラスを作る。
データをリレーショナルデータベースへ保存する設計は、Active Record と Data Mapper がある。
Rails が採用する Active Record パタンでは、一つ一つのレコード (表では行として表される) が振る舞い、つまり操作するメソッドも持つ。他方、Data Mapper パタンでは、レコードクラスは振る舞いのためのメソッドは持たない。Active Record 実装では、振る舞いのためのメソッドを定義する基底クラスから派生させる。そのため、ほかのクラスから派生させることができないが、いちいち別に操作のためのクラスが出てこないので、記述量は減る。
Data Mapper パタンであっても、ほかのフレームワーク・O/Rマッパでは, 下手な基底クラスを使うと上手く動かないこともままある。ほかのO/R マッパには、開発時にどちらか選べるようなものもある。そういうときは Active Record のほうがメリット大きいと思う。
複数の表を跨ぐようなビジネスロジックは, Rails でのモデル (=表) の上に, 別クラスとして構築する。
実際のソースコードを見ていこう。
rails generate コマンドでひな形を生成すると, migration コードも自動生成される。列と NOT NULL
制約などを記述する。
db/migrate/20210709151353_create_articles.rb
ファイル:
create_table
メソッドでテーブルを作成する。null:false
で NOT NULL
制約.
rake コマンドで, データベーススキーマのヴァージョンを上げ下げできる。
rake サブコマンド | 説明 |
---|---|
db:migrate | Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE... |
db:migrate:down | Runs the "down" for a given migration VERSION. 一つ戻したいときは db:rollback .
|
db:migrate:redo | Rolls back the database one migration and re-migrates up (opti... |
db:migrate:status | Display status of migrations |
db:migrate:up | Runs the "up" for a given migration VERSION |
では、データベーススキーマのヴァージョンを上げてみよう. hello_app
ディレクトリにて、コマンドを叩く。
$ rake db:migrate
== 20210709151353 CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0046s
== 20210709151353 CreateArticles: migrated (0.0049s) ==========================
$ rake db:migrate:status
database: db/development.sqlite3
Status Migration ID Migration Name
--------------------------------------------------
up 20210709151353 Create articles
app/models/article.rb
ファイル: データベースの表と対応したモデルクラス.
モデルクラスは, モデルに共通の ApplicationRecord
クラスから派生させる。ActiveRecord::Base
から派生している。
Rails では列を明示的に書かなくてもよい。クラスのプロパティが自動的に生成される。
バリデーション (検証) は、出来合いの内容なら validates
クラスメソッドで指定するだけ。メソッド定義してもよい。
app/models/application_record.rb
ファイル: 共通のコードを書く.
基底クラスである ActiveRecord::Base
が振る舞いを定義している。保存(新規, 更新) save()
, 削除 destroy()
など。
検索はクラスメソッドの find()
など。
リレーショナルデータベースに生成されるテーブル名は, 複数形、小文字アンダースコア (snake_case). SQL は大文字小文字を区別しないので、これしかない。
主キーは自動的に id
という名前で生成される。整数型。MySQLでは、auto_increment
も必要.
外部キー (FK) は、'単数形+_id'という名前にする。ex) product_id
SQLite だと次のスキーマが生成される。
CREATE TABLE "articles" ( "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar NOT NULL, "body" text NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL );
Action Packパッケージがリクエストからレスポンスまでのルーティングを担当。
コントローラ名: | 小文字のアンダースコア (snake_case). 'admin/credit_cards ' など'/ ' で区切って長くできる.
|
クラス名: | コントローラ名をPascalCase にして, + Controller 。パスの部分は Rubyモジュール名になる. コントローラ名が admin/credit_cards なら, Admin::CreditCardsController
|
ファイル名: | クラス名小文字アンダースコア化 (snake_case). |
Rails では, URLとHTTP動詞から, 処理するコントローラとアクションが決まる。コントローラは 1:1 で対応するクラスになる。アクションはそのクラスのメソッド。
コントローラとアクションから, クライアントに戻すためのデフォルトビューが自動的に決まる。
Rails はリソース中心に組み立てられている。config/routes.rb
にルーティングを記述する。このファイルに単に次のように書くと,
次のURL とアクションの対応が作られる。rails routes コマンドで一覧表示できる。※以前は rake routes コマンドだったが、Rails v6 で取り除かれた。rake routes の解説は古い。
Prefix | HTTP動詞 | URI Pattern | Controller#Action |
---|---|---|---|
articles | GET | /articles(.:format) | articles#index |
POST | /articles(.:format) | articles#create | |
new_article | GET | /articles/new(.:format) | articles#new |
edit_article | GET | /articles/:id/edit(.:format) | articles#edit |
article | GET | /articles/:id(.:format) | articles#show |
PATCH or PUT | /articles/:id(.:format) | articles#update | |
DELETE | /articles/:id(.:format) | articles#destroy |
URI が同じであっても、HTTP動詞によって、呼び出されるアクションが変わってくる。現代のRESTful API デザインでは, update のためには PUT
のほうがメジャー。Rails は, 過去との互換性のため, PATCH
(RFC 5789; 2010年) も受け付ける。
URI Pattern 内の :id
などのパラメータは, Rubyコード側から params[:名前]
で得られる。
/
の場合にリダイレクトするよう、次のようにする。
アプリケィションで共通の基底クラス. ActionController::Base
から派生させる。
app/controllers/application_controller.rb
:
基本は, リソースごとにコントローラクラスを作る。まず先に、コードを示す。
app/controllers/articles_controller.rb
:
モデルオブジェクトのメソッド
self.find(主キー)
self.new()
. ActiveRecord オブジェクトの生成。まだ保存しない。
save()
. 基本的に save!()
のほうを使う。後者は, 検証に失敗したとき ActiveRecord::RecordInvalid
例外を発生する。
update_attributes(hash)
は非推奨。update(hash)
に置き換わった。さらに, 基本的に update!(hash)
を使う. save!()
と同様に, 検証に失敗したとき ActiveRecord::RecordInvalid
例外を発生する。
フィールドを更新して、saveも行う
destroy
●●TODO: 更新.
最後に, ビューを作って、レスポンスを返せるようにする。
Rails v6.0 からデフォルトで, webpack v4 を wrap する 'webpacker' gem を利用する。(Rails v5 ではオプションだった.)
Webpacker は人気がないようで、素の webpack を使う向きが多い [2022-05] webpack v4 は OpenSSL v3.0 環境では動かない。そのため, Fedora 36, CentOS Stream 9 では動かなくなった。基本的なライブラリの更新は、思わぬところに影響が生じる。
新しくアプリケィションを作成するときは, Rails v7 (Webpacker は廃止) にするか、Rails v6 の場合であっても, Webpacker を剥がして、素の webpack v5 (v4 ではなく) を使うようにすればよい。'jsbundling-rails' gem を使えばよい。
ビルドに yarn コマンドが必要。
設定ファイルを更新。 ビルド.
サーバから HTML を返す部分は, ERB テンプレートで作る。
ビュー
Webpacker
ようだが、とりあえず、Rails のデフォルトに近い状態で動かす。
# npm install --global yarn
Gemfile
で Turbolinks を外したので、app/views/layouts/application.html.erb
ファイルも, 'data-turbolinks-track
' を外す。
エントリーポイントは app/javascript/packs/application.js
app/javascript/application.js
ファイルに変更。内容は同じ。
$
rails webpacker:install コマンドは次に変更; rails javascript:install:webpack
create config/webpacker.yml
Copying webpack core config
create config/webpack
create config/webpack/development.js
create config/webpack/environment.js
create config/webpack/production.js
create config/webpack/test.js
Copying postcss.config.js to app root directory
create postcss.config.js
Copying babel.config.js to app root directory
create babel.config.js
Copying .browserslistrc to app root directory
create .browserslistrc
The JavaScript app source directory already exists
apply /opt/rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/webpacker-5.4.0/lib/install/binstubs.rb
Copying binstubs
exist bin
create bin/webpack
create bin/webpack-dev-server
append .gitignore
Installing all JavaScript dependencies [5.4.0]
run yarn add @rails/webpacker@5.4.0 from "."
yarn add v1.22.10
info No lockfile found.
[1/4] Resolving packages...
以下略.
ERBファイル
app/views/articles
ディレクトリ:
app/views/articles/index.html.erb
<p id="notice"><%= notice %></p> <h1>Articles</h1> <table> <thead> <tr> <th>Title</th> <th>Body</th> <th colspan="2"></th> </tr> </thead> <tbody> <% @articles.each do |article| %> <tr> <td><%= article.title %></td> <td><%= article.body %></td> <td><%= link_to 'Show', article %></td> <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table> <p><%= link_to 'New Article...', new_article_path %>
app/views/articles/show.html.erb
<p id="notice"><%= notice %></p> <p> <strong>Title:</strong> <%= @article.title %> </p> <p> <strong>Body:</strong> <%= @article.body %> </p> <%= link_to 'Edit...', edit_article_path(@article) %> | <%= link_to 'Back', articles_path %>
render 'partial_form'
_partial_form.html.erb
ファイルの内容を出力
<%=h ... %>
XSSを発生させないために、忘れないこと。new.html.erb
と edit.html.erb
は、タイトルを除き、まったく同じ。
<h1>New Article</h1> <%= render 'form', article: @article %>
_form.html.erb
<% # Rails5 から form_with. 以前は form_for / form_tag だった. # url: オプションを指定 (以前の form_tag), or # model: オプションを指定 (以前の form_for). %> <%= form_with(model: article) do |form| %> <% if article.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2> <ul> <% article.errors.each do |error| %> <li><%= error.full_message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= form.label :title %> <%= form.text_field :title %> </div> <div class="field"> <%= form.label :body %> <%= form.text_area :body %> </div> <div class="actions"> <% # new (create) or edit (update) 判定 %> <%= link_to 'キャンセル', article.persisted? ? @article : articles_path %> <%= form.submit %> </div> <% end %>
動くことを確認!