Essentail Rails Design Pattern

Write Good Rails Code

RESTful

Rails 在 1.2 版本的時候引進了 RESTful 這一套設計風格。而在 2.0 版強迫正式成為開發預設值。

說到 RESTful,幾乎每個剛踏進 Rails 框架中的開發者都皺起眉頭。幾乎沒有人能夠在初學階段搞的懂這是什麼玩意。偏偏初學者往往有一個「死扣細節」的壞習慣:沒「弄懂」就「不敢用」,結果在第一個關卡挫折感就堆的如山高,把玩 RESTful 後打退堂鼓,回頭大罵爛設計的人比比皆是。

想學 Rails 就先把 RESTful 背起來

RESTful 是 Rails 裡面使用的最基礎最頻繁的一個設計手法,偏偏它也是最難一次講解清楚讓剛入門者理解的一個主題。因為在傳統 web 應用程式開發流程中,根本沒有這樣的概念。更別提在市面上這麼多網頁框架,只有 Rails 將之視為預設值。

別說是初學者,就連筆者和一些 Rails 界的前輩,在 Rails 剛納入 RESTful 為預設風格時,也沒能通透其中原理。

但是不熟練 RESTful 的開發架構,在開發 Rails 時幾乎會寸步難行。在訓練新 Developer 時如何讓他短時間就熟練便上手呢?

我的方法很簡單:強迫他背起來,然後重複寫十遍。

聽起來很唬爛,但練過十遍以後。這時候再重新講解一次 RESTful 的概念,原本模糊的概念一下就變得豁然開朗了。

初步熟悉使用 Rails RESTful

在 config/routes.rb 內加入 resources

在 config/routes.rb 加入 resources :posts,就是具體在 rails 對一個 controller 實作 RESTful 的方式(對一個 controller 宣告成為 resources)。

1
2
3
Demo::Application.routes.draw do
  resources :posts
end

這樣的宣告將在自動對應 URL路徑跟 Controller 的 action ,而有以下的結果:

  • GET: /posts => [:action => ‘index’]
  • GET: /posts.xml => [:action => ‘index’, :format => ‘xml’]
  • GET: /posts/1 => [:action => ‘show’, :id => 1]
  • GET: /posts/1/edit => [:action => ‘edit’, :id => 1]
  • GET: /posts/1.xml => [:action => ‘show’, :id => 1, :format => ‘xml’]
  • POST: /posts => [:action => ‘create’]
  • PUT: /posts/1 => [:action => ‘update’, :id => 1]
  • DELETE: /posts/1 => [:action => ‘destroy’, :id => 1]

並產生了一套 url helpers 給 View 使用。

helpers HTTP Verb 路徑 Action
posts_path GET /posts index
post_path(id) GET /posts/1 show
new_post_path GET /posts/new new
posts_path POST /posts create
edit_post_path(id) GET /posts/1/edit edit
bloh_path(id) PUT /posts/1 update
post_path(id) DELETE /posts/1 destroy


各 action 內部的動作以及對應 path

以下透過專案中使用 RESTful 應該會出現的程式碼作講解。讀者可以透過 rails generate scaffold Post subject:string content:text 看到這些程式碼。

index

通常會放置列表。因此 Post.all 是拉出 Post 這個 model 所有的資料。

1
2
3
  def index
    @posts = Post.all
  end

show

秀出單筆資料。 Post.find(123) 是指找 Post model 裡 id 為 123 的資料。http://demosite.com/posts/show/123 的 posts 是 controller、show 是 action;如果在 route 裡面沒有特別指定,則 123 通常就是 params[:id] 。

1
2
3
  def show
    @post = Post.find(params[:id])
  end

new

initial 一個新的 Post object。

1
2
3
  def new
    @post = Post.new
  end

edit

query 出指定的 Post model object,然後進行編輯。

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

create

這邊要搭配 app/views/posts/new.html.erb 這個 view 並列一起看。

1
2
3
4
5
6
7
8
9
def create
  @post = Post.new(params[:post])
  if @post.save
    flash[:notice] = 'Post was successfully created.'
    redirect_to post_path(@post)
  else
    render :action => "new"
  end
end
1
2
3
4
5
6
7
8
9
10
11
<h1>New post</h1>

<%= form_for @post , :url => posts_path do |f| %>
    <%= f.error_messages %>
    <div><label>subject</label><%= f.text_field :subject %></div>
    <div><label>content</label><%= f.text_area :content %> </div>
    <%= f.submit "Submit", :disable_with => 'Submiting...' %>
<% end -%>


<%= link_to 'Back', posts_path %>

在 RESTful Rails 的寫法中,對 posts_path 丟 POST 就是對應到 create 的動作。而 Rails 在設計上,form 是綁 model 的,因此整個 form 的內容會被包成一個 hash,在這裡就是 params[:post]。create action 初始一個 object ,並把 params[:post] 整包塞進這個 object 裡。如果 @post 能夠成功的儲存,就「重導」到 index action ,失敗則「退回」到 new action 。

update

這邊也要翻回 app/views/posts/edit.html.erb 這個 view 一起來看。

1
2
3
4
5
6
7
8
9
def update
    @post = Post.find(params[:id])
    if @post.update_attributes(params[:post])
     flash[:notice] = 'Post was successfully updated.'
     redirect_to post_path(@post)
    else
      format.html { render :action => "edit" }
    end
  end
1
2
3
4
5
6
7
8
9
10
<h1>Editing post</h1>
<%= form_for @post , :url => post_path(@post) , :html => {:method => :put} do |f| %>
    <%= f.error_messages %>
    <div><%= f.text_field :subject %>
    <div><%= f.text_area :content %></div>
    <%= f.submit "Submit", :disable_with => 'Submiting...' %>
<% end -%>

<%= link_to 'Show', post_path(post) %> |
<%= link_to 'Back', posts_path %>

在 RESTful Rails 的寫法中,對 post_path 丟 PUT 就是對應到 update 的動作。form 會背包成一個 hash,如果 @post 能夠吃進 params[:post] 進行更新且成功儲存,就會「重導」到 show action,失敗則「退回」到 edit action。

delete

找到該筆資料並刪除之。在 RESTful Rails 的寫法中,對 post_path 丟一個 DELETE,就會對應到 destroy 的動作。可看一下 link_to "Destroy"那一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h1>My post</h1>
<h2><%= link_to 'New post', new_post_path %></h2>
<table>
  <tr>
  </tr>

<% @posts.each do |post| %>
  <tr>
    <td><%= post.subject %> </td>
    <td><%= link_to 'Show', post_path(post) %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy',  post_path(post), :method => :delete %></td>
  </tr>
<% end %>
</table>

還是不理解為什麼要這樣寫?沒關係,先練習十遍。練到不需要思考就能夠手寫這些程式碼再說。

Comments