はじめに
Ruby on Rails理解しなからSNSのWebアプリを作成していく備忘録です。
開発環境はMacbookで、Railsは既にインストールされた状態からはじめます。
※本記事は記述途中です
Railsアプリの準備
まずはじめにRailsのワークディレクトリで、下のコマンドを打ち雛形の作成をすすめる。
- railsプロジェクトの作成
- 起動
- トップページの作成
$ rails new test_app
$ rails server
$ rails generate controller home top
ブラウザからそれぞれアクセスしてみる。
localhost:3000
localhost:3000/home/top
$ rails generate
によって、Webアプリに必要な以下のコンポーネントが作成される。
- View
- Controller
- Routing
View
Viewは文字通り見た目部分のHTMLファイル。
自動で作成されたビューは、test_app/app/views/home/top.html.erb
にある。
(「erb」とは「Embedded Ruby(埋め込みRuby)」の略) *後述
このtop.html.erb
を適当なHTMLに書き換えるとブラウザから見るページも変更される。
<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ファイルを探す。
class HomeController < ApplicationController
def top
end
end
新しいコントローラの追加
新しいページ(例えば、トップページの他に投稿ページなど)を作るときは、新しいコントローラを追加する。
(homeコントローラでも作成可能だが、投稿ページはhomeという訳ではないので、投稿に関する事は投稿用のコントローラを作成する)
"g"は、"generate"の省略
投稿一覧ページを作るため、コントローラ名は"posts"、アクション名は一覧なので**"index"**とする。
$ rails g controller posts index
これによって、以下が追加される。
- View (
app/views/posts/index.html.erb
+app/assets/stylesheets/posts.scss
) - Controller (
app/controllers/posts_controller.rb
) - Routing (
app/config/router.rb
->get "posts/index" => "posts#index"
が追加される)
Routing
ルーティングは、ブラウザとコントローラを繋ぐ役目を持つ。
ページが表示されるまでの一連の動きは、ルーティング->コントローラ->ビュー
というふうになる。
ルーティングは、送信さえたURLに対してどのコントローラのどのアクションで処理するのかを決める対応表である。
ブラウザに入力されたURLは、ルーティングによって適切なコントローラ、アクションが呼び出される。
localhost:3000/home/app
Railsサーバー/コントローラ/アクション
ルーティングはtest_app/app/config/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
に変えるときは以下のように変更。
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的なページが出力されるが、これを本来のトップページに変更したい場合
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)を追加
class HomeController < ApplicationController
def top
end
def about
end
end
- アクション(about)のルーティングを追加
Rails.application.routes.draw do
get "top" => "home#top"
get "about" => "home#about"
end
新しいviewファイルabout.html.erb
をapp/home/配下に作成
<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は全てのビューに適用される。
例
/* 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
に配置することでフルパスを指定しなくても、画像ファイルのファイル名のみで参照可能。
<img src="/画像名" >
background-image: url("/画像名");
リンク
HTMLの中でトップページや他ページへのリンクをつけたい場合。
Railsの中であればルーティングのURL部分と同じような指定で書ける。
<a href="/">Test App</a>
<a href="/about">Test App</a>
erbファイルについて
「erb」とは「Embedded Ruby(埋め込みRuby)」の略
変数の定義
<% %>
で囲むことでhtml.erbファイル内でRubyのコーディングと同じように変数も定義可能。
<% post1 = "テストです" %>
変数を表示するには<%= %>
とする。
<%= post1 %>
# 「テストです」を表示
配列
<%
posts = [
"テストです", <---コンマを忘れない
"テスト2です"
]
%>
each文
普通に書く(こんな感じ)
<% posts.each do |post| %>
<% end %>
も忘れず。
<div class="main posts-index">
<div class="container">
<% posts.each do |post| %>
<div class="posts-index-item">
<%= post %>
</div>
<% end %>
コントローラのアクションでの変数定義
一般的には変数はViewファイルではなくアクションで定義する。
変数名の前に***"@"をつける事でここで定義した変数が、Viewファイルでも使用できる。
Viewファイルから呼び出すときも"@"***をつける(<%= @post %>
)
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というクラスを継承したクラスがモデルと呼ばれる。
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テーブルにある最初のデータを取得できる
$ rails console
> post = Post.first
contentカラムにの値を取り出す
post.contentとすることでpost.firstで取得したデータから内容を取得できる
$ 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]**のようにインデックス番号で要素を取得する
$ 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で取得したデータを代入する
コード内に表示するテキストを埋め込んでいる場合
class PostsController < ApplicationController
def index
@posts = [
"Hello",
"World"
]
end
end
@postsに代入されている配列をPost.allに置き換える
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を書くことでリンクが作成されるぞ。
<%= link_to("About", "/about") %>
第一引数 第二引数
↓以下のaタグに変換される
<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アクションに行くようにする。
通常以下のようにルーティングを作成する
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
get "posts:/id" => "posts#show"
この様にルーティングが設定され、showアクションが定義されていれば、以下の様にコントローラファイルに飛ぶ
def show
end
注意
「posts/:id」というルーティングは「posts/index」より下に書かなければならない。
ルーティングは合致するURLを上から順に探すため、「posts/index」より上に書くと、localhost:3000/posts/index
というURLは「posts/:id」というルーティングに合致してしまう。
正しい例
get "posts/index" => "posts#index"
get "posts/:id" => "posts#show"
間違っている例
get "posts/:id" => "posts#show"
get "posts/index" => "posts#index"
ここまでで、ルーティングの設定(config/routes.rb
)、アクション(app/controllers/posts_controller.rb
)の設定ができている為、最後に表示するページのビューを作成して新しいページの完成となる。
app/views/posts/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/1
etc)にブラウザからアクセスすると、showアクションに行くようになり、上記のページビューが表示される。
URLからIDを取得
上の状態ではURLにどのIDを指定しても同じビューが表示される為、ID毎にページを振り分ける設定が必要になる。
コントローラのアクション内ではルーティングで設定したURLの「:id」の値を取得する事ができる。
その値はparamsという変数にハッシュとして入っている。
**params[;id]**とする事で、その値を取得することができる。
localhost:3000/posts/1
でアクセスした場合、以下のparams変数には**[id:1]**というハッシュが代入されている
def show
@id = params[:id]
end
ビューで以下の様にした場合は、@idから「1」が表示される
<%= @id %>
例
<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]**と等しいデータをデータベースから取得して代入する。
@postをshow.html.erb
で表示することで、書くURLに対応したデータが表示されるようにする。
以下例では、idカラムがparams[:id]であるデータをfind_byメソッドにより取得し、show.html.erbにより表示している。
def show
@post = Post.find_by(id: params[:id])
end
<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.each do |post| %>
<div class="posts-index-item">
<%= link_to(post.content, "/posts/#{post.id}") %>
</div>
<% end %>
ユーザーによる投稿ページを作成する
新規投稿ページの準備
新規投稿ページではlocalhost:3000/posts/new
というURLでアクセスできるようにする。
そのためにルーティング/アクション/ビューを追加する。
アクションは"new"アクションとする。
ルーティング
get "posts/index" => "posts#index"
#追加
get "posts/new" => "posts#new"
#post:/idよりも上に書くことに注意
get "posts/:id" => "posts#show"
アクション
class PostsController < ApplicationController
def new
end
end
ビュー
<div class="main posts-new">
<div class="container">
<h1 class="form-heading">投稿する</h1>
</div>
</div>
ここまででlocalhost:3000/posts/new
にアクセス可能となる。
投稿一覧ページに新規投稿ページのリンクを追加する
<!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="投稿"を指定する。
# 入力フォーム
<textarea></textarea>
# 投稿ボタン
<input type="submit" value="投稿"
例
<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」というように覚える。
get "posts/new" => "posts#new"
post "posts/create" => "posts#create"
フォームの送信先を指定
form_tagメソッドを用いると、フォームに入力されたデータを送信することができる。
form_tagは、「form_tag(送信先のURL) do」のように送信先のURLを指定する。
これによって、のボタンを押した時に、指定されたURLにデータが送信される。
<%= form_tag("/posts/create") do %>
# "posts/create"は送信先のURL
<textarea></textarea>
<input type="submit" value="投稿”>
<% end %>
form_tagメソッドの注意点
- **<%= %>**で囲む
- doとendの中にフォームを作る
実装
フォームからデータを送信するために以下を定義する
- ルーティング
- アクション
- form_tag
ルーティング
localhost:3000/posts/create
というURLへ、フォームからデータを送信できるようにルーティングを追加する。
ただし、post URL => コントローラ名#アクション名
という形で定義する。
対応するアクションはpostsコントローラのcreateアクションとする。
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アクションを定義する
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
データの送信先を指定
<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)」とすることで、そのページに転送することができる。
def create
# 指定したURLに転送する
redirect_to("/posts/index")
end
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で追加していく。
- 投稿がcreateアクションに送信される様にする
- 送信された内容を受け取り保存する
name属性
今の状態のフォームでは、投稿ボタンを押しても入力した内容をcreateアクションに伝えることができない。
タグにname属性を指定すると、入力データを送信することができるようになり、name属性の値をキーとしたハッシュがRails側に送られる。 投稿フォームに「ほげほげ」と入力し、投稿ボタンを押下することで、createアクションが実行される。 以下の様な例では、「content」が**name属性**となり、「ほげほげ」入力内容となる。 `{content: "ほげほげ"}` ```posts/new.html.erb入力データを送信できるようになる
`1. 投稿がcreateアクションに送信される様にする`
name属性を指定したフォームに入力されたデータは、コントローラのアクション内で受け取ることが可能になる。
フォームのデータは、変数paramsで受け取り、paramsはname属性に設定した文字列をキーとしたハッシュになっている。
```post_controller.rb
def create
params[:content]
# => "ほげほげ"
end
2. 送信された内容を受け取り保存する
実際に保存する手順に関しては、「rails console」で実施したように実装する。
以下ように、Postインスタンスを作成する際にparams[:content]を用いてそのPostインスタンスを保存することで投稿機能の完成。
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)に並べ替えると、新しいものから順番に表示するようにできる。
def index
@posts = Post.all.order(created_at: :desc)
# 作成日時 降順
end
投稿の編集/削除
rails consoleから試す
投稿を編集するには、以下の流れで実装する
- 編集したい投稿を取得
- その投稿のcontentの値を上書き
- データベースに保存
以下のように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メソッドを用いることで、データベースから削除することができる。
投稿を削除する手順は以下
- データベースから削除したい投稿を取得する
- destroyメソッドを使って、投稿を削除する
$ rails console
> post = Post.find_by(id:2)
> post.destroy
投稿編集ページを用意する
どの投稿の編集ページか判別するために、投稿編集ページのURLには編集したい投稿のidを入れるようにする。
そのため、showアクションと同様にルーティングにidを含むようにする。
また、postsコントローラ内にedit
アクションを作成し、対応するビューとして、edit.html.erb
も作成する。
get "posts/:od/edit" => "posts#edit"
# 編集したい投稿のidをURLに含む
# 「localhost:3000/posts/1/edit」
# 「localhost:3000/posts/2/edit」
# などのURLに対応させる
class PostsController < ApplicationController
def edit
end
end
<div class="main posts-new">
<div class="container">
<h1 class="form-heading">編集する</h1>
</div>
</div>
投稿編集ページへのリンクを追加
投稿詳細ページから投稿編集ページにアクセスできるように、show.html.erbに以下のような「編集」リンクを追加する。
<%= link_to("編集", "/posts#{@post.id}/edit") %>
# editアクションのURLを指定
実装
-
ルーティング
まずは投稿編集ページ用のルーティングを追加する。
localhost:3000/posts/1/edit
localhost:3000/posts/2/edit
などのURLでアクセスできるルーティングを追加する。
アクションはpostsコントローラのeditアクションとする
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
アクション
editアクションを追加する
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
ビュー
app/views/postsフォルダの中にedit.html.erb
を作成
<div class="main posts-new">
<div class="container">
<h1 class="form-heading">編集する</h1>
</div>
</div>
次に、投稿編集ページへのリンクを投稿詳細ページに作成する
<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>
のようにタグで囲んだ部分を初期値として設定できる。
<textarea>ほげほげ</textarea>
# "ほげほげ"が初期値として入力される
投稿内容をフォームの初期値にする
フォームの初期値として、編集したい投稿内容を表示する。
editアクションで、URLのidと同じidの投稿データをデータベースから取得し、そのcontentの値(=投稿の内容)を初期値に設定する。
localhost:3000/posts/1/edit
get "posts/:id/edit" => "posts#edit"
def edit
@post = Post.find_by(id: params[:id])
end
<textarea><%= @post.content %></textarea>
# ↑初期値として入力↑
実装
投稿編集ページに入力フォームを用意し、フォームに初期値が入るようにする。
まずは、editアクションで、URLに含まれるidと同じidの投稿データを取得する。
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
続いて入力フォームを作成
<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アクションを追加する必要がある。