Khi generate scaffold Rails, bạn thấy block respond_to quen thuộc:
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
Nhưng một số action như index không có!
# GET /tasks
# GET /tasks.json
def index
@tasks = Task.all
end
Điều này xấu. Tại sao? Nếu truy cập /tasks.txt, và txt không được support, bạn nhận sai error:
ActionView::MissingTemplate (Missing template tasks/index, application/index with {:locale=>[:en], :formats=>[:text], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]})
Điều này không đúng. Bạn nên báo client họ request format không support, không phải không tìm thấy file.
Nếu là error UnknownFormat, bạn trả response code tốt hơn. Thay vào đó, error này lẫn với error khác không liên quan, rất khó xử lý.
Bạn có thể thêm respond_to vào index:
# GET /tasks
# GET /tasks.json
def index
@tasks = Task.all
respond_to do |format|
format.html
format.json
end
end
Khi đó, bạn nhận exception và error code kỳ vọng:
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'
Tốt hơn nhiều. Nhưng rải rác respond_to khắp controller là điên rồ. Cảm giác không Rails-ish. Vi phạm DRY. Và phân tâm bạn khỏi công việc thực của controller.
Vẫn muốn xử lý format sai đúng. Làm sao?
Shortcut respond_to
Nếu không làm gì đặc biệt render object, bạn có shortcut. Nếu viết:
def index
@tasks = Task.all
respond_to :html, :json
end
hoạt động giống block respond_to đầy đủ trong index. Cách ngắn gọn báo Rails action biết format nào. Nếu action khác support format khác, đây là cách xử lý sự khác biệt đó mà không nhiều code.
Xử lý format ở cấp controller
Thường thì mỗi action trong controller dùng cùng format. Nếu index respond json, new, create, và cái khác cũng vậy. Hay nếu có respond_to ảnh hưởng toàn 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
Và nó chạy:
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'
Chính xác error mong muốn! Không phải sửa từng action.
Đôi khi muốn làm khác nhau tùy state model. Ví dụ create, redirect hoặc render lại form, tùy model valid hay không.
Rails xử lý được. Nhưng vẫn phải nói object nào check, bằng respond_with. Thay vì:
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
Bạn viết:
def create
@task = Task.new(task_params)
flash[:notice] = "Task was successfully created." if @task.save
respond_with(@task)
end
Cách này, tách code khỏi format respond. Bạn báo Rails một lần format muốn handle. Không phải lặp lại mỗi action.
Gem responders
Rails 4.2 có chút bắt: respond_with không còn included. Nhưng lấy lại được nếu cài gem responders. Gem này mang thêm tính năng hay.
Set flash message trong respond_with bằng responders :flash đầu controller:
class TasksController < ApplicationController
responders :flash
Tiện lợi, bạn set default flash message trong locale file.
Cũng, nếu có responders gem trong Gemfile và generate scaffold, generator tạo controller dùng respond_with thay 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
# ...
Sạch hơn scaffold mặc định Rails.
Cuối cùng, nếu muốn respond format chỉ cho action cụ thể, gọi respond_to nhiều lần:
class TasksController < ApplicationController
respond_to :html
respond_to :js, only: :create
end
Cảm ơn Jeroen Weeink trong comment cho tip cuối!
respond_with hay respond_to?
Nếu muốn trả thông tin khác nhau cho format khác, có vài lựa chọn. Controller-level respond_to kết hợp respond_with cho controller ngắn gọn. Nhưng giúp nhiều nhất khi tất cả action respond cùng format, và hành động như Rails kỳ vọng.
Đôi khi, muốn vài action hành động khác. One-liner respond_to tuyệt vời cho trường hợp đó.
Nếu cần control hơn, dùng full respond_to với block, handle từng format tùy ý.
Với bất kỳ cái nào, request format không support sẽ nhận đúng error. Và cả app lẫn client sẽ bớt bối rối.