Rubyチュートリアルまとめ

3章ふりかえり
やったこと
-
3度目の新規Railsアプリケーションを作成。
-
必要なgemのインストール
-
リモートリポジトリへのプッシュ
-
production環境設定
-
コントローラを新規作成
- railsコマンド "rails generate controller ControllerName アクション名"(省略可)
-
新しいルーティングはconfig/routes.rbファイルで定義
-
Viewでは、静的HTMLの他にERB(埋め込みRuby: Embedded Ruby)が使用できる
-
新機能開発は自動化テストを使って進める
- 自信を持ってリファクタリングできるようになる
- 回帰バグも素早くキャッチできるようになる
- テスト駆動開発は「 red ・ green ・REFACTOR」サイクルを繰り返す
-
Railsのレイアウトでは、アプリケーションのページの共通部分をテンプレートに置くことでコードの重複を解決することができる(DRYの原則)
-
ルートディレクトリは、root 'static_pages#home'と書く事で設定できる(root_urlメソッドがテストで使用可能になる)
-
assert_selectを使うことで表示されているHTMLタグのチェックが出来る、'title', "Home | #{@base_title}"
-
setupという特別なメソッド(各テストが実行される直前で実行されるメソッド)を使ってテストで共有できるものを変数などに収納できる
-
push前の自動テストを癖付ける

4章
対話的操作のできるツール rails console
rails console
上記コマンドを使って、起動することが出来る。
ビルドなどをせずにrailsを動かすことが出来る、チュートリアルや簡単な基本文法の確認などはこれで行ってもいいかも。
application_helper.rb
app/helpers/application_helper.rb
上記のヘルパーモジュールは、自動的に読み込んでくれるので、include行を使って読み込ませなくても良い
メソッドの種類
# Rubyの公式ドキュメント「Rubyリファレンスマニュアル」の表記 . で表記
String.new … クラスメソッド
# Rubyの公式ドキュメント「Rubyリファレンスマニュアル」の表記 # で表記
"foobar".length … インスタンスメソッド

5章ふりかえり
ルートURLをconfig/routes.rb
で定義することで、以下のメソッドを使うことが出来るようになる
root_path -> '/'
root_url -> 'https://www.example.com/'
基本的には_path
でいいが完全なURLが求められる場面では_url
を使用する
名前付きルーティングの定義
// config/routes.rb
get "static_pages/help"
以下のように変換する
get "/help", to: "static_pages#help"
これにより以下のメソッドも使用可能となる
help_path -> '/help'
help_url -> 'https://www.example.com/help'
また、名前付きルーティングは、as:オプションを使って呼び方を変更できる
Rails.application.routes.draw do
root "static_pages#home"
get "/help", to: "static_pages#help", as: 'helf'
test 'should get help' do
get helf_path
assert_response :success
assert_select 'title', "Help | #{@base_title}"
end
これはメソッド名などの呼び方が変わるだけでpathはhelpのままとなる。
リンクが正しく動いているかどうかチェックするテストの作成
テストのテンプレートを作成
rails generate integration_test site_layout
invoke test_unit
create test/integration/site_layout_test.rb
テスト内容
- ルートURL(Homeページ)にGETリクエストを送る。
- 正しいページテンプレートが描画されているかどうか確かめる。
- Home、Help、About、Contactの各ページへのリンクが正しく動くか確かめる。

ユーザー登録
Usersコントローラ作成
rails generate controller Users new
ユーザーモデル作成
rails generate model User name:string email:string
上記コマンドを実行すると、以下のテーブル作成する関数(マイグレーション)も作られる
// table”の頭文字を取ってt
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps // created_atとupdated_atという2つの「マジックカラム(Magic Columns)を作成
end
end
end
マイグレーションは以下のコマンドで実行できる「マイグレーションの適用(migrating up)」と呼ぶ
rails db:migrate
ロールバックは以下のコマンドで実行できる
rails db:rollback
サンドボックスモードでコンソールを起動(ここで行ったすべての変更は終了時にロールバックされます)
rails console --sandbox
Loading development environment in sandbox
Any modifications you make will be rolled back on exit
データベースにUserオブジェクトを保存するには、userオブジェクトのsaveメソッドを呼び出す必要があります
user.save
インスタンス化したuserはドット記法を用いてその属性にアクセスすることができる。
user.name
=> "Michael Hartl"
user.email
=> "michael@example.com"
user.updated_at
=> Thu, 14 Dec 2023 16:40:33.159758000 UTC +00:00
上記のようにモデルの生成と保存を2つのステップに分けておくと何かと便利
.newを行った後、.save保存
human2 = User.new(name: "Yuji Tani", email: "tani@example.com")
=>
#<User:0x000000010fc85798
human2.save
TRANSACTION (0.1ms) SAVEPOINT active_record_1
User Create
INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)
[["name", "Yuji Tani"],
["email", "tani@example.com"],
["created_at", "2023-12-14 16:48:42.920696"],
["updated_at", "2023-12-14 16:48:42.920696"]]
TRANSACTION (0.0ms) RELEASE SAVEPOINT active_record_1=> true
この2ステップを一気にやるコマンドもある(user.create)
User.create(name: "A Nother", email: "another@example.org")
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2022-03-11 01:53:22", updated_at: "2022-03-11 01:53:22">
>> foo = User.create(name: "Foo", email: "foo@bar.com")
#<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2022-03-11
01:54:03", updated_at: "2022-03-11 01:54:03">
6.1.4ユーザーオブジェクトを検索する
検索色々
{変数名}.find(0) // インデックス検索
{変数名}.find_by(name: 'hoge') // プロパティで検索
{変数名}.all // 全部取得(Arrayオブジェクトではないので注意)
データ更新
{変数名}.[プロパティ名] = 値
// 変更後、保存が必要
{変数名}.save
{変数名}.update([プロパティ名: 値, n...])

Railsのオブジェクトの調べ方
継承クラスを調べる事で祖先を調べられます
irb(main):012> yuji.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
irb(main):013> yuji.class.superclass
=> ApplicationRecord(abstract)
irb(main):014> yuji.class.superclass.superclass
=> ActiveRecord::Base
irb(main):015> yuji.class.superclass.superclass.superclass
=> Object
irb(main):016> yuji.class.superclass.superclass.superclass.superclass
=> BasicObject
irb(main):017> yuji.class.superclass.superclass.superclass.superclass.superclass
=> nil

作ったUserモデルの検証
setupメソッド内に書かれた処理は、各テストが走る直前に実行される。
@userはsetupメソッド内で宣言しておけば、すべてのテスト内でこのインスタンス変数が使えるようになる。
valid?メソッド
を使うと、有効性を確認できる
オブジェクトが1つ以上のバリデーションに失敗したときはfalse
を返す
すべてのバリデーションがパスしたときはtrue
を返す
この段階でのテストは特に何も確認していないのでtrueとなる
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
end
存在性を検証する
assert_not … 結果がtrue
なら失敗false
なら成功となる
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
test "name should be present" do
@user.name = " "
assert_not @user.valid?
end
end
この段階のテストでは@user.name
の値が空白でも問題ないのでuser.valid?
はtrueを返し、テストは失敗します
テストが成功するように、User.nameの値が空でないことを検証するように変更
空でないなら、true
validates :title, presence: true
class User < ApplicationRecord
validates(:name, presence: true)
end
再度テスト
$ rails console --sandbox // 終了時にロールバックする、コンソールを起動
>> user = User.new(name: "", email: "michael@example.com")
>> user.valid?
=> false
失敗したときにはerrorsオブジェクト
が作られる
user.errors.full_messages
=> ["Name can't be blank"]
エラーを吐いているオブジェクトは保存が失敗する
user.save
=> false
長さの検証
name
とemail
の長さを検証するテストを作成する
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
.
.
.
test "name should not be too long" do
@user.name = "a" * 51
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 244 + "@example.com"
assert_not @user.valid?
end
end
最大文字数を設定
length: { maximum: (最大文字数) }
class User < ApplicationRecord
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 }
end
フォーマットを検証する
email
属性の場合 … 有効なメールアドレスかどうかを判定する
emailのフォーマット検証にはformat
というオプションを使う
validates :email, format: { with: /<regular expression>/ }
このオプションは引数に正規表現(Regular Expression)とれる
正規表現の確認や、練習には対話的に確認のできる
Rubularを使うといいみたい
一意性を検証する
メールアドレスの一意性を強制するために、validatesメソッドの:uniqueness
をオプションを使う
※ ただし重大な警告
があります
通常、メールアドレスでは大文字小文字が区別されないため
一意性を検証するテスト(大文字小文字を区別しない)
test "email addresses should be unique" do
duplicate_user = @user.dup // dupは、同じ属性を持つデータを複製するメソッド
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
問題
Active Recordはデータベースのレベルでは一意性を保証していないため、瞬間的に2つ登録されてしまったという場合、データベース内で重複して登録されてしまうという可能性がある
しかし、この問題はデータベースレベルでも一意性を強制するだけで解決する。
データベース上でemail
のカラムにインデックス(index)を追加し、そのインデックスが一意であるようにすれば解決する。
コラム 6.2.データベースのインデックス
データベースでカラム作成時、そのカラムでレコードを検索する(find)必要が生じるかどうかを考えることは重要。
ログイン認証時、入力されたものと一致するメールアドレスのユーザーのレコードをデータベースの中から探す必要がある。
インデックス機能のないデータベースの場合、1件1件のレコーダーを確認するしかない、最後の1件だったとしても、全件確認する(データベースの世界では、全表スキャン(Full-table Scan
)として知られており、極めて不都合とされる)
emailカラムにインデックスを追加すると、書籍の索引のように'文字列'などを含む索引だけを検索すれば良い
Railsでの対応方法
emailインデックスを追加するには、データモデリングの変更が必要。
Railsではマイグレーションでインデックスを追加する。
既に存在するモデルに構造を追加する場合、下記コマンドを実行する
User.emailカラムにindexを追加
rails generate migration add_index_to_users_email
ユーザー用のマイグレーションの時とは異なり、メールアドレスの一意性を追加するマイグレーションでは、自動で内容を記述してくれていないため、自分で書く必要があります。
db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[7.0]
def change
add_index :users, :email, unique: true
end
end
上記マイグレーションではadd_index
というRails
のメソッドを使ってusers
テーブルのemail
カラムにインデックスを追加している。
インデックス自体は一意性は強制しない。
オプションにunique: true
を指定するとインデックスを追加し、一意性を矯正することになる

6章続き
セキュアなパスワード
Railsでの、セキュアなパスワード実装はhas_secure_password
というRailsメソッド
を呼び出すだけで、ほぼ完了する
class User < ApplicationRecord
.
.
.
has_secure_password // ユーザークラスで呼び出す
end
モデルにこのメソッドを追加すると、以下が使えるようになる
- ハッシュ化したパスワードを、データベース内の
password_digest
属性に保存できるようになる。 -
password
とpassword_confirmation
が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。 -
authenticate
メソッドが使えるようになる(引数の文字列がパスワードと一致するとUserオブジェクトを返し、一致しない場合はfalseを返すメソッド)。
has_secure_password
機能を使えるようにするには、モデル内にpassword_digest
属性が含まれている必要がある
password_digest
カラムを作成するマイグレーションを生成する
※ 末尾をto_users
にすると、usersテーブルにカラムを追加するマイグレーションが自動的に作成される
rails generate migration add_password_digest_to_users password_digest:string
上記を実行すると、下記が作成される
invoke active_record
create db/migrate/20231219132847_add_password_digest_to_users.rb
# db/migrate/20231219132847_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :password_digest, :string
end
end
マイグレーションを実行
rails db:migrate
必要なライブラリをインストールする
has_secure_password
を使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcrypt
ライブラリが必要となる
Gemfileに追記
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.2.2"
gem "rails", "7.0.4.3"
gem "bcrypt", "3.1.18" # ← new!
では、ついにhas_secure_password
をUserクラスに追加してみます!
class User < ApplicationRecord
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: true
has_secure_password
end
テストを実行する
rails test
テストは失敗する
失敗する理由はhas_secure_password
は、password
属性とpassword_confirmation
属性に対してバリデーションをする機能も(強制的に)追加されているから
しかし、テスト上にこの値が存在しないので失敗してしまう
テストのsetupに追記
password
属性とpassword_confirmation
属性を追加
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
パスワードの最小文字数設定
パスワードが空でないこと、最小文字数(6文字)以上であることの2つを設定する。
パスワードが空白じゃないか、長さは6文字以上かを検証するテスト
require "test_helper"
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "password should be present (nonblank)" do
# パスワードとパスワード確認に対し、同時に代入を行う書き方
@user.password = @user.password_confirmation = " " * 6
assert_not @user.valid?
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end
end

7章 ユーザー登録
Railsの3つの環境について
Railsは3つの環境がある
- テスト環境(test)
- 開発環境(development)
- 本番環境(production)
Rails consoleは、デフォルトでdevelopmentになる。
$ rails console
Loading development environment
>> Rails.env
=> "development"
>> Rails.env.development?
=> true
>> Rails.env.test?
=> false
テスト環境のデバッグなど、他の環境でconsoleを実行する必要が生じた場合、下記のようにオプションを指定できる
$ rails console --environment test
Loading test environment
>> Rails.env
=> "test"
>> Rails.env.test?
=> true
Railsサーバーもデフォルトはdevelopment
が使われる
# 本番環境でサーバーを立てる
$ rails server --environment production
マイグレーションを本番環境で実行して、本番データベースを作成する
$ rails db:migrate RAILS_ENV=production
Renderは本番サイト用のプラットフォームなので、実行されるアプリケーションはすべて本番環境となります。
下記の1行を追加すると、ユーザーのURLを生成するための多数の名前付きルーティング(5.3.3)と共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる
Rails.application.routes.draw do
root "static_pages#home"
get "/help", to: "static_pages#help"
get "/about", to: "static_pages#about"
get "/contact", to: "static_pages#contact"
get "/signup", to: "users#new"
resources :users
end
登録したUser[1]を表示できるようにする
ルーティングを追加したので、ユーザーのビューとアクションを定義します。
ビュー
// app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>
アクション
// app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
デバッグメソッド
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger
end
def new
end
end
が開きその時点の変数やparamsなどの値を検証することが出来る
サーバーのプロセスが落とせなくなったとき
下記でプロセスをキルできる
lsof -i :3000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ruby 48455 user 10u IPv4 0x4495ffb590b11a85 0t0 TCP localhost:hbci (LISTEN)
ruby 48455 user 11u IPv6 0x4495ffb592eeeaf5 0t0 TCP localhost:hbci (LISTEN)
ruby 48455 user 18u IPv4 0x4495ffb590110b55 0t0 TCP localhost:hbci->localhost:55821 (CLOSED)
kill -9 48455 48455 48455
日が空いたので、復習と図解を書いていた
めもは最後に行って、それまでアナログにまとめるほうが頭に入りそう
7.3.4失敗時のテスト

Active Recordについて
Active Recordとは、MVCで言うところのM、つまりモデルに相当するもの
O/Rマッピング(オブジェクト/リレーショナルマッピングとは
アプリケーションが持つオブジェクトをリレーショナルデータベース(RDBMS)のテーブルに接続すること
ORMを用いると、SQL文を直接書く代わりにわずかなアクセスコードを書くだけで、SQLと同じようなことが出来るようになる
ORMフレームワークとしてのActive Record
Active Recordにはさまざまな機能が搭載されており、その中でも以下のものが特に重要です。
- モデルおよびモデル内のデータを表現する
- モデル同士の関連付け(アソシエーション)を表現する
- 関連付けられているモデル間の継承階層を表現する
- データをデータベースで永続化する前にバリデーション(検証)を行なう
- データベースをオブジェクト指向スタイルで操作する
Active Recordの命名ルール
ORM作成時に従うべきルールがいくつか存在する
- モデルのクラス - 単数形、アッパーキャメルケースで書く
- データベースのテーブル - (モデルの)複数形にする、スネークケースで書く
スキーマのルール
テーブルで使うカラム名にも利用目的に応じたルールがある
- 外部キー(foreign key): テーブル名の単数形_idにする必要があります(例: item_id、order_id)
- 主キー(primary key): デフォルトでは id という名前のintegerカラムがテーブルの主キーに使われる、自動的に作成される。
その他
- created_at: レコード作成時に現在の日付時刻が自動的に設定される
- updated_at: レコード作成時や更新時に現在の日付時刻が自動的に設定る
- lock_version: モデルにoptimistic lockingを追加する
- type: モデルでSingle Table Inheritanceを使う場合に指定する
- 関連付け名_type: ポリモーフィック関連付けの種類を保存する
- テーブル名_count: 関連付けた、オブジェクトの数をキャッシュするのに使う。
Articleクラスにcomments_countというカラムがある場合、そこにCommentが多数あると、ポストごとにコメント数が追加される
Active Recordモデル作成
非常に簡単で、以下のようにApplicationRecordクラスのサブクラスを作成するだけで完了する
class Product < ApplicationRecord
end
テーブルの作成はマイグレーションなどで行う
他にもテーブル名の指定や、CRUD操作などの方法は、下記リンクで確認すること