ตอน generate scaffold ใน Rails จะเห็น respond_to blocks ปกติ:
def destroy @task.destroy respond_to do |format| format.html { redirect_to tasks_url, notice: 'Task was successfully destroyed.' } format.json { head :no_content } end end แตบาง actions เช่น index ไม่มี!
# GET /tasks # GET /tasks.json def index @tasks = Task.all end นี่แย่ ทำไม? ถ้า hit /tasks.txt และ txt ไม่ supported แอป จะได้ error ผิด:
ActionView::MissingTemplate (Missing template tasks/index, application/index with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]} ไม่ควรเป็นแบบนี้ ควรบอก client ว่าพวกเขา request format ที่ไม่ support ไม่ใช่ว่าหา file ไม่เจอ
ถ้าเป็น UnknownFormat error ส่ง response code ดีกว่า แทนที่จะรวมกับ errors อื่นๆ และยากจัดการ
ใส่ respond_to block ใน index ได้:
# GET /tasks # GET /tasks.json def index @tasks = Task.all respond_to do |format| format.html format.json end end ได้ exception และ error code ที่คาดหวัง:
Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:05:12 -0800 Processing by TasksController#index as TEXT Completed 406 Not Acceptable in 21ms ActionController::UnknownFormat (ActionController::UnknownFormat): app/controllers/tasks_controller.rb:8:in `index' ดีกว่ามาก แต่กระจาย respond_to ทุก controllers แบบนั้น crazy ไม่ Rails-ish ละเมิด DRY และเบี่ยงเบนจากงานจริงของ controller
ยังอยากจัดการ bad formats ถูกต้อง ทำยังไง?
respond_to Shortcut
ถ้าไม่ทำอะไรพิเศษ render objects สามารถ shortcut ได้ เขียน:
def index @tasks = Task.all respond_to :html, :json end ทำงานเหมือน full respond_to block ใน index วิธีสั้นบอก Rails formats ที่ action รู้จัก และ actions ต่างๆ support formats ต่างๆ วิธีนี้จัดการได้โดยไม่โค้ดมาก
จัดการ Formats ที่ Controller Level
ปกติ actions ใน controller ทำงานกับ formats เดียวกัน ถ้า index respond json new, create และอื่นๆ ก็จะ respond ดังนั้น ถ้ามี respond_to ที่ affect ทั้ง controller จะดี:
class TasksController < ApplicationController before_action :set_task, only: [:show, :edit, :update, :destroy] respond_to :html, :json # GET /tasks # GET /tasks.json def index @tasks = Task.all respond_with(@tasks) end และมันทำงานจริง:
Started GET "/tasks.txt" for 127.0.0.1 at 2014-11-03 22:17:37 -0800 Processing by TasksController#index as TEXT Completed 406 Not Acceptable in 7ms ActionController::UnknownFormat (ActionController::UnknownFormat): app/controllers/tasks_controller.rb:8:in `index' error แบบที่หวังพอดี! และไม่ต้องแก้แต่ละ action
บางครั้งอยากทำอย่างต่างกันตาม state ของ model เช่น create redirect หรือ re-render form ขึ้นกับว่า model valid หรือไม่
Rails จัดการได้ แตยังต้องบอกว่า check object ไหน ด้วย respond_with แทนที่จะ:
def create @task = Task.new(task_params) respond_to do |format| if @task.save format.html { redirect_to @task, notice: 'Task was successfully created.' } format.json { render :show, status: :created, location: @task } else format.html { render :new } format.json { render json: @task.errors, status: :unprocessable_entity } end end end เขียนได้:
def create @task = Task.new(task_params) flash[:notice] = "Task was successfully created." if @task.save respond_with(@task) end วิธีนี้ แยกโค้ดจาก formats ที่ respond บอก Rails ครั้งเดียว formats ที่อยากจัดการ ไม่ต้อง repeat ทุก action
The Responders Gem
ใน Rails 4.2 มี catch: respond_with ไม่ included แล้ว แตติดตั้ง responders gem ได้กลับมา และ responders gem นำฟีเจอร์ดีๆ อื่นมาด้วย
Set flash messages ใน respond_with ได้ด้วย responders :flash บน controller:
class TasksController < ApplicationController responders :flash สะดวก ตั้งค่า default flash messages ใน locale files ได้
ด้วย responders gem ใน Gemfile และ generate scaffold จะสร้าง controllers 用 respond_with แทน respond_to:
class TasksController < ApplicationController before_action :set_task, only: [:show, :edit, :update, :destroy] respond_to :html, :json def index @tasks = Task.all respond_with(@tasks) end def show respond_with(@task) end # ... สะอาดกว่า scaffold ที่ Rails มาพร้อม
และสุดท้าย ถ้าอยาก respond format เฉพาะ actions เรียก respond_to หลายครั้ง:
class TasksController < ApplicationController respond_to :html respond_to :js, only: :create end ขอบคุณ Jeroen Weeink ในคอมเมนต์สำหรับ tip สุดท้าย!
respond_with หรือ respond_to?
ถ้าอยาก return ข้อมูลต่างกันตาม formats มีตัวเลือก Controller-level respond_to กับ respond_with ทำ controllers สั้น ได้ผลดีตอน actions ทั้งหมด respond format เดียวกัน และทำงานแบบ Rails คาดหวัง
แตบางครั้งอยาก actions บางตัวทำงานต่างออกไป One-liner respond_to ดีสำหรับกรณีนี้
ถ้าต้องการ control มากกว่า ใช้ full respond_to block จัดการแต่ละ format ตามใจ
ด้วยอันไหนก็ตาม requests สำหรับ formats ที่ไม่ support จะได้ error ถูกต้อง และทั้งแอปและ clients จะสับสนน้อยลงมาก