Rails チュートリアル復習用メモ

以下を参考にした、Rails アプリを作成する手順の覚書です。

モデルとコントローラーの基本的な部分を知ることが目的だったので、モデルの応用的な部分や View 周りはできるだけ省いたアプリケーションに仕上がっています。具体的には 4, 11, 12, 14 章をスキップし、それ以外の各章もスタイルやインテグレーションテストについては省いています。(発展的なログイン機構、アカウント有効化のメール送信、パスワード再設定、ユーザーのフォローなどが機能として未実装。)

新規アプリ作成

rails _5.1.4_ new first_app
rails server
# => http://localhost:3000/

Gemfile 更新

feat: upadte Gemfile · ryotah/rails-tutorial@6336633

bundle install --without production

モデルを作成

User モデルと Micropost モデルを作成します。

User モデルを作成

rails generate model User name:string email:string password_digest:string
# => app/models/user.rb
# => db/migrate/xxx_create_users.rb
# => test/fixtures/user.yml
# => test/models/user_test.rb

rails db:migrate
# => db/schema.rb
# Rails 4以前
# bundle exec rake db:migrate

すでに存在するモデルにインデックスを追加。

rails generate migration add_index_to_users_email

生成されたdb/migrate/[timestamp]_add_index_to_users_email.rbを更新してから migrate を実行。

class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    # 以下の行を追加
    #
    # unique: true => 一意性を強制
    add_index :users, :email, unique: true
  end
end

User モデルに validates, has_secure_password, has_many :microposts, dependent: :destroy などを追加。

class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  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: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
end

(メモ)rails console からユーザーを追加

user = User.create(name: "Michael Hartl", email: "mhartl@example.com", password: "foobar", password_confirmation: "foobar")

user.authenticate("foobaz")
# => false
!!user.authenticate("foobar")
# => true

Micropost モデル

rails generate model Micropost content:text user:references

生成されたdb/migrate/[timestamp]_create_microposts.rbを更新。

class CreateMicroposts < ActiveRecord::Migration[5.1]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    # 以下の行を追加
    #
    # インデックスを追加して、user_id に関連付けられたマイクロポストを
    # 作成時刻の逆順で取り出しやすくする
    add_index :microposts, [:user_id, :created_at]
  end
end

Micropost モデルに validates, default_scope を追加。(belongs_to :user は最初から用意されている。)

class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

テスト

# 全テストを実行
rails test

# modelsのみテストを実行
rails test:models

# 特定ファイルのテストを実行
rails test test/models/micropost_test.rb

コントローラー作成

必要なControllerを作成。

rails generate controller StaticPages home
rails generate controller Users new
rails generate controller Sessions new
rails generate controller Microposts

ルーティングを設定

Rails.application.routes.draw do
  # root_path
  root 'static_pages#home'

  # 名前付きルートを定義
  get '/signup', to: 'users#new'
  post '/signup', to: 'users#create'
  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'

  resources :users
  resources :microposts, only: [:create, :destroy]
end

セッションヘルパー

  • 以下 5 つを定義
    • ログイン
    • セッション情報から現在のユーザー(ログインユーザ)を取得
    • ログインユーザーか確認
    • ログイン済みか確認
    • ログアウト
module SessionsHelper
  def log_in(user)
    session[:user_id] = user.id
  end

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def current_user?(user)
    user == current_user
  end

  def logged_in?
    !current_user.nil?
  end

  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

ApplicationControllerincludeする。(各ビューとコントローラーで利用できるようになる)

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

サンプルデータを追加

https://github.com/ryotah/rails-tutorial/blob/b27827169748425dbff08188d97c366eb73ec1fb/db/seeds.rb

rails db:seed

ビューとコントローラーを調整

ヘッダー

(メモ)デバッグ

<%= debug(params) if Rails.env.development? %>

ユーザー一覧

views/users/index.html.erbを追加。

<h1>Users#index</h1>
<%= render @users %>

views/users/_user.html.erbを追加。(render @usersに対応するパーシャル)

<div><%= user.name %>, <%= user.email %></div>

コントローラーにindexを追加。

class UsersController < ApplicationController
  # ...
  def index
    @users = User.all
  end
  # ...
end

ログイン

views/sessions/new.html.erbにフォームを追加。

<%= form_for(:session, url: login_path) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email, class: 'form-control' %>
  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>
  <%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>

<p>New user? <%= link_to "Sign up now!", signup_path %></p>

コントローラーにcreateを追加。

def create
  user = User.find_by(email: params[:session][:email])
  if user && user.authenticate(params[:session][:password])
    log_in user
    redirect_to user
  else
    flash.now[:danger] = 'Invalid email/password combination'
    render 'new'
  end
end

(メモ)Flash

<% flash.each do |message_type, message| %>
  <div><%= message_type %> | <%= message %></div>
<% end %>

ユーザー作成

<%= form_for(@user, url: signup_path) do |f| %>
<%= render 'shared/error_messages', object: f>
# ...

ユーザー詳細

ホーム(マイクロポスト作成)

  • feat: post a micropost · ryotah/rails-tutorial@98a775c
    • ログイン済みユーザーかどうか確認するlogged_in_userApplicationControllerに追加
    • MicropostsControllercreateを実装
      • before_action :logged_in_user, only: :create
    • app/views/static_pages/home.html.erbにマイクロポスト用のフォームを追加
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>  <p>Find me in app/views/static_pages/home.html.erb</p>
<% if logged_in? %>
  <%= current_user.name %>, <%= current_user.email %>
  <%= form_for(@micropost) do |f| %>
    <%= render 'shared/error_messages', object: f.object %>
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    <%= f.submit "Post", class: "btn btn-primary" %>
  <% end %>
<% end %>

元記事のメモ/リンク

@user = users(:michael)
# このコードは慣習的に正しくない
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)

Rails ガイドより

users = User.all
user = User.first
david = User.find_by(name: 'David')