over 4 years ago

關於MVC

參考文章

什麼是controller

Controller主要是扮演橋樑的角色,負責跟Model要資料並把資料傳給View。另外一方面也會接收View傳來的各種http request傳給Model。

什麼東西應該放在controller

基本上屬於「過程中應該被處理」的動作都應該放在controller,比方說有一個view需要前20筆的Products資料,我們不可能把所有的Products都丟給view然後再在View裡面判斷前20筆,這樣會很悲劇。所以在這種情況下,這個View對應的Controller就要負責「跟Model拿資料」以及「只拿20筆資料」的動作。

這個感覺有點像是球團、球員和經紀人三者的關係:

球團扮演的角色是Model,負責出錢、辦比賽。

球員扮演的角色是View,負責拿錢、比賽。

而經紀人就是Controller,負責幫球員跟各個球團議價,讓球團跟球員能夠專注於自己的事情。

所以只要抱著Model處理資料庫,View處理前端呈現這樣的原則,就知道什麼東西應該放在controller了。

filters : before_filter, after_filter, around_filter

before_filter的意思就是要求rails在run controller下的action前要先跑指定的method,相對的after_filter就是跑完action後才要跑的method,至於around_filter就是之前之後都要跑(嘖嘖,真貪心)。以下為before_filter的示範:

class TopicsController < ApplicationController
    #rails4之後也可以使用before_action

  before_filter :find_board
  def index
    @topics = @board.topics
  end
  
  def find_board
    @board = Board.find(params[:id])
  end
end

我們在TopicsController上方要求它在每次執行action前都要find_board,所以就不用在index裡面再定義一次@board,如此一來若有很多action就不會有重複的程式碼產生。
此外我們也可以限定filter要作用的action,例如:

before_filter :find_board, :only => [:index]

這樣controller在執行時就只會針對index這個action執行find_board。

render :template

render的中文意思是給予,所以render template就是給予指定的模板,render的特性是不跑controller action,直接將該action下預設的模板傳出來,我們也可以自己指定要哪個特定的模板。
舉例說明:

#render特定的模板

#render views/foo/bar.html.erb

render "foo/bar"

#render同一個controller下的action的view,用symbol和string都是一樣的

render :foobar
render "foobar"

render :layout

rails預設的layout是app/view/layouts/application.html.erb這個檔案
但有時候我們會希望預設的版型不一樣,比方說我們的admin頁面head內不希望加上GA和一些有的沒的追蹤script
這時候我們就可以建立一個新的layout版型app/view/layouts/admin.html.erb
只要在controller中指定使用admin layout即可:

class AdminsController < ApplicationController
   layout "admin"
end

或是我們也可以指定某個action要使用admin layout:

class AdminsController < ApplicationController
   layout "admin", :only => :new
   # 另外也可以在render的時候就指定要使用哪一個layout

   def show 
      render :layout => "admin" 
   end
   
   # 甚至可以指定模板再指定layout

   def index
    render :template => "others/weired_topics", layout: "admin"
  end
end

大概就是這樣囉。

render :text

render純文字,沒有用過這個功能,但猜想若是要把複雜的code包在js裡面的時候,可能會出現單雙引號打架之類的問題,所以用render的方式吐出string也許是一種方法。

render "嗨,自己的文字自己render"

render options

Rails接受以下四種render options:

  • :content_type
    Rails預設會找text/html類型的檔案(除非我們有下:json:xml的選項)M但我們也可以藉由:content_type設定其他的檔案形態,例如:

    render file: filename, content_type: "application/rss"
    

    註記:content_type類型可Google MIME搜尋

    By default, Rails will serve the results of a rendering operation with the MIME content-type of text/html (or application/json if you use the :json option, or application/xml for the :xml option.). There are times when you might like to change this, and you can do so by setting the :content_type option

  • :layout
    文章上方已解釋過,可參考render :layout的部分

  • :location
    location header是HTTP通訊協定回應時幫client重新定位的方法,當client丟一個request到server,我們可以丟回location這個header將client導到別的地方,舉例:

    render xml: photo, location: photo_url(photo)
    

    這段code的意思是告訴client端說photo的xml檔要從photo_url(photo)這邊拿,所以假設我們有個ajax script要來拿這個檔案,它就會跑到photo_url(photo)拿。

  • :status
    指定server要丟什麼樣的http request status給client

    # 以下兩種寫法都可以
    
    render status: 500
    render status: :forbidden
    

    可以參考rails guide內的status列表:
    http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option

redirec_to 與 render

redirec_to通常使用在要讓使用者跳轉頁面的時候,會執行指定頁面的controller action。
render則是將指定頁面的樣板拿出來而已,並沒有執行controller。
通常render的使用時機是讓使用者回到同一個頁面,例如表單填寫不完全時再重回表單填寫頁,這樣做的原因是render會傳模板給使用者,而這個模板在使用者第一次送出表單時就已經被存起來了,所以render同一個模板的時候就會保留剛剛使用者打的表單資料,不用全部重打。相反的若使用redirect_to跳轉到同一個表單頁面就會是一個全新的模板,不會有任何送出前填寫的資料,所以適合在跳轉到不同頁面時使用。

respond_to 與 respond_with

respond_to可以用來回應不同的資料格式,如果使用者要get www.example.com/ex.xml,rails會先尋找`ex.xml.erb`和`ex.xml.buulder`,最後會找`ex.html.erb`的檔案。

def index
    @topics = Topic.all
    respond_to do |format|
        format.html
        format.xml { render :xml => @topics }
        format.json { render :json => @topics }
    end
end

respond_with則是rails3之後才有的做法,可以先告知controller respond_to支援哪幾種檔案形態,如此一來在action內只要寫respond_with就可以了,因此可將上方的例子寫成下面的樣子:

respond_to :html, :xml, :json
def index
    @topics = Topic.all
    respond_with(@topics)
end

respond_with也可以寫成block,並在block內重新定義預設的render行為:

respond_to :html, :xml, :json
def index
    @topics = Topic.all
    respond_with(@topics) do |format|
        format.html { redirect_to root_path }
    end
end

builders

builders是一種Template Handler,跟erb是很像的東西,erb會幫我們把內容轉化為瀏覽器看得懂的html或js等,而builder也是做一樣的事情,只是大家習慣使用erb作為html和js的handler,使用builder作為xml、rss、atom的handler,原因待查。

補充:

Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object named xml is automatically made available to templates with a .builder extension.

Action View Base http://api.rubyonrails.org/classes/ActionView/Base.html/…

所謂的more programmatic alternative to ERB應該是指它是以更程式的邏輯去generate出xml file,所以更能展現tag間彼此的相對關係的意思吧,見下方例子:

app/views/topics/show.xml.builder
xml.topic do |t|
  t.title @topic.title
  t.content @topic.content
end

會產出這樣的xml

<topic>
  <title>Topic Title</title>
  <content>Topic Content Here</content>
</topic>

這也許是其中一個原因,不過總之就先照rails建議的convention做囉。

← [Rails 高級新手系列] 關於MVC-什麼東西應該放在View [Rails 高級新手系列] 關於MVC-什麼東西應該放在Model →
 
comments powered by Disqus