概要
- 前回: Rails5「中級」チュートリアル(3-7)投稿機能: Service Object(翻訳)
- 次回: Rails5「中級」チュートリアル(4-1-1)インスタントメッセージ: 非公開チャット - 前編(翻訳)
概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: The Ultimate Intermediate Ruby on Rails Tutorial: Let’s Create an Entire App!
- 原文公開日: 2017/12/17
- 著者: Domantas G
Rails5中級チュートリアルはセットアップが短めで、RDBMSにはPostgreSQL、テストにはRSpecを用います。
原文が非常に長いので分割します。章ごとのリンクは順次追加します。
翻訳と同時に動作をRails 5.1とRuby 2.5で検証しています。
注意: Rails中級チュートリアルは、Ruby on Railsチュートリアル(https://railstutorial.jp/)(Railsチュートリアル)とは著者も対象読者も異なります。
目次
- 1. 序章とセットアップ
- 2. レイアウト
- 3. 投稿
- 4. インスタントメッセージ
- 4-1 非公開チャット
- 4-1-1 前編
- 4-1-2 中編
- 4-1-3 後編
- 4-2 連絡先
- 4-3 グループチャット
- 4-4 メッセンジャー
- 4-1 非公開チャット
- 5. 通知
- 5-1 つながりリクエスト
- 5-2 チャット
Rails5「中級」チュートリアル(3-8)投稿機能: 新しい投稿を作成する(翻訳)
ここまでの投稿はseedで作った人工的なものでした。今度はユーザーが投稿できるようにユーザーインターフェイスを追加しましょう。
posts_controller.rb
ファイルにnew
アクションとcreate
アクションを追加します(Gist)。
# controllers/posts_controller.rb
...
def new
@branch = params[:branch]
@categories = Category.where(branch: @branch)
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to post_path(@post)
else
redirect_to root_path
end
end
...
new
アクションでは投稿を作成するフォームで用いるインスタンス変数をいくつか定義しています。@categories
インスタンス変数の内部には特定のブランチのカテゴリが保存されます。@post
インスタンス変数には、Railsフォームで必要となる新しい投稿のオブジェクトが保存されます。
create
アクションでは、post_params
メソッドでデータを持たせたPost
オブジェクトを新規作成し、@post
に保存します。このpost_params
メソッドは次のようにprivate
スコープ内で定義します(Gist)。
# controllers/posts_controller.rb
...
def post_params
params.require(:post).permit(:content, :title, :category_id)
.merge(user_id: current_user.id)
end
...
この[permit](https://apidock.com/rails/ActionController/Parameters/permit)
メソッドは、オブジェクトの属性をホワイトリスト化するのに使われます。これにより、指定の属性を渡すことを明示的に許可します。
PostsController
の冒頭に次の行を追加します(Gist)。
# controllers/posts_controller.rb
...
before_action :redirect_if_not_signed_in, only: [:new]
...
このbefore_action
はRailsのフィルタです。サインインしていないユーザーが投稿を作成するページにアクセスできるようにしたくありません。そのために、new
アクションが呼ばれる前にこのredirect_if_not_signed_in
メソッドが呼び出されます。このメソッドは他のコントローラにも同様に必要になるので、application_controller.rb
ファイルにこのメソッドを定義しておきましょう。ついでにサインインしているユーザーをリダイレクトするメソッドもあれば今後便利なので、どちらも定義しておきましょう(Gist)。
# controllers/application_controller.rb
...
def redirect_if_not_signed_in
redirect_to root_path if !user_signed_in?
end
def redirect_if_signed_in
redirect_to root_path if user_signed_in?
end
...
ユーザーが投稿を作成するには、new
のテンプレートが必要です。post
ディレクトリの下にnew.html.erb
ファイルを作成します(Gist)。
<!--- posts/new.html.erb -->
<div class="container new-post">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<h1>Create a new post</h1>
<%= render 'posts/new/post_form' %>
</div>
</div>
</div>
new
ディレクトリを作成し、その下に_post_form.html.erb
ファイルを作成します(Gist)。
<!-- posts/new/_post_form.html.erb -->
<%= bootstrap_form_for(@post) do |f| %>
<%= f.text_field :title,
maxlength: 100,
placeholder: 'Title',
class: 'form-control',
required: true,
minlength: 5,
maxlength: 100 %>
<%= f.hidden_field :branch, :value => @branch %>
<%= f.text_area :content,
rows: 6,
required: true,
minlength: 20,
maxlength: 1000,
placeholder: 'Describe what you are looking for. E.g. specific interests, expertise level, etc.',
class: 'form-control' %>
<%= f.collection_select :category_id, @categories, :id, :name, class: 'form-control' %>
<%= f.submit "Create a post", class: 'form-control' %>
<% end %>
このフォームはかなり素朴な作りです。フィールドの属性を定義し、collection_select
でカテゴリを1つ選択できるようにしています。
変更をcommitします。
git add -A
git commit -m "Create a UI to create new posts
- Inside the PostsController:
define new and create actions
define a post_params method
define a before_action filter
- Inside the ApplicationController:
define a redirect_if_not_signed_in method
define a redirect_if_signed_in method
- Create a new template for posts"
フォームをテストするspecを書いてテストします。特定のリクエストを送信後に正しいレスポンスを得られることを確認するため、request specsから書くことにします。spec
ディレクトリの下に以下のディレクトリを作成します。
spec/requests/posts
その下にnew_spec.rb
ファイルを作成します(Gist)。
# spec/requests/posts/new_spec.rb
require 'rails_helper'
include Warden::Test::Helpers
RSpec.describe "new", :type => :request do
context 'non-signed in user' do
it 'redirects to a root path' do
get '/posts/new'
expect(response).to redirect_to(root_path)
end
end
context 'signed in user' do
let(:user) { create(:user) }
before(:each) { login_as user }
it 'renders a new template' do
get '/posts/new'
expect(response).to render_template(:new)
end
end
end
前述したように、request specは結合テストの薄いラッパーを提供しているので、特定のリクエストが送信されたときに正しいレスポンスを取得できるかどうかをテストすることができます。include Warden::Test::Helpers
の行は、テスト用のログインを行うlogin_as
メソッドを使うために必要になります。
変更をcommitします。
git add -A
git commit -m "Add request specs for a new post template"
これまで作成したページをテストするrequest specを追加することもできます。
同じディレクトリにbranches_spec.rb
ファイルを作成します(Gist)。
# spec/requests/posts/branches_spec.rb
require 'rails_helper'
include Warden::Test::Helpers
RSpec.describe "branches", :type => :request do
shared_examples 'render_templates' do
it 'renders a hobby template' do
get '/posts/hobby'
expect(response).to render_template(:hobby)
end
it 'renders a study template' do
get '/posts/study'
expect(response).to render_template(:study)
end
it 'renders a team template' do
get '/posts/team'
expect(response).to render_template(:team)
end
end
context 'non-signed in user' do
it_behaves_like 'render_templates'
end
context 'signed in user' do
let(:user) { create(:user) }
before(:each) { login_as user }
it_behaves_like 'render_templates'
end
end
このようにして、すべてのブランチページのテンプレートがレンダリングできることをチェックします。同じコードを繰り返し避けるために[shared_examples](https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples)
も使っています。
変更をcommitします。
git add -A
git commit -m "Add request specs for Posts branch pages' templates"
同様に、show
テンプレートもレンダリングできることを確認します。同じディレクトリにshow_spec.rb
ファイルを作成します(Gist)。
# spec/requests/posts/show_spec.rb
require 'rails_helper'
include Warden::Test::Helpers
RSpec.describe "show", :type => :request do
shared_examples 'render_show_template' do
let(:post) { create(:post) }
it 'renders a show template' do
get post_path(post)
expect(response).to render_template(:show)
end
end
context 'non-signed in user' do
it_behaves_like 'render_show_template'
end
context 'signed in user' do
let(:user) { create(:user) }
before(:each) { login_as user }
it_behaves_like 'render_show_template'
end
end
変更をcommitします。
git add -A
git commit -m "Add request specs for the Posts show template"
今度はユーザーが新しい投稿を作成できることを確かめるために、フォームをテストするfeature specを作成しましょう。features/posts
ディレクトリの下にcreate_new_post_spec.rb
ファイルを作成します(Gist)。
# spec/features/posts/create_new_post_spec.rb
require "rails_helper"
RSpec.feature "Create a new post", :type => :feature do
let(:user) { create(:user) }
before(:each) { sign_in user }
shared_examples 'user creates a new post' do |branch|
scenario 'successfully' do
create(:category, name: 'category', branch: branch)
visit send("#{branch}_posts_path")
find('.new-post-button').click
fill_in 'post[title]', with: 'a' * 20
fill_in 'post[content]', with: 'a' * 20
select 'category', from: 'post[category_id]'
click_on 'Create a post'
expect(page).to have_selector('h3', text: 'a' * 20)
end
end
include_examples 'user creates a new post', 'hobby'
include_examples 'user creates a new post', 'study'
include_examples 'user creates a new post', 'team'
end
変更をcommitします。
git add -A
git commit -m "Create a create_new_post_spec.rb file with feature specs"
new
テンプレートに少し新しいデザインを適用しましょう。
以下のディレクトリに移動します。
assets/stylesheets/partials/posts
new.scss
ファイルを作成します(Gist)。
// assets/stylesheets/partials/posts/new.scss
.new-post {
height: calc(100vh - 50px);
background-color: white;
h1 {
text-align: center;
margin: 25px 0;
}
input, textarea, select {
width: 100%;
}
}
ブラウザでこのテンプレートを開くと、以下のような基本フォームが表示されるはずです。
変更をcommitします。
git add -A
git commit -m "Add CSS to the Posts new.html.erb template"
最後に、すべてのフィールドに正しく入力されるようにしたいと思います。Post
モデルにいくつかバリデーションを追加しましょう。Post
モデルに以下のコードを追加します(Gist)。
# models/post.rb
...
validates :title, presence: true, length: { minimum: 5, maximum: 255 }
validates :content, presence: true, length: { minimum: 20, maximum: 1000 }
validates :category_id, presence: true
...
変更をcommitします。
git add -A
git commit -m "Add validations to the Post model"
バリデーションをspecでカバーしましょう。Post
モデルのspecファイルを開きます。
spec/models/post_spec.rb
以下を追加します(Gist)。
# spec/models/post_spec.rb
context 'Validations' do
let(:post) { build(:post) }
it 'creates successfully' do
expect(post).to be_valid
end
it 'is not valid without a category' do
post.category_id = nil
expect(post).not_to be_valid
end
it 'is not valid without a title' do
post.title = nil
expect(post).not_to be_valid
end
it 'is not valid without a user_id' do
post.user_id = nil
expect(post).not_to be_valid
end
it 'is not valid with a title, shorter than 5 characters' do
post.title = 'a' * 4
expect(post).not_to be_valid
end
it 'is not valid with a title, longer than 255 characters' do
post.title = 'a' * 260
expect(post).not_to be_valid
end
it 'is not valid without a content' do
post.content = nil
expect(post).not_to be_valid
end
it 'is not valid with a content, shorter than 20 characters' do
post.content = 'a' * 10
expect(post).not_to be_valid
end
it 'is not valid with a content, longer than 1000 characters' do
post.content = 'a' * 1050
expect(post).not_to be_valid
end
end
変更をcommitします。
git add -A
git commit -m "Add specs for the Post model's validations"
specific_branches
ブランチをmaster
ブランチにmergeします。
git checkout -b master
git merge specific_branches
git branch -D specific_branches
- 前回: Rails5「中級」チュートリアル(3-7)投稿機能: Service Object(翻訳)
- 次回: Rails5「中級」チュートリアル(4-1-1)インスタントメッセージ: 非公開チャット - 前編(翻訳)