概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: The Ultimate Intermediate Ruby on Rails Tutorial: Let’s Create an Entire App!
- 原文公開日: 2017/12/17
- 著者: Domantas G
Rails5中級チュートリアルはセットアップが短めで、RDBMSにはPostgreSQL、テストにはRSpecを用います。
原文が非常に長いので分割します。章ごとのリンクは順次追加します。
注意: Rails中級チュートリアルは、Ruby on Railsチュートリアル(https://railstutorial.jp/)(Railsチュートリアル)とは著者も対象読者も異なります。
目次
- 1. 序章とセットアップ
- 2. レイアウト
- 3. 投稿
- 3-1 認証(本セクション)
- 3-2 ヘルパー
- 3-3 テスト
- 3-4 メインフィード
- 3-5 単一の投稿
- 3-6 特定のブランチ
- 3-7 Service Object
- 3-8 新しい投稿を作成する
- 4. インスタントメッセージ
- 4-1 非公開チャット
- 4-2 連絡先
- 4-3 グループチャット
- 4-4 メッセンジャー
- 5. 通知
- 5-1 つながりリクエスト
- 5-2 チャット
Rails5「中級」チュートリアル(3-1)投稿機能: 認証(翻訳)
3 投稿機能
そろそろ投稿機能の実装に取りかかれるようになってきました。このアプリの目的は、ユーザーが自分の好みに近い人に出会えるようにすることなので、投稿の著者を識別できるようにしなければなりません。これを行うには認証システムが必要です。
3-1 認証
認証システムにはDevise gemを使うことにします。認証システムを自作することもできなくはありませんが、相当頑張らないといけないでしょう。本チュートリアルでは楽な道を進むことにします。かつDeviseはRailsコミュニティで人気の高いgemでもあります。
まずは新しいブランチを切りましょう。
git checkout -b authentication
他のgemと同様、Deviseのセットアップはドキュメントに従って進めます。幸いなことに、セットアップは非常に簡単です。
Gemfile
に以下を追加します。
gem 'devise'
以下のコマンドを実行します。
bundle install
rails generate devise:install
実行後、コマンドプロンプトにいくつか指示が表示されます。本チュートリアルではmailerを使わないので、追加の設定は不要です。
もしRailsのモデルについてまったく知識がないのであれば、RailsガイドのActive Recordの基礎とActive Modelの基礎を取り急ぎ読んで、ActiveRecordやActiveModelに慣れておく必要があります。
それでは、DeviseのジェネレータでUser
モデルを作成しましょう。
rails generate devise User
以下を実行してアプリのデータベースを初期化します。
rails db:create
続いて、以下を実行してデータベースに新しいテーブルを作成します。
rails db:migrate
以上で認証システムの技術面のセットアップは終わりです。これで、Deviseが提供するメソッドを用いて新しいユーザーを作成できるようになりました。変更をcommitしましょう。
git add -A
git commit -m "Add and configure the Devise gem"
Devise gemをインストールすると、バックエンドの機能の他にデフォルトのビューも使えるようになります。以下を実行してルーティングを表示してみましょう。
rails routes
さっきまではrootへのルーティングしかなかったのに、新しいルーティングがたくさんできているのがわかります。もしわからなくなりそうだったら、いつでもDevise wikiのドキュメントを開いて情報を見つけることができます。自分と同じ問題で悩んだ人はたくさんいるはずなので、ググって答えを見つけられる可能性はかなりあります。
参考: Devise How-To wiki日本語目次もどうぞ。
ルーティングをいくつか試してみましょう。localhost:3000/users/sign_inをブラウザで開くと、以下のようなサインインページが表示されるはずです。
localhost:3000/users/sign_upを開いてみると、同じようなページが表示されます。しかしviews
ディレクトリを見てみると、どこにもDeviseのディレクトリがありません。どこをいじればよいのでしょうか?Noob Noobならここで「Got Damn!」というところです。Deviseのドキュメントに記載されているように、Deviseのビューを変更するにはジェネレータを使ってビューを生成する必要があります。以下を実行します。
rails generate devise:views
views
ディレクトリを見てみると、Devise用のディレクトリが生成されているのがわかります。ここでサインアップページやログインページの表示方法を変更できます。このアプリではログインページの実装の方が素直になるので、まずはログインページから変更します。この登録ページに機能を追加したいので、少し手間をかける必要があります。
ログインページ
app/views/devise/sessions/new.html.erb
を開きます。
ログインページのビューはこのファイルに保存されており、その実体は単なるログインフォームです。このフォームの生成に[form_for](http://api.rubyonrails.org/v5.1/classes/ActionView/Helpers/FormHelper.html)
メソッドが使われていることにお気づきでしょうか。form_for
はフォームの生成に便利なRailsのメソッドです。このフォームのスタイルを変更してBootstrapを適用することにします。このファイルの内容を以下で置き換えます(Gist)。
<!-- views/devise/sessions/new.html.erb -->
<%= bootstrap_form_for(resource,
as: resource_name,
url: session_path(resource_name)) do |f| %>
<%= f.email_field :email,
autofocus: true,
class: 'form-control',
placeholder: 'email' %>
<%= f.password_field :password,
autocomplete: "off",
class: 'form-control',
placeholder: 'password' %>
<% if devise_mapping.rememberable? -%>
<%= f.check_box :remember_me %>
<% end -%>
<%= f.submit "Log in", class: 'form-control login-button' %>
<% end %>
特別なことは何もしていません。このフォームをBootstrapのフォームに変えるためにメソッド名をbootstrap_form_for
に変更し、form-control
クラスをフィールドに追加しているだけです。
このメソッドの引数の書き方を見てみましょう。各引数を1行ずつ分けて書いてあります。このように書いた理由は、コードが横に長くなるのを避けるためです。読みやすさのため、コード行は最大80文字を超えないようにするのが普通です。本チュートリアルでは今後もこのスタイルでコードを書くことにします。
この時点でlocalhost:3000/users/sign_inをブラウザで開くと、以下のエラーが表示されます。
undefined method 'bootstrap_form_for'
RailsでBootstrapのフォームを使うには、bootstrap_form gemの追加が必要です。Gemfile
に以下を追加します。
gem 'bootstrap_form'
続いて以下を実行します。
bundle install
これで、以下のようにログインページが表示されるはずです。
変更をcommitします。
git add -A
git commit -m "Generate devise views, modify sign in form
and add the bootstrap_form gem."
Bootstrapのグリッドシステムをページに適用するには、以下のようにログインフォームをBootstrapのコンテナでラップします(Gist)。
<!-- views/devise/sessions/new.html.erb -->
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<h2 class="text-center">Log in</h2>
<!-- 上のログインフォームをここに貼る -->
</div>
</div>
</div>
このログインフォームの幅は12カラムのうち6カラムを使い、オフセットは3カラムにします。画面の小さなデバイスではフル画面の幅でフォームが表示されます。Bootstrapのグリッドはこのように動作します。
ここでさらにcommitしておきましょう。変更量が少ないのではないかとお思いかもしれませんが、私は普段このぐらいの粒度でcommitしています。私は、ある部分に明確な変更を実装したらcommitするようにしています。こうすることで変更が追いやすくなり、コードの移り変わりも理解しやすくなると私は思います。
git add -A
git commit -m "wrap login form in the login page with a boostrap container"
ログインページにアクセスするURLを、/users/sign_in
から/login
に変えるとよさそうです。それにはルーティングの変更が必要ですが、そのためにアクションの場所を知っておく必要があります。ログインページにアクセスすると、その場所にあるアクションを呼び出します。Deviseのコントローラは、Devise gem自身の中にあります。Deviseのドキュメントを読むと、Deviseのコントローラはすべてdevise
ディレクトリの下にあることがわかります。正直、大した発見ではありませんが(U_U)。ルーティングの単純な変更はdevise_scope
メソッドで行えます。routes.rb
ファイルを開いて以下を追加します。
devise_scope :user do
get 'login', to: 'devise/sessions#new'
end
変更をcommitします。
git add -A
git commit -m "change route from /users/sign_in to /login"
ログインページはとりあえずこのままにしておきましょう。
サインアップページ
localhost:3000/users/sign_upをブラウザで開くと、こちらはDeviseのデフォルトのサインアップページのままになっています。先ほど申し上げたとおり、このページには少し手間をかける必要があります。users
テーブルに:name
カラムを追加して、User
オブジェクトで:name
属性を扱えるようにするためです。
schema.rb
ファイルに少し変更を加えます。スキーマの変更やマイグレーションがよくわからない方は、ガイドのActive Recordマイグレーションをひととおり読んでおくことをおすすめします。
最初に、users
テーブルにカラムを追加しなければなりません。これは、マイグレーションファイルを新しく作成し、change_table
メソッドでカラムを追加することで行えます。しかし開発はまだ始まったばかりなので、アプリはまだデプロイされていません。そこで、既存のdevise_create_users
マイグレーションファイルにカラムを追加してデータベースを再作成することにします。db/migrate
ディレクトリに移動して(作成日)_devise_create_users.rb
ファイルを開き、create_table
の内側にt.string :name, null: false, default: ""
を追加します。
以下のコマンドを実行して、データベースの削除と再作成を行ってから、マイグレーションを実行します。
訳注: データベース操作中はRailsサーバーを停止しておきましょう。
rails db:drop
rails db:create
rails db:migrate
これでusers
テーブルにカラムが追加され、schema.rb
ファイルが変更されました。
追加した属性を送信できるようにするには、Deviseのコントローラで属性を受信できるよう、コントローラレベルの変更を少々行わなければなりません。Deviseコントローラの変更方法はいくつか考えられます。Deviseのジェネレータでコントローラを生成する方法や、ファイルを1つ作成して変更したいコントローラやメソッドをそこで指定する方法があります。どちらの方法でも構いませんが、ここでは後者の方法にします。
app/controllers
ディレクトリに移動してregistrations_controller.rb
ファイルを作成し、以下のコードをファイルに追加します(Gist)。
# controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit( :name,
:email,
:password,
:password_confirmation)
end
def account_update_params
params.require(:user).permit( :name,
:email,
:password,
:password_confirmation,
:current_password)
end
end
上のコードによってsign_up_params
メソッドとaccount_update_params
メソッドがオーバーライドされ、:name
属性を受信できるようになります。見てのとおり、2つのメソッドはDeviseのRegistrationsController
クラスにあるので、このクラスを指定してメソッドを変更しました。2つのメソッドがオーバーライドされるよう、このコントローラをルーティングの内部で指定しなければなりません。route.rb
で以下の変更を行います。
devise_for :users
上を以下のコードに置き換えます。
devise_for :users, :controllers => {:registrations => "registrations"}
変更をcommitします。
git add -A
git commit -m "
- Add the name column to the users table.
- Include name attribute to sign_up_params and account_update_params
methods inside the RegistrationsController"
次にnew.html.erb
ファイルを開きます。
app/views/devise/registrations/new.html.erb
今度も同様に、フォーム以外のコードをすべて削除し、フォームをBootstrapのフォームに変えます。今回はname
フィールドを追加します(Gist)。
# views/devise/registrations/new.html.erb
<%= bootstrap_form_for(resource,
:as => resource_name,
:url => registration_path(resource_name)) do |f| %>
<%= f.text_field :name,
placeholder: 'username (will be shown publicly)',
class: 'form-control' %>
<%= f.text_field :email,
placeholder: 'email',
class: 'form-control' %>
<%= f.password_field :password,
placeholder: 'password',
class: 'form-control' %>
<%= f.password_field :password_confirmation,
placeholder: 'password confirmation',
class: 'form-control' %>
<%= f.submit 'Sign up', class: 'btn sign-up-button' %>
<% end %>
変更をcommitします。
git add -A
git commit -m "
Delete everything from the signup page, except the form.
Convert form into a bootstrap form. Add an additional name field"
フォームをBootstrapコンテナでラップし、テキストを追加します(Gist)。
# views/devise/registrations/new.html.erb
<div class="container" id="sign-up-form">
<div class="row">
<h1>Get in touch with like-minded people</h1>
<h3>Create, study, accomplish goals together</h3>
<div class="col-sm-offset-4 col-sm-4">
<h3>Sign up <small>it's free!</small></h3>
<!-- 上のフォームをここに貼り付ける -->
</div>
</div>
</div>
変更をcommitします。
git add -A
git commit -m "
Wrap the sign up form with a bootstrap container.
Add informational text inside the container"
ログインページのときと同様、サインアップページのURLもusers/sign_up
から/signup
に変えるのがよさそうです。routes.rb
ファイルに以下のコードを追加します。
devise_scope :user do
get 'signup', to: 'devise/registrations#new'
end
変更をcommitします。
git add -A
git commit -m "Change sign up page's route from /users/sign_up to /signup"
先に進む前に、スタイルを少し変更しておきましょう。app/assets/sytlesheets/partials
に移動してsignup.scss
ファイルを作成し、以下のCSSコードを追加します(Gist)。
// assets/stylesheets/partials/signup.scss
#sign-up-form {
margin-top: 100px;
h1 {
font-size: 36px !important;
font-size: 3.6rem !important;
}
text-align: center;
padding-bottom: 20px;
}
application.scss
ファイル内でpartials
からファイルをimportしていないので、ここでやっておきましょう。application.scss
ファイルを開いて、@import partials/layout/*
のすぐ上の行でpartials
ディレクトリからすべてのファイルをimportします。変更後のapplication.scss
は次のようになります(Gist)。
// assets/stylesheets/application.scss
// Partials - メインのcssファイル
@import "partials/*";
@import "partials/layout/*";
変更をcommitします。
git add -A
git commit -m "
- Create a signup.scss and add CSS to the sign up page
- Import all files from partials directory to the application.scss"
Webサイト全体の外観に別の変更を加えます。app/assets/stylesheets/base
ディレクトリに移動してdefault.scss
を作成し、以下のCSSコードを追加します(Gist)。
// assets/stylesheets/base/default.scss
* {
box-sizing: border-box;
}
html {
font-size: 62.5%;
}
body {
background: $backgroundColor;
font-size: 14px;
font-size: 1.4rem;
}
h1 {
font-size: 24px;
font-size: 2.4rem;
}
i {
width: 26px;
}
ul {
list-style-type: none;
}
a:hover, a:active, a:link, a:visited {
text-decoration: none;
}
.control-label {
display: none;
}
上ではWebサイト全体に一般的なスタイル変更をいくつか適用しています。font-size
を62.5%
に設定することで、単位1 rem
が10px
を表すようにします。rem
単位がよくわからない場合は、こちらのチュートリアルをご覧ください。Bootstrapフォームでラベル文字を表示したくないので、以下をCSSで設定しています。
.control-label {
display: none;
}
$backgroundColor
変数が使われていることにお気づきでしょうか。しかしこの変数はまだ設定されていないので、variables.scss
ファイルを開いて以下を追加します。
$backgroundColor: #f0f0f0;
default.scss
ファイルがapplication.scss
でまだimportされていないので、「// Variables」の下にimportを追加します。追加後のapplication.scss
ファイルは次のようになります。
// assets/stylesheets/application.scss
...
// Variables
@import "base/variables";
// Default styles
@import "base/default";
...
変更をcommitします。
git add -A
git commit -m "
Add CSS and import CSS files inside the main file
- Create a default.scss file and add CSS
- Define $backgroundColor variable
- Import default.scss file inside the application.scss"
ナビゲーションバーの更新
ここまでに、homeページ、ログインページ、サインアップページを作りました。このあたりで、これらのページを互いにつないでユーザーがWebサイトを楽に操作できるようにしましょう。ナビゲーションバーにサインアップページとログインページへのリンクを置くことにします。_navigation.html.erb
ファイルを開きます。
app/views/layouts/_navigation.html.erb
ここにコードを少し追加することにします。ここには今後さらにコードを追加する予定です。このままだとファイルのコード量が増え、管理やテストがやりにくくなってしまいます。長いコードを扱いやすくするために、もっと短いコード(チャンク)に分割することにします。本チュートリアルではパーシャルを使って行います。コードを追加する前に、現在の_navigation.html.erb
のコードをパーシャルに切り出してみましょう。
ここで、アプリのナビゲーションバーの使われ方について簡単に説明しておきます。ナビゲーションバーを2つに分け、一方は画面サイズにかかわらず常に表示し、他方は大画面のときだけ表示して小画面では隠すようにします。
.container
要素の内部は以下のような構造になる予定です(Gist)。
<!-- layouts/_navigation_html.erb -->
<div class="row">
<!-- 常に表示する要素 -->
<div class="col-sm-7">
</div><!-- col-sm-7 -->
<!-- 小画面デバイスで隠す要素 -->
<div class="col-sm-5">
</div><!-- col-sm-5 -->
</div><!-- row -->
app/views/layouts
ディレクトリの下にnavigation
ディレクトリを作成し、その下に_header.html.erb
パーシャルファイルを作成します。
app/views/layouts/navigation/_header.html.erb
_navigation.html.erb
ファイルの.navbar-header
セクションをまるごとカットして_header.html.erb
ファイルに貼り付けます。navigation
ディレクトリの下に別の_collapsible_elements.html.erb
パーシャルファイルを作成します。
app/views/layouts/navigation/_collapsible_elements.html.erb
_navigation.html.erb
ファイルの.navbar-collapse
セクションをまるごとカットして_collapsible_elements.html.erb
ファイルに貼り付けます。今度は2つのパーシャルを_navigation.html.erb
ファイルの内部でレンダリングしましょう。変更後のファイルは以下のようになります。
<!-- layouts/_navigation_html.erb -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="row">
<!-- 常に表示する要素 -->
<div class="col-sm-7">
<%= render 'layouts/navigation/header' %>
</div><!-- col-sm-7 -->
<!-- 小画面デバイスで隠す要素 -->
<div class="col-sm-5">
<%= render 'layouts/navigation/collapsible_elements' %>
</div><!-- col-sm-5 -->
</div><!-- row -->
</div><!-- container -->
</nav>
この時点でhttp://localhost:3000をブラウザで表示しても、表示結果は何も変わっていません。コードを少し整理して開発を進める準備を整えただけです。
ナビゲーションバーにリンクを追加する準備が整いました。_collapsible_elements.html.erb
ファイルを再度開きます。
app/views/layouts/_collapsible_elements.html.erb
このファイルにリンクを追加します。置き換え後のファイルは以下のようになります。
<!-- layouts/navigation/_collapsible_elements.html.erb -->
<!-- ナビゲーションリンク/フォームなどのコンテンツをここにまとめて表示をオンオフできるようにする -->
<div class="collapse navbar-collapse navbar-right" id="navbar-collapsible-content">
<ul class="nav navbar-nav ">
<% if user_signed_in? %>
<li class="dropdown pc-menu">
<a id="user-settings" class="dropdown-toggle" data-toggle="dropdown" href="#">
<span id="user-name"><%= current_user.name %></span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><%= link_to 'Edit Profile', edit_user_registration_path %></li>
<li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li>
</ul>
</li>
<li class="mobile-menu">
<%= link_to 'Edit Profile', edit_user_registration_path %>
</li>
<li class="mobile-menu">
<%= link_to 'Log out', destroy_user_session_path, method: :delete %>
</li>
<% else # ユーザーがサインインしていない場合 %>
<li ><%= link_to 'Login', login_path %></li>
<li ><%= link_to 'Signup', signup_path %></li>
<% end # ユーザーがサインインした場合 %>
</ul>
</div><!-- navbar-collapse -->
ここでコードの変更点について少し補足します。最初に、2行目で要素のid
をnavbar-collapsible-content
に変更しました。これはBootstrapの機能で、コンテンツを折りたたみ可能にするために必要です(デフォルトのid
はbs-example-navbar-collapse-1
)。この機能をトリガするために、data-target
属性を持つボタンが_header.html
ファイルにあります。views/layouts/navigation/_header.html.erb
ファイルを開いて、data-target
属性をdata-target="#navbar-collapsible-content"
に変更します。これで、ボタンも折りたたみ可能になりました。
次に、_collapsible_elements.html.erb
にif/else
ロジックがいくつかあり、そこにDeviseのuser_signed_in?
メソッドがあります。これは、ユーザーがサインインしているかどうかでリンクを変更するためのものです。ビューのこうしたif/else
ロジックを放置するのはよくありません。ビューは判断を一切行わず、ひたすら情報を吐き出す「静的な」記述に徹するべきです。このロジックは後ほどヘルパーを使ってリファクタリングする予定です。
最後に、このファイルにpc-menu
とmobile-menu
という2つのCSSクラスがあります。これらのクラスは、画面サイズが変わったときのリンクの表示を制御するのに使います。これらのクラスのCSSを追加してみましょう。app/assets/stylesheets
ディレクトリに移動してresponsive
ディレクトリを作成し(訳注: このディレクトリは前の手順で作成済みです)、そこにdesktop.scss
ファイルとmobile.scss
ファイルを作成します。Scssファイルを2つ作るのは、画面サイズごとに設定を変えるためです。desktop.scss
ファイルに以下を追加します(Gist)。
// assets/styleshseets/responsive/desktop.scss
@media screen and (min-width: 767px) {
.mobile-menu {
display: none !important;
}
}
mobile.scss
ファイルに以下を追加します(Gist)。
// assets/styleshseets/responsive/mobile.scss
@media screen and (max-width: 767px) {
.pc-menu {
display: none !important;
}
}
CSSのメディアクエリがよくわからない方はこちらをお読みください。
次に、responsive
ディレクトリの内容をapplication.scss
でimportします。以下のようにファイルの末尾にimportを追加します(Gist)。
// app/assets/stylesheets/application.scss
// メディアクエリ(レスポンシブデザイン用)
@import "responsive/*";
次はnavigation.scss
ファイルを開きます。
app/assets/stylesheets/partials/layout/navigation.scss
このファイルのnav
要素セレクタの内側に以下を追加して、ナビゲーションバーのスタイルを微調整します(Gist)。
// assets/styleshseets/partials/layout/navigation.scss
.col-sm-5, .col-sm-7 {
padding: 0;
}
続いて、nav
要素の外に以下のCSSコードを追加します(Gist)。
// assets/styleshseets/partials/layout/navigation.scss
.pc-menu {
margin-right: 10px;
}
.mobile-menu {
i {
color: white;
}
ul {
padding: 0px;
}
a {
display: block;
padding: 10px 0px 10px 25px !important;
}
a:hover {
background: white !important;
color: black !important;
i {
color: black;
}
}
}
.icon-bar {
background-color: white !important;
}
.active a {
background: $navbarColor !important;
border-bottom: solid 5px white;
}
.dropdown-toggle, .dropdown-menu {
background: $navbarColor !important;
border: none;
}
.dropdown-menu a:hover {
color: black !important;
background: white !important;
}
ここまでを終えると、アプリにユーザーがログインしていないときの外観は以下のようになっているはずです。
アプリにユーザーがログインしたときの外観は以下のようになります。
画面サイズを小さくしたときの外観は以下のようになります。
変更をcommitします。
git add -A
git commit -m "
Update the navigation bar
- Add login, signup, logout and edit profile links on the navigation bar
- Split _navigation.scss code into partials
- Create responsive directory inside the stylesheets directory and add CSS.
- Add CSS to tweak navigation bar style"
これで基本的な認証機能ができあがり、要求を満たせました。今度はauthentication
ブランチをmaster
ブランチにmergeしましょう。
git checkout master
git merge authentication
今度も変更の概要が表示されます。authentication
ブランチが不要になったので削除します。
git branch -D authentication