LoginSignup
9
8

More than 3 years have passed since last update.

Ruby on RailsでSNSを作る

Posted at

はじめに

Ruby on Rails理解しなからSNSのWebアプリを作成していく備忘録です。
開発環境はMacbookで、Railsは既にインストールされた状態からはじめます。
※本記事は記述途中です

Railsアプリの準備

まずはじめにRailsのワークディレクトリで、下のコマンドを打ち雛形の作成をすすめる。

  1. railsプロジェクトの作成
  2. 起動
  3. トップページの作成
$ rails new test_app
$ rails server
$ rails generate controller home top

ブラウザからそれぞれアクセスしてみる。

localhost:3000
localhost:3000/home/top

$ rails generateによって、Webアプリに必要な以下のコンポーネントが作成される。
1. View
2. Controller
3. Routing

View

Viewは文字通り見た目部分のHTMLファイル。
自動で作成されたビューは、test_app/app/views/home/top.html.erbにある。
(「erb」とは「Embedded Ruby(埋め込みRuby)」の略) *後述
このtop.html.erbを適当なHTMLに書き換えるとブラウザから見るページも変更される。

top.html.erb
<div class="main top-main">
  <div class="top-message">
    <h2>ほげほげ</h2>
    <p>ふがふが</p>
  </div>
</div>

Controller

ブラウザでページが表示されるときは、このコントローラを経由してビューをブラウザに返す。
コントローラファイルの場所: test_app/app/controllers/home_controller.rb
topメソッドが定義されてあるが、コントローラ内のメソッドをアクションと呼び、ブラウザに返すビューを`views
フォルダから探し出す役目を持つ。
アクションは、コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探す。

home_controller.rb
class HomeController < ApplicationController
  def top
  end
end

新しいコントローラの追加

新しいページ(例えば、トップページの他に投稿ページなど)を作るときは、新しいコントローラを追加する。
(homeコントローラでも作成可能だが、投稿ページはhomeという訳ではないので、投稿に関する事は投稿用のコントローラを作成する)

"g"は、"generate"の省略
投稿一覧ページを作るため、コントローラ名は"posts"、アクション名は一覧なので"index"とする。

$ rails g controller posts index

これによって、以下が追加される。
1. View (app/views/posts/index.html.erb + app/assets/stylesheets/posts.scss)
2. Controller (app/controllers/posts_controller.rb)
3. Routing (app/config/router.rb -> get "posts/index" => "posts#index"が追加される)

Routing

ルーティングは、ブラウザとコントローラを繋ぐ役目を持つ。
ページが表示されるまでの一連の動きは、ルーティング->コントローラ->ビューというふうになる。

ルーティングは、送信さえたURLに対してどのコントローラのどのアクションで処理するのかを決める対応表である。
ブラウザに入力されたURLは、ルーティングによって適切なコントローラ、アクションが呼び出される。

localhost:3000/home/app
Railsサーバー/コントローラ/アクション

ルーティングはtest_app/app/config/routers.rbに作成される。

routers.rb
Rails.application.routes.draw do
  get 'home/top'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

ルーティングを変更したい場合、例えばlocalhost:3000/home/topからlocalhost/topに変えるときは以下のように変更。

routers.rb
Rails.application.routes.draw do
  get 'top' => "home#top"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

URLの変更

localhost:3000でアクセスすると、railsのHwlloWorld的なページが出力されるが、これを本来のトップページに変更したい場合

routers.rb
Rails.application.routes.draw do
  get '/' => "home#top"
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

ページの追加

トップページ以外のなにかしらのページを追加したい場合など、以下のようにする。

  • ビューを追加 適当にabout.html.erbという名前でHTMLファイルを作成し、home配下に置く

app/views/home/about.html.erb

  • コントローラファイルにアクション(about)を追加
controllers/home_controller.rb
class HomeController < ApplicationController
  def top
  end

  def about
  end

end
  • アクション(about)のルーティングを追加
app/config/routers.rb

Rails.application.routes.draw do
  get "top" => "home#top"

  get "about" => "home#about"

end

新しいviewファイルabout.html.erbをapp/home/配下に作成

about.html.erb
<div class="about-main">
  <h2>ほげほげ</h2>
  <p>
    ほげほげサービスです。
    ふがふが。
  </p>
  <img class="about-img" src="/tweets.png">
</div>

これでlocalhost:3000/aboutにブラウザアクセスすると、Viewに追加したHTMLが表示される。

レイアウト(CSS)

CSSファイルはapp/assets/stylesheets/home.scssに生成されている。
stylesheets配下に書いたCSSは全てのビューに適用される。

home.scss
/* reset ================================ */
* {
  box-sizing: border-box;
}

html {
  font: 100%/1.5 'Avenir Next', 'Hiragino Sans', sans-serif;
  line-height: 1.7;
  letter-spacing: 1px;
}

ul, li {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

a {
  text-decoration: none;
  color: #2d3133;
  font-size: 14px;
}

h1, h2, h3, h4, h5, h6, p {
  margin: 0;
}

input {
  background-color: transparent;
  outline-width: 0;
}

form input[type="submit"] {
  border: none;
  cursor: pointer;
}

/* 共通レイアウト ================================ */
body {
  color: #2d3133;
  background-color: #3ecdc6;
  margin: 0;
  min-height: 1vh;
}

.main {
  position: absolute;
  top: 64px;
  width: 100%;
  height: auto;
  min-height: 100%;
  background-color: #f5f8fa;
}

.container {
  max-width: 600px;
  margin: 60px auto;
  padding-left: 15px;
  padding-right: 15px;
  clear: both;
}

/* ヘッダー ================================ */
header {
  height: 64px;
  position: absolute;
  z-index: 1;
  width: 100%;
}

.header-logo {
  float: left;
  padding-left: 20px;
  color: white;
  font-size: 22px;
  line-height: 64px;
}

.header-logo a{
  color: white;
  font-size: 22px;
}

.header-menus {
  float: right;
  padding-right: 20px;
}

.header-menus li {
  float: left;
  line-height: 64px;
  font-size: 13px;
  color: white;
  padding-left: 15px;
}

.header-menus a {
  float: left;
  font-size: 13px;
  color: white;
}

.header-menus .fa {
  padding-right: 5px;
}

.header-menus input[type="submit"] {
  padding: 0 20px;
  float: left;
  line-height: 64px;
  color: white;
  margin: 0;
  font-size: 13px;
}

/* top ================================ */
.top-main {
  padding: 200px 0 100px;
  text-align: center;
  position: absolute;
  top: 0;
  width: 100%;
  height: auto;
  min-height: 100%;
  color: white;
  background-color: #3ecdc6;
  background-repeat: no-repeat;
  background-position: center 50%;
  background-size: cover;
  background-image: url("/top.jpg");
}

.top-message {
  position: relative;
}

.top-main h2 {
  font-size: 70px;
  font-weight: 500;
  line-height: 1.3;
  -webkit-font-smoothing: antialiased;
  margin-bottom: 20px;
}

.top-main p {
  font-size: 24px;
}

/* about ================================ */
.about-main {
  padding: 180px 8% 0;
  color: white;
}

.about-main h2 {
  font-size: 64px;
  font-weight: 500;
  line-height: 1.4;
}

.about-main p {
  font-weight: 200;
  font-size: 20px;
}

.about-img {
  width: 84%;
}

/* フォーム================================ */
.form {
  max-width: 600px;
  margin: 0 auto;
  background-color: white;
  box-shadow: 0 2px 6px #c1ced7;
}

.form-heading {
  font-weight: 300;
  margin: 60px 0 20px;
  font-size: 48px;
  color: #bcc8d4;
}

.form-body {
  padding: 30px;
}

.form-error {
  color: #ff4d75;
}

.form input {
  width: 100%;
  border: 1px solid #d8dadf;
  padding: 10px;
  color: #57575f;
  font-size: 16px;
  letter-spacing: 2px;
  border-radius: 2px;
}

.form textarea {
  width: 100%;
  min-height: 110px;
  font-size: 16px;
  letter-spacing: 2px;
}

.form input[type="submit"] {
  background-color: #3ecdc6;
  color: white;
  cursor: pointer;
  font-weight: 300;
  width: 120px;
  border-radius: 2px;
  margin-top: 8px;
  margin-bottom: 0;
  float: right;
}

.form-body:after {
  content: '';
  display: table;
  clear: both;
}

/* フラッシュ ================================ */
.flash {
  padding: 10px 0;
  color: white;
  background: rgb(251, 170, 88);
  text-align: center;
  position: absolute;
  top: 64px;
  z-index: 10;
  width: 100%;
  border-radius: 0 0 2px 2px;
  font-size: 14px;
}

画像の表示

画像ファイルは、test_app/publicに配置することでフルパスを指定しなくても、画像ファイルのファイル名のみで参照可能。

example.html
<img src="/画像名" >
example.css
background-image: url("/画像名");

リンク

HTMLの中でトップページや他ページへのリンクをつけたい場合。
Railsの中であればルーティングのURL部分と同じような指定で書ける。

example.html
<a href="/">Test App</a>
<a href="/about">Test App</a>

erbファイルについて

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

変数の定義

<% %>で囲むことでhtml.erbファイル内でRubyのコーディングと同じように変数も定義可能。

example.html.erb
<% post1 = "テストです" %>

変数を表示するには<%= %>とする。

example.html.erb
<%= post1 %>
# 「テストです」を表示

配列

example.html.erb
<%
  posts = [
    "テストです",      <---コンマを忘れない
    "テスト2です"
  ]
%>

each文

普通に書く(こんな感じ)
<% posts.each do |post| %>
<% end %>も忘れず。

example.html
<div class="main posts-index">
  <div class="container">

    <% posts.each do |post| %>

      <div class="posts-index-item">

        <%= post %>

      </div>

    <% end %>

コントローラのアクションでの変数定義

一般的には変数はViewファイルではなくアクションで定義する。
変数名の前に"@"をつける事でここで定義した変数が、Viewファイルでも使用できる。
Viewファイルから呼び出すときも"@"をつける(<%= @post %>

example.rb
def index
  @posts = [
    "テストです",
    "テスト2です"
  ]
end

Rails Console

ターミナルからインタラクティブにRubyコードを実行可能。
終了はquit

$ rails console
$ quit

データベース

テーブル作成の準備 - マイグレーションファイルの作成

マイグレーションファイルとは、データベースへの変更を指示するファイルである。
"posts"テーブルを作成する想定で、以下のコマンド。

$ rails g model Post content:text

Post = モデル名 ※後述(postsテーブルを作成する場合、単数形にする)
content = カラム名(データベースのテーブルで言う縦の列 -> ID、名前などの部分)
text = データ型(textを指定した場合は長いテキストを取り扱う宣言)

マイグレーションファイルの場所は以下
test-app/db/migrate/<日付>_create_posts.rb

テーブル作成

作成したマイグレーションファイルを使って、データベースに変更を加える。
テーブルの作成は以下。

$ rails db:migrate

テーブルが作成できたら、マイグレーションファイル作成時に指定した"content"カラム以外にも、以下のカラムが自動生成されている。
- ID
1から順に割り当てられ重複する事はない
- created_at
データが作成された日時
- updated_at
データが更新された日時

注意

マイグレーションファイルを作成後、データベースに変更を加えずにどこかのページにアクセスするとマイグレーションエラーでエラーページが表示される。
その為、マイグレーションファイルを作成したら必ず$ rails db:migrateを実行する事。

テーブル操作

作成したテーブルは、モデルという特殊なクラスを使用して操作する。
$ rails g model ...のコマンドで既にpostsテーブルを操作する為のPostモデルが生成されている。
場所はapp/models/post.rb/

以下のように、ApplicationRecordというクラスを継承したクラスがモデルと呼ばれる。

post.rb
class Post < ApplicationRecord
end

テーブルにデータを保存する

newメソッド

Postモデル(Postテーブル)からPostインスタンスを作成する。
インスタンスの作成時にはnewメソッドを使用する。

Contentカラムが"Hello World"であるPostインスタンスを作成し、post変数に代入

$ rails console
> post = Post.new(content: "Hello World")

saveメソッド

作成したPostインスタンスをpostsテーブルに保存するためにsaveメソッドを使用する。
saveメソッドはPostモデルがApplicationRecordを継承しているから使用可能

$ rails console
> post.save

テーブルからデータを取り出す

テーブルにある最初のデータを取り出す

Post.firstのようにする事でpostsテーブルにある最初のデータを取得できる

example.rb
$ rails console
> post = Post.first

contentカラムにの値を取り出す

post.contentとすることでpost.firstで取得したデータから内容を取得できる

example.rb
$ rails console
> post = Post.first
> post.content
$ rails console
Loading development environment (Rails 5.0.3)
[1] pry(main)> post = Post.first
  Post Load (0.1ms)  SELECT  "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Post:0x000055cb4cf67c60
 id: 1,
 content: "Hello World",
 created_at: Sun, 19 Apr 2020 00:00:36 JST +09:00,
 updated_at: Sun, 19 Apr 2020 00:00:36 JST +09:00>
[2] pry(main)> post.content
=> "Hello World"
[3] pry(main)>

全てのデータを取得

テーブルに保存されている全てのデータを取得するにはPost.allを使用する
全てのデータは配列で取得される
配列の中から一つの値を取り出す場合はPost.all[0]のようにインデックス番号で要素を取得する

example.rb
$ rails console
> posts = Post.all
> Post.all[0]

配列のデータからコンテンツを取得

Post.all[0].contentのようにする事でcontentカラムの値を取得できる

$ rails console
Loading development environment (Rails 5.0.3)
[1] pry(main)> posts = Post.all
  Post Load (1.0ms)  SELECT "posts".* FROM "posts"
=> [#<Post:0x000055ecac398390
  id: 1,
  content: "Hello",
  created_at: Sun, 19 Apr 2020 00:00:36 JST +09:00,
  updated_at: Sun, 19 Apr 2020 00:00:36 JST +09:00>,
 #<Post:0x000055ecac3900a0
  id: 2,
  content: "World",
  created_at: Sun, 19 Apr 2020 00:00:59 JST +09:00,
  updated_at: Sun, 19 Apr 2020 00:00:59 JST +09:00>]
[2] pry(main)> posts[0]
=> #<Post:0x000055ecac398390
 id: 1,
 content: "Hello",
 created_at: Sun, 19 Apr 2020 00:00:36 JST +09:00,
 updated_at: Sun, 19 Apr 2020 00:00:36 JST +09:00>
[3] pry(main)> posts[1]
=> #<Post:0x000055ecac3900a0
 id: 2,
 content: "World",
 created_at: Sun, 19 Apr 2020 00:00:59 JST +09:00,
 updated_at: Sun, 19 Apr 2020 00:00:59 JST +09:00>
[4] pry(main)> posts[0].content
=> "Hello"
[5] pry(main)> posts[1].content
=> "World"
[6] pry(main)>

データをビューに表示

postsコントローラのindexアクション内の@postsに、Post.allで取得したデータを代入する
コード内に表示するテキストを埋め込んでいる場合

posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = [
      "Hello",
      "World"
    ]
  end
end

@postsに代入されている配列をPost.allに置き換える

posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

ビューでは@postsに代入されている配列データをeach文で1つずつ変数postに代入し、post.contentを用いて繰り返し表示させる
app/views/配下のhtml.erbファイルでは以下のように記述する

<header>
  <div class="header-logo">
    <a href="/">タイトル</a>
  </div>
  <ul class="header-menus">
    <li>
      <a href="/about">ほげほげ</a>
    </li>
  </ul>
</header>

<div class="main posts-index">
  <div class="container">
    <% @posts.each do |post| %>
      <div class="posts-index-item">
        <%= post.content %>
      </div>
    <% end %>
  </div>
</div>
end

共通レイアウト

共有のレイアウトをまとめる

Railsでは、「views/layouts/application.html.erb」に共通のHTMLを書いておくことができます。
初期状態でも、

タグやタグなどの共通部分はこちらに書かれています。今回は、以下の図のように、ここにヘッダーを追加し、どのページでも共通のヘッダーが表示されるようにしましょう。

以下のようなヘッダーが記述されたhtmlファイルが複数ある場合は、「views/layouts/application.html.erb」に記述する。

<header>
  <div class="header-logo">
    <a href="/">タイトル</a>
  </div>
  <ul class="header-menus">
    <li>
      <a href="/about">ほげほげ</a>
    </li>
  </ul>
</header>

トップページなど、各ページのhtmlファイルでは以下のようにヘッダーを記述していなくてもヘッダー部分が表示されるようになる

<div class="main top-main">
  <div class="top-message">
    <h2>ほげほげ</h2>
    <p>ふがふが</p>
  </div>
</div>

「views/layouts/application.html.erb」の全体は以下のように記述

<!DOCTYPE html>
<html>
  <head>
    <title>タイトル</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<!--- Rails6.0以降は"javascript_pack_tag"、5.0以前は"javascript_include_tag"--->
  </head>

  <body>
    <header>
      <div class="header-logo">
        <a href="/">ほげほげ</a>
      </div>
      <ul class="header-menus">
        <li><a href="/about">ふがふが</a></li>
      </ul>
    </header>
    <!-- 各ビューファイルは以下のyieldに代入され、application.html.erbの一部となる -->
    <%= yield %>
  </body>
</html>

レイアウトの仕組み

「views/layouts/application.html.erb」には<%= yield %>というコードがあり、top.html.erbなどの各ビューファイルは、この<%= yield %>の部分に代入され、application.html.erbの一部としてブラウザに表示される。
この仕組みによって、ヘッダーなどの共通のレイアウトを1つにまとめることができる

link_toメソッド

Rails ではlink_toというメソッドを使うとタグを作成することができるぞ。 link_to メソッドは Ruby のコードなので、「<%=%>」で囲むことに注意するのじゃ。

右の図のように、第一引数に表示する文字を、第二引数に URLを書くことでリンクが作成されるぞ。

application.html.erb
<%= link_to("About", "/about") %>
            第一引数   第二引数

↓以下のaタグに変換される

application.html
<a href="/about">About</a>

例えば、以下のようにaタグを付与しているhtml.erbファイルがあった場合

<body>
    <header>
      <div class="header-logo">
        <a href="/">トップページ</a>
      </div>

以下の様に置き換えができる

<body>
    <header>
      <div class="header-logo">
        <%= link_to("トップページ", "/")%>
      </div>

複数ページのリンクを指定する例

      <ul class="header-menus">
        <li>
          <%= link_to("サービス説明", "/about")%>
        </li>
        <li>
          <%= link_to("投稿一覧", "/posts/index")%>
        </li>
      </ul>
    </header>

find_byメソッド

find_byメソッドは、データベースから特定のIDなど条件に合致したデータを取得する
取得するデータの条件は以下の様に指定する

モデル名.find_by(カラム名: 値)

idが3のデータを取得し、post変数に代入してcontent、created_at、idのそれぞれの絡むの値を取得する例

$ rails console
Loading development environment (Rails 5.0.3)
[1] pry(main)> post = Post.find_by(id:3)
  Post Load (0.1ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
=> #<Post:0x000055ae42ffa8c0
 id: 3,
 content: "ほげほげ",
 created_at: Mon, 20 Apr 2020 18:20:09 JST +09:00,
 updated_at: Mon, 20 Apr 2020 18:20:09 JST +09:00>
[2] pry(main)> post.content
=> "ふがふが"
[3] pry(main)> post.created_at
=> Mon, 20 Apr 2020 18:20:09 JST +09:00
[4] pry(main)> post.id
=> 3
[5] pry(main)>

データベースからデータを表示するページを作成

ルーティング/アクション/ビューの設定

データのID毎にページを分ける設計で
詳細ページにはshowアクションを作成する
URLが/post/1/post/2のとき、showアクションに行くようにする。
通常以下のようにルーティングを作成する

routers.rb
get "post/1" => "posts#show"
get "post/2" => "posts#show"
get "post/2" => "posts#show"

上の場合だと投稿データの数だけ書かなければいけなくなるので、次の例ではURL部分「:」を使用し、「posts/:id」と指定する事で、「/posts/1」や「/posts/2」でもshowアクションにいくようにできる。
routers.rbに「posts/:id」と書くと、以下「/posts/〇〇」のような全てのURLが該当する。

localhost:3000/posts/1
localhost:3000/posts/2

routers.rb
get "posts:/id" => "posts#show"

この様にルーティングが設定され、showアクションが定義されていれば、以下の様にコントローラファイルに飛ぶ

posts_controller.rb
def show
end

注意

posts/:id」というルーティングは「posts/index」より下に書かなければならない。
ルーティングは合致するURLを上から順に探すため、「posts/index」より上に書くと、localhost:3000/posts/indexというURLは「posts/:id」というルーティングに合致してしまう。

正しい例

correct_routers.rb
get "posts/index" => "posts#index"
get "posts/:id" => "posts#show"

間違っている例

incorrect_routers.rb
get "posts/:id" => "posts#show"
get "posts/index" => "posts#index"

ここまでで、ルーティングの設定(config/routes.rb)、アクション(app/controllers/posts_controller.rb)の設定ができている為、最後に表示するページのビューを作成して新しいページの完成となる。

app/views/posts/show.html.erbとして新しいページビューを作成

show.html.erb
<div class="main posts-show">
  <div class="container">
    <div class="posts-show-item">
      <p>投稿詳細画面です</p>
    </div>
  </div>
</div>

新しくルーティング設定したURL(localhost:3000/posts/1etc)にブラウザからアクセスすると、showアクションに行くようになり、上記のページビューが表示される。

URLからIDを取得

上の状態ではURLにどのIDを指定しても同じビューが表示される為、ID毎にページを振り分ける設定が必要になる。

コントローラのアクション内ではルーティングで設定したURLの「:id」の値を取得する事ができる。
その値はparamsという変数にハッシュとして入っている。
params[;id]とする事で、その値を取得することができる。

localhost:3000/posts/1でアクセスした場合、以下のparams変数には[id:1]というハッシュが代入されている

posts_controller.rb
def show
  @id = params[:id]
end

ビューで以下の様にした場合は、@idから「1」が表示される

posts/show.html.erb
<%= @id %>

posts/show.html.erb
<div class="main posts-show">
  <div class="container">
    <div class="posts-show-item">
      <p>
        <%= "idが「#{@id}」の投稿詳細画面です" %>
      </p>
    </div>
  </div>
</div>

データベースの内容を表示

showアクションで変数@postを定義し、idカラムの値がparams[:id]と等しいデータをデータベースから取得して代入する。
@postshow.html.erbで表示することで、書くURLに対応したデータが表示されるようにする。

以下例では、idカラムがparams[:id]であるデータをfind_byメソッドにより取得し、show.html.erbにより表示している。

posts_controller.rb
def show
  @post = Post.find_by(id: params[:id])
end
posts/show.html.erb
<div class="posts-show-item>
  <%= @post.content %>
  <div class="post-time">
    <%= @post.created_at %>
  </div>
</div>

データベース参照ページへのリンクを作成

例として投稿一覧ページに、各投稿の詳細ページへのリンクを作成する。
各投稿の内容の部分をクリックすると詳細ページに移動できるように、link_to(post.content, "/posts/#{post.id}")とする。
#{post.id}部分は変数展開を用いてデータのidを指定している。

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

ユーザーによる投稿ページを作成する

新規投稿ページの準備

新規投稿ページではlocalhost:3000/posts/newというURLでアクセスできるようにする。
そのためにルーティング/アクション/ビューを追加する。
アクションは"new"アクションとする。

ルーティング

routes.rb
get "posts/index" => "posts#index"
#追加
get "posts/new" => "posts#new"
#post:/idよりも上に書くことに注意
get "posts/:id" => "posts#show"

アクション

posts_controller.rb
class PostsController < ApplicationController
 def new
  end
end

ビュー

posts/new.html.erb
<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">投稿する</h1>
  </div>
</div>

ここまででlocalhost:3000/posts/newにアクセス可能となる。

投稿一覧ページに新規投稿ページのリンクを追加する

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>TweetApp</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <header>
      <div class="header-logo">
        <%= link_to("タイトル", "/") %>
      </div>
      <ul class="header-menus">
        <li>
          <%= link_to("ほげほげ", "/about") %>
        </li>
        <li>
          <%= link_to("投稿一覧", "/posts/index") %>
        </li>
        <li>
          <%= link_to("新規投稿", "/posts/new") %>
        </li>
      </ul>
    </header>
    <%= yield %>
  </body>
</html>

入力フォームを作成

HTMLやCSSを使用し、タグやタグを用いることで入力フォームを作成することができる。
送信ボタンにはtype="submit"と、value="投稿"を指定する。

posts/new.html.erb
# 入力フォーム
<textarea></textarea>

# 投稿ボタン
<input type="submit" value="投稿"

new.html.erb
<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">投稿する</h1>
    <div class="form">
      <div class="form-body">
        <textarea></textarea>
        <input type="submit" value="投稿">
      </div>
    </div>
  </div>
</div>

投稿を保存する流れ

フォームの投稿ボタンを押すと、Rails側に投稿データが送信される。
例ではcreateアクションを用意して、受け取った投稿データをデータベースに保存するようにする。
createアクションのURLは「/posts/create」とする。

createアクションのルーティング

フォームの値を受け取る場合は「get」ではなく「post」とする。(この「post」はPostモデルの「Post」とは関係ない。)
通常は「get」、フォームの値を受け取るときは「post」というように覚える。

routes.rb
get "posts/new" => "posts#new"
post "posts/create" => "posts#create"

フォームの送信先を指定

form_tagメソッドを用いると、フォームに入力されたデータを送信することができる。
form_tagは、「form_tag(送信先のURL) do」のように送信先のURLを指定する。
これによって、のボタンを押した時に、指定されたURLにデータが送信される。

posts/new.html.erb
<%= form_tag("/posts/create") do %>
# "posts/create"は送信先のURL

  <textarea></textarea>
  <input type="submit" value="投稿”>
<% end %>

form_tagメソッドの注意点
- <%= %>で囲む
- doendの中にフォームを作る

実装

フォームからデータを送信するために以下を定義する
1. ルーティング
2. アクション
3. form_tag

ルーティング

localhost:3000/posts/createというURLへ、フォームからデータを送信できるようにルーティングを追加する。
ただし、post URL => コントローラ名#アクション名という形で定義する。
対応するアクションはpostsコントローラのcreateアクションとする。

config/routes.rb
Rails.application.routes.draw do
  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"
end

アクション
createアクションを定義する

controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = Post.find_by(id: params[:id])
  end

  def new
  end

  def create
  end
end

form_tag
データの送信先を指定

posts/new.html.erb
<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">投稿する</h1>

    <%= form_tag("/posts/create") do %>
      <div class="form">
        <div class="form-body">
          <textarea></textarea>
          <input type="submit" value="投稿">
        </div>
      </div>
    <% end %>

  </div>
</div>

フォームの送信先を指定をしたが、現状では「投稿」ボタンを押してもまだ何も起きない。
これは
・ createアクションに対応するビューがない
・ 保存処理を書いていないため、フォームから送信されたデータがDBに保存されない
という未実装部分があるため。

createアクションの内容を作成

createアクションに対応するビューがないに対する対応としてリダイレクトを実装する。
今回はリダイレクトを用いて投稿一覧画面に転送するようにする。

redirect_to

他のURLに転送(リダイレクト)するには、redirect_toメソッドを使用する。
redirect_toは「redirect_to(URL)」とすることで、そのページに転送することができる。

posts_controller.rb
def create
# 指定したURLに転送する
  redirect_to("/posts/index")
end
posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = Post.find_by(id: params[:id])
  end

  def new
  end

  def create
    redirect_to("/posts/index")
  end
end

投稿内容を保存する

ここまででビューを用意する代わりにredirect_toを使用する事ができたので、
保存処理を書いていないため、フォームから送信されたデータがDBに保存されないに対応するため、送信されてきたデータをデータベースに保存する処理を2STEPで追加していく。

  1. 投稿がcreateアクションに送信される様にする
  2. 送信された内容を受け取り保存する

name属性

今の状態のフォームでは、投稿ボタンを押しても入力した内容をcreateアクションに伝えることができない。
タグにname属性を指定すると、入力データを送信することができるようになり、name属性の値をキーとしたハッシュがRails側に送られる。

投稿フォームに「ほげほげ」と入力し、投稿ボタンを押下することで、createアクションが実行される。
以下の様な例では、「content」がname属性となり、「ほげほげ」入力内容となる。
{content: "ほげほげ"}

posts/new.html.erb
<textarea name="content"></textarea>
# 入力データを送信できるようになる

1. 投稿がcreateアクションに送信される様にする

name属性を指定したフォームに入力されたデータは、コントローラのアクション内で受け取ることが可能になる。
フォームのデータは、変数paramsで受け取り、paramsはname属性に設定した文字列をキーとしたハッシュになっている。

post_controller.rb
def create
  params[:content]
# => "ほげほげ"
end

2. 送信された内容を受け取り保存する

実際に保存する手順に関しては、「rails console」で実施したように実装する。
以下ように、Postインスタンスを作成する際にparams[:content]を用いてそのPostインスタンスを保存することで投稿機能の完成。

post_controller.rb
def create

  @post = Post.new(content: params[:content])
# contentが入力データであるインスタンスを作成している

  @post.save
  redirect_to("/posts/index")
end

変数paramsのまとめ

入力データを受け取るためのparamsはURLからidの値を取得するときにも使用した。
paramsは以下の2通りの使い方があるため、整理して覚えておく。
①「:○○」を使ったルーティングのURLから値を取得する
②「name="○○"」が付いたフォームの入力内容を受け取る

get "posts/:○○" => URL

<textarea name="○○"></textarea>

投稿を並び替える

現状では新しい投稿は一番下に表示されるため、上から順に新しい投稿を表示するよう実装していく。

orderメソッド

orderメソッドを用いることで、投稿一覧を並び替えることができる。
order(カラム名: 並び替えの順序)のように使う。並び替えの順序には、昇順(:asc)と降順(:desc)のどちらかを指定する。
created_atを基準に降順(:desc)に並べ替えると、新しいものから順番に表示するようにできる。

posts_controller.rb
def index
  @posts = Post.all.order(created_at: :desc)
#                         作成日時      降順
end

投稿の編集/削除

rails consoleから試す

投稿を編集するには、以下の流れで実装する

  1. 編集したい投稿を取得
  2. その投稿のcontentの値を上書き
  3. データベースに保存

以下のようにpost.content = "新しい値"とすることで、投稿のcontentの値を上書きすることができる。

$ rails console
> post = Post.find_by(id:1)
# データベースから編集したい投稿を取得する

> post.content = "Rails"
# contentの値を上書きする

> post.save
# データベースに保存する

updated_at

created_atカラムとupdated_atカラムには投稿データ作成時の時刻が入る。
投稿データを編集してデータベースに保存すると、updated_atカラムの値がデータを更新したときの時刻に更新される。

投稿を削除する

次は、削除の流れを「rails console」で確認する。
投稿を削除するには、削除したい投稿を取得し、その投稿に対してdestroyメソッドを用いることで、データベースから削除することができる。
投稿を削除する手順は以下

  1. データベースから削除したい投稿を取得する
  2. destroyメソッドを使って、投稿を削除する
$ rails console

> post = Post.find_by(id:2)
> post.destroy

投稿編集ページを用意する

どの投稿の編集ページか判別するために、投稿編集ページのURLには編集したい投稿のidを入れるようにする。
そのため、showアクションと同様にルーティングにidを含むようにする。
また、postsコントローラ内にeditアクションを作成し、対応するビューとして、edit.html.erbも作成する。

routes.rb
get "posts/:od/edit" => "posts#edit"
# 編集したい投稿のidをURLに含む
# 「localhost:3000/posts/1/edit」
# 「localhost:3000/posts/2/edit」
# などのURLに対応させる
posts_controller.rb
class PostsController < ApplicationController
  def edit
  end
end
posts/edit.hrml.erb
<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">編集する</h1>
  </div>
</div>

投稿編集ページへのリンクを追加

投稿詳細ページから投稿編集ページにアクセスできるように、show.html.erbに以下のような「編集」リンクを追加する。

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

実装

  1. ルーティング まずは投稿編集ページ用のルーティングを追加する。 localhost:3000/posts/1/edit localhost:3000/posts/2/edit などのURLでアクセスできるルーティングを追加する。 アクションはpostsコントローラのeditアクションとする
routes.rb
Rails.application.routes.draw do
  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"

  get "/" => "home#top"
  get "about" => "home#about"
end
  1. アクション

editアクションを追加する

posts_controller.rb
class PostsController < ApplicationController
  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

  # editアクションを追加
  def edit
  end

end
  1. ビュー

app/views/postsフォルダの中にedit.html.erbを作成

edit.html.erb
<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">編集する</h1>
  </div>
</div>

次に、投稿編集ページへのリンクを投稿詳細ページに作成する

show.hrml.erb
<div class="main posts-show">
  <div class="container">
    <div class="posts-show-item">
      <p>
        <%= @post.content %>
      </p>
      <div class="post-time">
        <%= @post.created_at %>
      </div>
      <!-- 編集ページへのリンクを作成 -->
      <div class="post-menus">
        <%= link_to("編集", "/posts/#{@post.id}/edit") %>
      </div>
    </div>
  </div>
</div>

以上で投稿編集ページが完成するが、編集用のフォームがまだ無い。
次に編集用フォームを追加する必要がある。

入力フォームを用意する

編集内容を入力できるように、タグを使って入力フォームを作成する。

フォームに初期値を用意する

<textarea>タグでは、<textarea>初期値 </textarea>のようにタグで囲んだ部分を初期値として設定できる。

posts/edit.html.erb
<textarea>ほげほげ</textarea>
# "ほげほげ"が初期値として入力される

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

フォームの初期値として、編集したい投稿内容を表示する。
editアクションで、URLのidと同じidの投稿データをデータベースから取得し、そのcontentの値(=投稿の内容)を初期値に設定する。

localhost:3000/posts/1/edit

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>
#             ↑初期値として入力↑

実装

投稿編集ページに入力フォームを用意し、フォームに初期値が入るようにする。
まずは、editアクションで、URLに含まれるidと同じidの投稿データを取得する。

posts_controller.rb
class PostsController < ApplicationController
  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 = Post.find_by(id: params[:id])
  end

end

続いて入力フォームを作成

edit.html.erb
<div class="main posts-new">
  <div class="container">
    <h1 class="form-heading">編集する</h1>
    <!-- 入力フォームを作成 -->
    <div class="form">
      <div class="form-body">
        <textarea><%= @post.content %></textarea>
        <input type="submit" value="保存">
      </div>
    </div>
  </div>
</div>

ここまでで編集ページの見た目が完成するが、まだ入力した内容を保存することができない。
次からデータベースに保存するためのupdateアクションを追加する必要がある。

フォームの編集内容を保存する

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8