Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Đơn giản hóa Rails: Làm chủ `respond_to` cho Controller sạch hơn

Khi generate scaffold Rails, bạn thấy block respond_to quen thuộc:

app/controllers/tasks_controller.rb
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ó!

app/controllers/tasks_controller.rb
# 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:

app/controllers/tasks_controller.rb
# 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:

app/controllers/tasks_controller.rb
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:

app/controllers/tasks_controller.rb
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ì:

app/controllers/tasks_controller.rb
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:

app/controllers/tasks_controller.rb
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:

app/controllers/tasks_controller.rb
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:

app/controllers/tasks_controller.rb
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.