9a. ProgateでRuby on RailsⅠ~Ⅴ(備忘録)

Progateで学習した基本的なコマンド等をおさらいしました。
ここでは、投稿機能の実装をしています。

Ruby on Rails5 学習コースⅠ

・Railsアプリケーションの作成

$ rails new tweet_app							# Railsアプリケーションの作成
$ rails server									# サーバ起動

$ rails g controller home top					# homeコントローラの作成

# 上記コマンドで下記3つのファイルが作成される
tweet_app/config/routes.rb						# routing設定ファイル
tweet_app/app/controllers/home_controller.rb	# controllerファイル
tweet_app/app/views/home/top.html.erb			# viewファイル
tweet_app/app/assets/stylesheets/home.scss		# CSSファイル

# 画像は下記ディレクトリ配下に格納する
tweet_app/public/

# 記述方法
# viewファイル(views/home/about.html.erb)
<img src="/tweets.png">

# CSSファイル(assets/stylesheets/home.scss)
background-image: url("/top.jpg");

・コントローラの削除

$ rails destroy controller home			# 誤って作成した場合、このコマンドで削除できる

・完成版

# (routes.rb)
  get "/" => "home#top"
  get "about" => "home#about"

# (home_controller.rb)
  def top
  end

  def about
  end


Ruby on Rails5 学習コースⅡ

変数の定義: erbという形式のファイルで、<% %> で囲む
変数の値の表示: <%= %> で囲む

(posts/index.html.erb)
<% post1 = “今日からProgateでRails” %>
<%= post1 %>

※ erb とは Embedded Ruby(埋め込みRuby) の略

・マイグレーションファイルの作成

$ rails g model Post content:text	# Postモデルとpostsテーブルを作成するマイグレーションファイルの作成(text型のcontentカラム)
$ rails db:migrate					# データベースに変更を反映(エラーが発生するので必ず実行する)

※ 「rails g model Post ...」で以下のファイルが作成される
tweet_app/app/models/post.rb							# モデルが定義されたファイル
tweet_app/db/migrate/2018MMDDHHMMSS_create_posts.rb		# マイグレーションファイル

・コンソールの起動

$ rails console			# コンソールの起動
> text = "Hello"			# 変数に文字列を代入
> text					# 変数の表示
"Hello"
> post = Post.new(content: "Hello world")	# Postインスタンスの作成
> post.save									# Postインスタンスをテーブルに保存
> post = Post.first							# postsテーブルの最初のデータをpost変数に代入
> post.content								# 投稿内容(content)のみ出力
"Hello world"
> posts = Post.all							# テーブルにある全てのデータを配列で取得
> Post.all[0].content						# 最初のデータの投稿内容を出力
"Hello world"
> quit

・全ての投稿を表示する

# 配列(viewで使う変数はコントローラーのアクション内で定義)
# (posts_controller.rb)
def index
  @posts = Post.all
end

# each文で表示
# (posts/index.html.erb)
<% @posts.each do |post| %>
  <%= post.content %>
<% end %>

・「views/layouts/application.html.erb」に共通のHTMLを書いておくことができる

・リンクの追加

# (layouts/application.html.erb)
<%= link_to("About", "/about") %>

# 以下のaタグに変換される
# (application.html)
<a href="/about">About</a>

・完成版

# (2018MMDDHHMMHH_create_posts.rb)
  def change
    create_table :posts do |t|
      t.text :content

      t.timestamps
    end
  end

# (routes.rb)
  get "posts/index" => "posts#index"			# 追加

  get "/" => "home#top"
  get "about" => "home#about"

# (posts_controller.rb)
  def index
    @posts = Post.all
  end

# (layouts/application.html.erb)
    <header>
      <div class="header-logo">
        <%= link_to("TweetApp", "/") %>
      </div>
      <ul class="header-menus">
        <li><%= link_to("TweetAppとは", "/about") %></li>
        <li><%= link_to("投稿一覧", "/posts/index") %></li>
      </ul>
    </header>

# (posts/index.html.erb)
    <% @posts.each do |post| %>
      <div class="posts-index-item">
        <%= post.content %>
      </div>
    <% end %>


・確認すること

  • /(トップページ)が表示される
  • ヘッダーメニューの「TweetAppとは」と「投稿一覧」にリンクが張られていて、画面遷移できる
  • 投稿一覧に複数の投稿が表示できる


Ruby on Rails5 学習コースⅢ

・投稿詳細ページの作成

# (routes.rb)
get "posts/:id" => "posts#show"			# 「/posts/1」でも「/posts/2」でもshowアクションに遷移

# (posts_controller.rb)
def show
  @post = Post.find_by(id: params[:id])	# idカラムがparams[:id]である投稿データを取得
end

# (posts/show.html.erb)
<%= @post.content %>
<%= @post.created_at %>

・投稿詳細ページへのリンクを作成

# (posts/index.html.erb)
<% @posts.each do |post| %>
  <%= link_to(post.content, "/posts/#{post.id}") %>	# 変数展開を用いて投稿のidを指定
<% end %>

・投稿内容を受け取るアクションを用意

# (posts/new.html.erb)
<%= form_tag("/posts/create") do %>		# form_tagメソッドを用いてフォームに入力されたデータを送信
  <textarea name="content"></textarea>		# name属性を指定することで入力データを送信できる
  <input type="submit" value="投稿">		# 投稿ボタン
<% end %>

# (routes.rb)
post "posts/create" => "posts#create"		# フォームの値を受け取る時は「post」を使う

# (posts_controller.rb)
def create
  @post = Post.new(content: params[:content])	# contentが入力データであるインスタンスを作成
  @post.save
  redirect_to("/posts/index")					# DBに保存後にリダイレクト
end

・特定のidの投稿を取得するためには、find_byメソッドを使用

$ rails c
> post = Post.find_by(id: 3)
> post.content
"Rails勉強中!"

・他のURLに転送(リダイレクト)するには、redirect_toメソッドを使用

# (posts_controller.rb)
def create
  redirect_to("/posts/index")
end

orderメソッドを用いることで、投稿一覧を並び替えることができる

# (posts_controller.rb)
def index
  @posts = Post.all.order(created_at: :desc)
end

・完成版

# (routes.rb)
  get "posts/index" => "posts#index"
  get "posts/new" => "posts#new"				# 追加
  get "posts/:id" => "posts#show"			# 追加
  post "posts/create" => "posts#create"		# 追加
  
  get "/" => "home#top"
  get "about" => "home#about"

# (posts_controller.rb)
  def index
    @posts = Post.all.order(created_at: :desc)	# orderメソッドを使用
  end
  
  def show
    @post = Post.find_by(id: params[:id])
  end
  
  def new
  end
  
  def create
    @post = Post.new(content: params[:content])
    @post.save
    redirect_to("/posts/index")
  end

# (layouts/application.html.erb)
    <header>
      <div class="header-logo">
        <%= link_to("TweetApp", "/") %>
      </div>
      <ul class="header-menus">
        <li><%= link_to("TweetAppとは", "/about") %></li>
        <li><%= link_to("投稿一覧", "/posts/index") %></li>
        <li><%= link_to("新規投稿", "/posts/new") %></li>		# 追加
      </ul>
    </header>

# (posts/index.html.erb)
    <% @posts.each do |post| %>
      <div class="posts-index-item">
        <%= link_to(post.content, "/posts/#{post.id}") %>	# 変数展開を用いて投稿のidを指定
      </div>
    <% end %>

# (posts/show.html.erb)
        <%= @post.content %>
        <%= @post.created_at %>

# (posts/new.html.erb)
    <%= form_tag("/posts/create") do %>
      <div class="form">
        <div class="form-body">
          <textarea name="content"></textarea>
          <input type="submit" value="投稿">
        </div>
      </div>
    <% end %>


・確認すること

  • 投稿一覧ページにて、新規の投稿から表示できている
  • 投稿一覧ページの投稿内容のリンクから、投稿詳細ページへ画面遷移できる
  • ヘッダーメニューの新規投稿リンクから、新規投稿ページへ画面遷移できる
  • 新規投稿の内容が保存され、投稿一覧ページへリダイレクトされる


Ruby on Rails5 学習コースⅣ

・投稿を編集する

$ rails c
> post = Post.find_by(id: 1)		# DBから編集したい投稿を取得
> post.content = "Rails"			# contentの値を上書き
> post.destroy					# destroyメソッドを使用して投稿を削除
> post.save						# DBを保存(忘れがち!)

・編集ボタンの設置

# (posts/show.html.erb)
<%= link_to("編集", "/posts/#{@post.id}/edit") %>	# editアクションのURLを指定

# (routes.rb)
get "posts/:id/edit" => "posts#edit"

・投稿内容をフォームの初期値にする

# (posts_controller.rb)
def edit
  @post = Post.find_by(id: params[:id])
end

# (posts/edit.html.erb)
<textarea><%= @post.content %></textarea>

・編集機能を実装

# (posts/edit.html.erb)
<%= form_tag("/posts/#{@post.id}/update") do %>	# form_tagで送信先のURLを指定
  <textarea name="content"><%= @post.content %></textarea>
  <input type="submit" value="保存">
<% end %>

# (routes.rb)
post "posts/:id/update" => "posts#update"	# 値を受け取るのでpostにする

# (posts_controller.rb)
def update
  @post = Post.find_by(id: params[:id])
  @post.content = params[:content]			# フォームの値を受け取ってDBへ代入
  @post.save
  redirect_to("/posts/index")				# 投稿一覧ページへリダイレクト
end

・削除機能を実装

# (posts/show.html.erb)
# 「{method: "post"}」を追加することで、postで書かれているルーティングを探してくれる
<%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %>

# (routes.rb)
post "posts/:id/destroy" => "posts#destroy"	# DBを変更するのでpostを使用

# (posts_controller.rb)
def destroy
  @post = Post.find_by(id: params[:id])
  @post.destroy
  redirect_to("/posts/index")				# 投稿一覧ページへリダイレクト
end

・完成版

# (routes.rb)
  get "posts/index" => "posts#index"
  get "posts/new" => "posts#new"
  get "posts/:id" => "posts#show"
  post "posts/create" => "posts#create"
  get "posts/:id/edit" => "posts#edit"			# 追加
  post "posts/:id/update" => "posts#update"		# 追加
  post "posts/:id/destroy" => "posts#destroy"	# 追加
  
  get "/" => "home#top"
  get "about" => "home#about"

# (posts_controller.rb)
  def index
    @posts = Post.all.order(created_at: :desc)
  end
  
  def show
    @post = Post.find_by(id: params[:id])
  end
  
  def new
  end
  
  def create
    @post = Post.new(content: params[:content])
    @post.save
    redirect_to("/posts/index")
  end

  # ここから追加
  def edit
    @post = Post.find_by(id: params[:id])
  end
  
  def update
    @post = Post.find_by(id: params[:id])
    @post.content = params[:content]
    @post.save
    redirect_to("/posts/index")
  end
  
  def destroy
    @post = Post.find_by(id: params[:id])
    @post.destroy
    redirect_to("/posts/index")
  end

# (posts/show.html.erb)
        <%= @post.content %>
        <%= @post.created_at %>
        <%= link_to("編集", "/posts/#{@post.id}/edit") %>						# 追加
        <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %>	# 追加

# (posts/edit.html.erb)
    <%= form_tag("/posts/#{@post.id}/update") do %>
      <div class="form">
        <div class="form-body">
          <textarea name="content"><%= @post.content %></textarea>
          <input type="submit" value="保存">
        </div>
      </div>
    <% end %>


・確認すること

  • 編集ボタンが設置されていて、投稿を編集でき、投稿内容がフォームの初期値になっている
  • 削除ボタンが設置されていて、投稿を削除でき、投稿一覧へリダイレクトされる


Ruby on Rails5 学習コースⅤ

・バリデーションの実装

# (models/post.rb)
  validates :content, {presence: true, length: {maximum: 140}}			# 空の投稿を防ぎ、最大文字数を140文字に設定

・エラーメッセージの表示

# (posts_controller.rb)
def update
  @post = Post.find_by(id: params[:id])
  @post.content = params[:content]
  if @post.save								# 投稿の編集に成功した場合
    redirect_to("/posts/index")
  else										# 投稿の編集に失敗した場合
    render("posts/edit")					# editアクションを経由せずにedit.html.erbを表示
  end
end

# (posts/edit.html.erb)
<% @post.errors.full_messages.each do |message| %>
  <%= message %>
<% end %>

・サクセスメッセージの表示

# 変数flashは1度表示された後に自動で削除される
# (posts_controller.rb)
def update
  if @post.save
    flash[:notice] = "投稿を編集しました"
      :
end

# (layouts/application.html.erb)
<% if flash[:notice] %>
  <%= flash[:notice] %>		# フラッシュメッセージが存在する場合のみ、表示する
<% end %>

・完成版

# (models/port.rb)
  validates :content, {presence: true, length: {maximum: 140}}

# (posts_controller.rb)
  def index
    @posts = Post.all.order(created_at: :desc)
  end
  
  def show
    @post = Post.find_by(id: params[:id])
  end
  
  def new
    @post = Post.new							# 追記
  end
  
  def create
    @post = Post.new(content: params[:content])
      if @post.save
      flash[:notice] = "投稿を作成しました"		# 変数flash[:notice]を定義
      redirect_to("/posts/index")
    else
      render("posts/new")
    end
  end

  def edit
    @post = Post.find_by(id: params[:id])
  end
  
  def update
    @post = Post.find_by(id: params[:id])
    @post.content = params[:content]
    if @post.save								# UPDATEの成否で条件分岐
      flash[:notice] = "投稿を編集しました"		# 変数flash[:notice]を定義
      redirect_to("/posts/index")
    else
      render("posts/edit")
    end
  end
  
  def destroy
    @post = Post.find_by(id: params[:id])
    @post.destroy
    flash[:notice] = "投稿を削除しました"		# 変数flash[:notice]を定義
    redirect_to("/posts/index")
  end

# (layouts/application.html.erb)
    <% if flash[:notice] %>
      <div class="flash">
        <%= flash[:notice] %>					# フラッシュメッセージが存在する場合のみ、表示する
      </div>
    <% end %>

# (posts/edit.html.erb)
    <%= form_tag("/posts/#{@post.id}/update") do %>
      <div class="form">
        <div class="form-body">
          <% @post.errors.full_messages.each do |message| %>
            <div class="form-error">
              <%= message %>					# エラーメッセージを出力
            </div>
          <% end %>

          <textarea name="content"><%= @post.content %></textarea>
          <input type="submit" value="保存">
        </div>
      </div>
    <% end %>

# (posts/new.html.erb)
    <%= form_tag("/posts/create") do %>
      <div class="form">
        <div class="form-body">
          <% @post.errors.full_messages.each do |message| %>
            <div class="form-error">
              <%= message %>
            </div>
          <% end %>
          
          <textarea name="content"><%= @post.content %></textarea>
          <input type="submit" value="投稿">
        </div>
      </div>
    <% end %>


・確認すること

  • 新規投稿に成功した場合、サクセスメッセージが表示される
  • 新規投稿に失敗した場合(空の投稿、文字数が140文字より大きい場合)、エラーメッセージが表示され、新規投稿ページに戻る
  • 投稿の編集に成功した場合、サクセスメッセージが表示される
  • 投稿の編集に失敗した場合(空の投稿、文字数が140文字より大きい場合)、エラーメッセージが表示され、編集ページに戻る
  • 投稿を削除した際、サクセスメッセージが表示される