iT邦幫忙

DAY 10
0

從想法到快速實作的捷徑:Rails系列 第 10

[ Day 10 ][ Dev ] 從開發Po文功能認識MVC #3 完成post的CRUD

  • 分享至 

  • xImage
  •  

今天要完成的項目如下:

  • posts#create的重新導向

  • posts的編輯

  • 刪除單篇post(如果要刪除多篇會開另個篇幅來介紹)

  • 瀏覽所有的po文

先來回顧一下我們新建的posts controller:

class PostsController < ApplicationController

  def show
    @posts = Post.find(params[:id])
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
  end

  private

  def post_params
    params.require(:posts).permit(:title, :content, :note, :source)
  end

end

這裡如果實際打開local host會出現錯誤,

因為我們並沒有告訴rails新建完post之後該到哪個頁面去,

所以這裡我們會加上重新導向位址。

同時,我們之後會在post上面加上一些驗證,(比如說:將標題列為必填欄位)

創立post失敗的時候將會重新render new這個頁面,並且用flash給user看到錯誤訊息。

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post
      #redirect_to :action => "show", :id => @post
    else 
      render :new
    end
  end

這裡我們不用去指定action,只要直接傳入@post這個變數rails就能夠自己找到對應的show頁面,

如果你對原本的redirect有興趣,我也將它列在下方註釋起來。

再來就是edit和update這兩個跟更新有關的action,

把它擺在new和create後面介紹是因為它們幾乎是兩兩相對應的,

不同的是edit和update要先指定id才能進行動作,

概念上也非常直觀,因為new和create是新的物件,本來還不存在;

edit和update則是對現在已經存在的物件做更動,所以當然要先找到他們才能進行動作。

 def edit
    @post = Post.find(params[:id])
  end

  def update
    @post = Post.find(params[:id])
    if @post.update(post_params)
      redirect_to @post
    else 
      render :edit
    end
  end

這裡就不再重複一遍為什麼要render :edit了。

接著就是編輯的頁面了,你會發現edit和new兩個頁面幾乎長得是一模一樣,

所以這時候我們會把重複的部分抽出來做成partial view,

這是為了減少copy and paste,

同時以這個例子來說,如果你有新增或刪除什麼欄位的話,

就只要對partial view做更動就好。

做partial的方法很簡單,就是把重複的html code一起提出來就對了:

<%= simple_form_for @post do |f| %>
  <%= f.input :title %>
  <%= f.input :content %>
  <%= f.input :note %>
  <%= f.input :source %>
  <%= f.button :submit %>
<% end %>

命名的話會用底線(_)開頭,跟一般的view做區分,(否則不知道的人可能還會以為有form這個頁面)。

做好之後使用的方法也很簡單,下面以edit為例:

<h1>編輯文章</h1>
<%= render partial: "form" %>





<h1>新增文章</h1>
<%= render partial: "form" %>

再來就是index了,

這個頁面可以檢視我們目前所有的post,

  def index
    @posts = Post.all
  end
  • 如果未來有要對@posts增加搜尋或是其他條件限定的話,可以再加入,並不是index action底下就一定是ModelName.all這樣子的形式。

頁面這裡會用到each這個iterator,其實相當好理解。

我們假設現在有一個叫做items的array,

裡面有許許多多的item物件,假設我們要一一印出item的名字可以這樣做:

items.each do |item|
    puts item.name
end

||裡面可以任意命名,不過習慣上我們就會娶單數的名詞,

因為我們等於是一個一個把它拿出來做的,

從do到end中間的區塊裡面,都可以借由我們設定的名字來取用物件,

到end之後就換items中的下一個,一直到取完了為止。

做到這裡我們會發現,目前為止所有的操作都是要透過網址,

如果就這樣直接上去的話,將會是一個非常可怕的網站,

試著模擬一下:

你要看文章必須要知道文章的id,也就是當你新建完post之後還要先將id記下來,

才能連到post的show頁面以及對他做編輯。

所以我們將在這裡一步步把連結給加上去。

<h1>所有文章列表</h1>
<table>
  <thead>
    <th>標題</th>
    <th>內容</th>
    <th></th>
  </thead>
  <tbody>
    <% @posts.each do |post| %>
    <tr> 
      <td><%= link_to post.title, post_path(post) %></td>
      <td>
        <%= truncate(post.content, length: 14) %>
      </td>
      <td>
        <%= link_to "編輯", edit_post_path(post) %>
      </td>
      <td></td>
    </tr>
    <% end %>
  </tbody>
</table>

這裡值得一提的是我們現在是要瀏覽每則po文大概的樣子,

一則po文的content可能會到上百字,這樣全部load出來其實蠻有礙觀瞻的,

所以我們用了一個rails內建的helper,truncate

<%= truncate(post.content, length: 14) %>

在第一個參數放入要truncate的字串,後面其實應該是放入一個{},裡面有許多其他的設定,

但是ruby的語法裡面是可以省略括號的,

所以這裡直接用length: 14,如果想要客製化truncate後接上的東西可以用omission這個key。

想知道更多可以直接去看Rails的api:

http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-truncate

最後就只差刪除啦!

destroy這個action的長相是這樣:

  def destroy
    @post = Post.find(params[:id])
    @post.destroy
  end

路徑該怎麼設定呢?

我們先回顧一下bundel exec rake routes的結果:

GET /posts/:id(.:format) posts#show  
PATCH /posts/:id(.:format) posts#update  
PUT /posts/:id(.:format) posts#update  
DELETE /posts/:id(.:format) posts#destroy  

有沒有發現他們都共用post_path這個路徑,只是http method不同而已呢?

要改變這個在rails中是很簡單的事情,

只要在link_to中加上 method: :method\_type就可以了,

link\_to "刪除", post\_path(post), method: :delete


稍微暫停一下,我們記得Rails的設計哲學之一:DRY(Don't repeat yourself)

可是卻發現有一行程式碼重複了非常多次:

@post = Post.find(params[:id])

這裡可以把他抽出來放到find_post這個private的action中,

之後再從controller裡面的before_action去指定哪幾個action要呼叫(也可以寫before_filter)。

簡單重構後的code會長這樣子:

class PostsController < ApplicationController
  before_action :find_post, only: [:show, :edit, :update, :destroy]
  
  def index
    @posts = Post.all
  end

  def show
    
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post
    else 
      render :new
    end
  end

  def edit
    
  end

  def update
    if @post.update(post_params)
      redirect_to @post
    else 
      render :edit
    end
  end

  def destroy
    @post.destroy
    redirect_to posts_path 
  end

  private

  def post_params
    params.require(:post).permit(:title, :content, :note, :source)
  end

  def find_post
    @post = Post.find(params[:id])
  end

end

下一篇將要介紹rspec,簡單測試一下我們的CRUD到底行不行,

同時也會更認識rails的restful是怎麼一回事。


上一篇
[ Day 9 ][ Dev ] 從開發Po文功能認識MVC #2 新增一則po文
下一篇
[ Day 11 ][ Dev ] 更進一步認識Rails - rspec
系列文
從想法到快速實作的捷徑:Rails30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言