Creating microposts

Chapter 7 建立了表單來實現登入,表單會向 Users controller 的 create action 發送 POST 請求。建立 micropost 的功能也類似這樣,不同的地方是,表單不會放在單獨的 /microposts/new 頁面上,而是在網站的首頁(例如 root path:/),mockup 如下:

之前當我們離開首頁時,畫面會長得像這樣:

也就是會有「Sign up」按鈕顯示在中間。因為只有已登入的使用者才能建立新的 micropost,這節的目標就是要根據使用者的登入狀態來顯示不同的首頁內容。

我們要先來定義 Microposts controller 的 create action,它和 Users controller 的 create action 類似,不同的是,建立 micropost 時,要使用 user/micropost 的關聯關係來建立 micropost 物件,如下所示,注意,micropost_params 中的 strong parameters 只允許透過 web 來修改 micropost 的 content 屬性:

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end

為了建立能產生 microposts 的表單,使用了以下的程式碼,也就是根據使用者登入狀態來顯示不同的 HTML 內容:

app/views/static_pages/home.html.erb

<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>
  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails logo"),
              'http://rubyonrails.org/' %>
<% end %>

if-else 的分支上使用這麼多程式碼有點雜亂,之後會使用 partial 來整理,留做練習題。

為了讓上述程式碼能夠執行,我們還要建立兩個 partial,首先是首頁的側邊欄:

app/views/shared/_user_info.html.erb

<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>

注意,和 profile 側邊欄一樣,上述的 user info 顯示使用者的所有 microposts 數量,不過顯示上有個細微的差別是,在 profile 側邊欄,「Microposts」是標示,所以顯示「Microposts(1)」是合理的;在本例中,如果是「1 microposts」就不合語法了,所以我們調用了 pluralize 方法,顯示成「1 micropost」、「2 microposts」等。

接下來我們來建立產生 microposts 的表單,跟註冊表單有點類似:

app/views/shared/_micropost_form.html.erb

<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>

為了讓上述表單能夠執行,我們要做兩件事,(和之前一樣)第一我們要透過關聯定義 @micropost

@micropost = current_user.microposts.build

把上述程式碼加進 controller 裡:

app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController

  def home
    @micropost = current_user.microposts.build if logged_in?
  end

  def help
  end

  def about
  end

  def contact
  end
end

因為只有使用者登入後,current_user 才存在,所以 @micropost 變數只能在使用者登入後才能定義。

第二,要重新定義 error-message partial:

<%= render 'shared/error_messages', object: f.object %>

回想之前的 error-message partial 是直接引用了 @user 變數,但我們現在提供的變數是 @micropost。為了在這兩個地方都能使用 error-message partial,我們可以把表單變數 f 傳進 partial,透過 f.object 取得相對應的物件,所以在:

form_for(@user) do |f|

f.object@user,然後在:

form_for(@micropost) do |f|

f.object@micropost

我們透過一個 hash 把物件傳進 partial,value 是這個物件,key 是 partial 需要的變數名稱,就像剛剛上面我們定義的 <%= render 'shared/error_messages', object: f.object %> 所示。

換句話說,object: f.object 會建立一個叫 object 的變數,提供給 error_message partial 使用,然後我們就可以透過這個物件,自訂 error message:

app/views/shared/_error_messages.html.erb

<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

現在執行測試會失敗(Red):

$ bundle exec rake test

這個失敗測試提示我們要更新其他有使用 error-message partial 的 view。包括使用者註冊、重設密碼、編輯使用者資料。以下是更新 view 的程式碼:

更新使用者註冊表單中渲染錯誤訊息的方式 app/views/users/new.html.erb

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

更新編輯使用者表單中渲染錯誤訊息的方式 app/views/users/edit.html.erb

<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails">change</a>
    </div>
  </div>
</div>

更新密碼重設表單中渲染錯誤訊息的方式 app/views/password_resets/edit.html.erb

<% provide(:title, 'Reset password') %>
<h1>Password reset</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

現在執行測試就會通過了(Green):

$ bundle exec rake test

此外,本節的 HTML 都應該能正確顯示了,以下是建立 micropost 的表單:

這是顯示提交表單後有一個錯誤:

這是成功建立的訊息,原本「50 microposts」更新為「51 microposts」: