edge
เพิ่มเติมที่ rubyonrails.org: เพิ่มเติมเกี่ยวกับ Ruby on Rails

ภาพรวมของ Action Controller

ในเอกสารนี้คุณจะเรียนรู้ว่า controller ทำงานอย่างไรและมันจะอยู่ในรอบของ request ในแอปพลิเคชันของคุณ

หลังจากอ่านเอกสารนี้คุณจะรู้ว่า:

Chapters

  1. Controller ทำอะไร?
  2. การตั้งชื่อ Controller
  3. เมธอดและแอคชัน
  4. พารามิเตอร์
  5. เซสชัน
  6. คุกกี้
  7. การเรนเดอร์
  8. ตัวกรอง
  9. การป้องกันการขอข้อมูลโดยการขอข้อมูลที่ปลอมแปลง (Request Forgery Protection)
  10. อ็อบเจ็กต์คำขอและคำตอบ
  11. การรับรองความถูกต้องของ HTTP
  12. การสตรีมและการดาวน์โหลดไฟล์
  13. การกรองล็อก
  14. การช่วยเหลือ
  15. บังคับโปรโตคอล HTTPS
  16. จุดตรวจสุขภาพที่มีอยู่แบบในตัว

1 Controller ทำอะไร?

Action Controller เป็น C ใน MVC หลังจากที่เราได้กำหนดว่า controller ใดที่จะใช้สำหรับ request แล้ว controller จะรับผิดชอบในการทำความเข้าใจของ request และสร้างเอาต์พุตที่เหมาะสม โชคดีที่ Action Controller ทำงานหลังคาสำหรับคุณและใช้กฎเกณฑ์ที่ฉลาดเพื่อทำให้ง่ายที่สุด

สำหรับแอปพลิเคชันที่เป็นแบบ RESTful ทั่วไป controller จะรับ request (ซึ่งเป็นสิ่งที่คุณเห็นไม่ได้เนื่องจากคุณเป็นนักพัฒนา) ดึงหรือบันทึกข้อมูลจาก model และใช้ view เพื่อสร้างเอาต์พุต HTML หาก controller ของคุณต้องการทำสิ่งที่แตกต่างออกไปนั้นไม่ใช่ปัญหา นี่เป็นวิธีที่พบบ่อยที่สุดสำหรับการทำงานของ controller

ดังนั้น controller สามารถถูกพิจารณาว่าเป็นตัวกลางระหว่าง model และ view มันทำให้ข้อมูลของ model สามารถใช้ใน view เพื่อแสดงข้อมูลนั้นให้กับผู้ใช้ และ controller จะบันทึกหรืออัปเดตข้อมูลผู้ใช้ไปยัง model

หมายเหตุ: สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับกระบวนการเส้นทาง ดูที่ Rails Routing from the Outside In.

2 การตั้งชื่อ Controller

การตั้งชื่อของ controller ใน Rails นิยมการพหุนามของคำสุดท้ายในชื่อของ controller แม้ว่าจะไม่จำเป็นต้องทำเช่นนั้น (เช่น ApplicationController) ตัวอย่างเช่น ClientsController นั้นเป็นที่ชื่นชอบกว่า ClientController SiteAdminsController นั้นเป็นที่ชื่นชอบกว่า SiteAdminController หรือ SitesAdminsController ฯลฯ

การทำตามกฎเกณฑ์นี้จะช่วยให้คุณสามารถใช้ตัวสร้างเส้นทางเริ่มต้น (เช่น resources ฯลฯ) โดยไม่จำเป็นต้องระบุทุก :path หรือ :controller และจะทำให้การใช้ช่วยเหลือของชื่อเส้นทางที่มีชื่อเหมือนกันสม่ำเสมอในแอปพลิเคชันของคุณ ดูที่ Layouts and Rendering Guide สำหรับรายละเอียดเพิ่มเติม

หมายเหตุ: การตั้งชื่อ controller แตกต่างจากการตั้งชื่อของ model ที่คาดหวังว่าจะต้องมีชื่อในรูปแบบเอกพจน์

3 เมธอดและแอคชัน

Controller เป็นคลาส Ruby ที่สืบทอดมาจาก ApplicationController และมีเมธอดเหมือนคลาสอื่น ๆ เมื่อแอปพลิเคชันของคุณได้รับ request เส้นทางจะกำหนดว่า controller และ action ใดที่จะทำงาน จากนั้น Rails จะสร้างอินสแตนซ์ของ controller นั้นและเรียกใช้เมธอดที่มีชื่อเหมือนกับ action

class ClientsController < ApplicationController
  def new
  end
end

เป็นตัวอย่างเช่น หากผู้ใช้ไปที่ /clients/new ในแอปพลิเคชันของคุณเพื่อเพิ่ม client ใหม่ Rails จะสร้างอินสแตนซ์ของ ClientsController และเรียกใช้เมธอด new ของมัน โปรดทราบว่าเมธอดที่ว่างเปล่าจากตัวอย่างข้างต้นจะทำงานได้ดีเพราะ Rails จะแสดง new.html.erb view โดยค่าเริ่มต้น ยกเว้นว่า action จะกำหนดให้เป็นอย่างอื่น โดยการสร้าง Client และเมธอด new สามารถทำให้ตัวแปร @client เข้าถึงได้ใน view: ruby def new @client = Client.new end

คำอธิบายเพิ่มเติมสามารถดูได้ใน เอกสารเกี่ยวกับเลเอาท์และการเรนเดอร์

ApplicationController สืบทอดมาจาก ActionController::Base, ซึ่งกำหนดคุณสมบัติหลายอย่างที่มีประโยชน์ คู่มือนี้จะอธิบายบางส่วนของคุณสมบัติเหล่านี้ แต่หากคุณอยากรู้ว่ามีอะไรอยู่ในนั้น คุณสามารถดูทั้งหมดใน เอกสาร API หรือในต้นฉบับของโค้ดเอง

เมื่อเรียกใช้เมธอดในคลาสคอนโทรลเลอร์ จะสามารถเรียกใช้ได้เฉพาะเมธอดที่เป็น public เท่านั้น การลดระดับการมองเห็นของเมธอด (ด้วย private หรือ protected) ที่ไม่ได้ตั้งใจให้เป็น action เช่น เมธอดช่วยเหลือหรือตัวกรอง ถือเป็นหลักการที่ดี

คำเตือน: บางชื่อเมธอดถูกสงวนไว้สำหรับ Action Controller การกำหนดชื่อเมธอดเหล่านั้นให้เป็น action หรือเมธอดช่วยเหลืออาจทำให้เกิด SystemStackError หากคุณจำกัดคอนโทรลเลอร์ของคุณให้มีเฉพาะ action ที่เกี่ยวข้องกับ Resource Routing คุณไม่จำเป็นต้องกังวลเรื่องนี้

หมายเหตุ: หากคุณต้องใช้เมธอดที่สงวนไว้เป็นชื่อ action วิธีการแก้ไขหนึ่งคือใช้เส้นทางที่กำหนดเองเพื่อแมปชื่อเมธอดที่สงวนไว้กับเมธอด action ที่ไม่ได้สงวนไว้

4 พารามิเตอร์

คุณอาจต้องการเข้าถึงข้อมูลที่ส่งมาจากผู้ใช้หรือพารามิเตอร์อื่นในการกระทำของคอนโทรลเลอร์ของคุณ มีสองประเภทของพารามิเตอร์ที่เป็นไปได้ในแอปพลิเคชันเว็บ ประเภทแรกคือพารามิเตอร์ที่ส่งมาเป็นส่วนหนึ่งของ URL ที่เรียกว่า query string parameters สตริงคิวรีเซ็นต์ทั้งหมดหลังจาก "?" ใน URL ประเภทที่สองของพารามิเตอร์มักถูกเรียกว่า POST data ข้อมูลเหล่านี้มักมาจากฟอร์ม HTML ที่ผู้ใช้กรอกข้อมูลลงไป มันเรียกว่า POST data เพราะมันสามารถส่งได้เฉพาะในรูปแบบของคำขอ HTTP POST เท่านั้น Rails ไม่ทำการแยกแยะระหว่างพารามิเตอร์ query string และ POST parameters และทั้งสองสามารถเข้าถึงได้ในแฮช params ในคอนโทรลเลอร์ของคุณ:

class ClientsController < ApplicationController
  # การกระทำนี้ใช้พารามิเตอร์ query string เพราะมันถูกเรียกใช้
  # โดยคำขอ HTTP GET แต่สิ่งนี้ไม่มีผลต่อวิธีการเข้าถึงพารามิเตอร์
  # URL สำหรับการกระทำนี้จะมีลักษณะเช่นนี้เพื่อแสดงรายการลูกค้าที่เปิดใช้งาน: /clients?status=activated
  def index
    if params[:status] == "activated"
      @clients = Client.activated
    else
      @clients = Client.inactivated
    end
  end

  # การกระทำนี้ใช้พารามิเตอร์ POST ซึ่งมักมาจากฟอร์ม HTML ที่ผู้ใช้ส่ง
  # URL สำหรับคำขอ RESTful นี้จะเป็น "/clients" และข้อมูลจะถูกส่งเป็นส่วนหนึ่งของร้องขอ
  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      # บรรทัดนี้เขียนทับการเรนเดอร์เริ่มต้น ซึ่ง
      # มีการเรนเดอร์วิว "create" เป็นค่าเริ่มต้น
      render "new"
    end
  end
end

4.1 พารามิเตอร์แบบแฮชและแอเรย์

แฮช params ไม่จำกัดตัวแปรแบบหนึ่งมิติและค่า มันสามารถมีแอเรย์และแฮชที่ซ้อนกันได้ เพื่อส่งแอเรย์ของค่า ให้เพิ่มเครื่องหมายวงเล็บเหล่านั้น "[]" ที่ชื่อคีย์:

GET /clients?ids[]=1&ids[]=2&ids[]=3

หมายเหตุ: URL จริงในตัวอย่างนี้จะถูกเข้ารหัสเป็น "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3" เนื่องจากตัวอักษร "[" และ "]" ไม่ได้รับอนุญาตใน URL ในส่วนมากคุณไม่ต้องกังวลเรื่องนี้เพราะเบราว์เซอร์จะเข้ารหัสให้คุณโดยอัตโนมัติและ Rails จะถอดรหัสโดยอัตโนมัติ แต่หากคุณต้องส่งคำขอเหล่านั้นไปยังเซิร์ฟเวอร์ด้วยตนเองคุณควรจำไว้เสมอ ค่าของ params[:ids] จะเป็น ["1", "2", "3"] ตัวแปรที่รับมาเป็นสตริงเสมอ ระบบ Rails ไม่พยายามทายหรือแปลงประเภท

หมายเหตุ: ค่าเช่น [nil] หรือ [nil, nil, ...] ใน params จะถูกแทนที่ด้วย [] เป็นค่าเริ่มต้นเพื่อเหตุผลด้านความปลอดภัย ดูรายละเอียดเพิ่มเติมที่ Security Guide

ในการส่งแฮช คุณต้องรวมชื่อคีย์ในวงเล็บ:

<form accept-charset="UTF-8" action="/clients" method="post">
  <input type="text" name="client[name]" value="Acme" />
  <input type="text" name="client[phone]" value="12345" />
  <input type="text" name="client[address][postcode]" value="12345" />
  <input type="text" name="client[address][city]" value="Carrot City" />
</form>

เมื่อฟอร์มถูกส่ง ค่าของ params[:client] จะเป็น { "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } } โปรดทราบว่ามีแฮชซ้อนกันใน params[:client][:address]

อ็อบเจ็กต์ params ทำหน้าที่เหมือนแฮช แต่คุณสามารถใช้สัญลักษณ์และสตริงแทนกันได้เป็นคีย์

4.2 พารามิเตอร์ JSON

หากแอปพลิเคชันของคุณเปิดเผย API คุณจะต้องรับพารามิเตอร์ในรูปแบบ JSON หากส่วนหัว "Content-Type" ของคำขอของคุณถูกตั้งค่าเป็น "application/json" Rails จะโหลดพารามิเตอร์ของคุณเข้าสู่แฮช params โดยอัตโนมัติ คุณสามารถเข้าถึงได้เหมือนเดิม

ตัวอย่างเช่น หากคุณส่ง JSON นี้:

{ "company": { "name": "acme", "address": "123 Carrot Street" } }

คอนโทรลเลอร์ของคุณจะได้รับ params[:company] เป็น { "name" => "acme", "address" => "123 Carrot Street" }

นอกจากนี้หากคุณเปิดใช้งาน config.wrap_parameters ในไฟล์กำหนดค่าเริ่มต้นหรือเรียกใช้ wrap_parameters ในคอนโทรลเลอร์ของคุณ คุณสามารถละเว้นองค์ประกอบรากในพารามิเตอร์ JSON ได้อย่างปลอดภัย ในกรณีนี้พารามิเตอร์จะถูกคัดลอกและห่อหุ้มด้วยคีย์ที่เลือกตามชื่อคอนโทรลเลอร์ของคุณ ดังนั้นคำขอ JSON ด้านบนสามารถเขียนได้เป็น:

{ "name": "acme", "address": "123 Carrot Street" }

และถ้าคุณกำลังส่งข้อมูลไปยัง CompaniesController มันจะถูกห่อหุ้มด้วยคีย์ :company ดังนี้:

{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }

คุณสามารถปรับแต่งชื่อคีย์หรือพารามิเตอร์ที่คุณต้องการห่อหุ้มได้โดยเรียกดูเอกสาร API ที่ API documentation

หมายเหตุ: การสนับสนุนในการแยกวิเคราะห์พารามิเตอร์ XML ได้ถูกแยกออกเป็น gem ที่ชื่อว่า actionpack-xml_parser

4.3 พารามิเตอร์เส้นทาง

แฮช params จะมีคีย์ :controller และ :action เสมอ แต่คุณควรใช้เมธอด controller_name และ action_name เพื่อเข้าถึงค่าเหล่านี้ พารามิเตอร์อื่น ๆ ที่ถูกกำหนดโดยเส้นทาง เช่น :id ก็จะสามารถใช้ได้เช่นกัน เป็นตัวอย่างเช่น พิจารณาการแสดงรายการลูกค้าที่รายการสามารถแสดงลูกค้าที่ใช้งานหรือไม่ใช้งานได้ เราสามารถเพิ่มเส้นทางที่จับคู่กับพารามิเตอร์ :status ใน URL ที่สวยงามได้:

get '/clients/:status', to: 'clients#index', foo: 'bar'

ในกรณีนี้ เมื่อผู้ใช้เปิด URL /clients/active params[:status] จะถูกตั้งค่าเป็น "active" เมื่อใช้เส้นทางนี้ params[:foo] ก็จะถูกตั้งค่าเป็น "bar" เหมือนกับถูกส่งผ่านสตริงคิวรี่ คอนโทรลเลอร์ของคุณยังจะได้รับ params[:action] เป็น "index" และ params[:controller] เป็น "clients"

4.4 default_url_options

คุณสามารถตั้งค่าพารามิเตอร์เริ่มต้นสำหรับการสร้าง URL โดยการกำหนดเมธอดที่ชื่อ default_url_options ในคอนโทรลเลอร์ของคุณ มีเงื่อนไขว่าเมธอดดังกล่าวต้องส่งคืนแฮชที่มีค่าเริ่มต้นที่ต้องการ โดยที่คีย์ต้องเป็นสัญลักษณ์:

class ApplicationController < ActionController::Base
  def default_url_options
    { locale: I18n.locale }
  end
end

ตัวเลือกเหล่านี้จะถูกใช้เป็นจุดเริ่มต้นในการสร้าง URL ดังนั้นอาจมีการแทนที่ด้วยตัวเลือกที่ถูกส่งผ่านไปยังการเรียกใช้ url_for.

หากคุณกำหนด default_url_options ใน ApplicationController เช่นในตัวอย่างด้านบน ค่าเริ่มต้นเหล่านี้จะถูกใช้สำหรับการสร้าง URL ทั้งหมด แต่เมธอดยังสามารถกำหนดได้ในคอนโทรลเลอร์ที่เฉพาะเจาะจง ในกรณีนี้จะมีผลกับการสร้าง URL ที่สร้างขึ้นในคอนโทรลเลอร์นั้นเท่านั้น

ในคำขอที่กำหนดให้เมธอดไม่ได้ถูกเรียกใช้จริง สำหรับเหตุผลด้านประสิทธิภาพ แฮชที่ส่งคืนจะถูกเก็บไว้ในแคช และจะมีการเรียกใช้ไม่เกินหนึ่งครั้งต่อคำขอ

4.5 Strong Parameters

ด้วยพารามิเตอร์ที่แข็งแกร่ง พารามิเตอร์ของ Action Controller ถูกห้ามใช้ในการกำหนดค่าของ Active Model mass assignments จนกว่าจะได้รับอนุญาต นั่นหมายความว่าคุณต้องตัดสินใจอย่างมีสติเกี่ยวกับแอตทริบิวต์ที่จะอนุญาตให้มีการอัปเดตแบบมวลกัน นี่เป็นการปฏิบัติที่ดีกว่าเพื่อช่วยป้องกันไม่ให้ผู้ใช้งานอัปเดตแอตทริบิวต์ที่สำคัญโดยไม่ได้ตั้งใจ

นอกจากนี้ พารามิเตอร์สามารถถูกกำหนดให้เป็นค่าที่ต้องการและจะไหลผ่านกระแสการเรียกใช้ที่กำหนดไว้ล่วงหน้า ซึ่งจะทำให้เกิดการตอบสนอง 400 Bad Request ถ้าไม่ได้รับพารามิเตอร์ที่จำเป็นทั้งหมด

class PeopleController < ActionController::Base
  # ส่วนนี้จะเกิดข้อยกเว้น ActiveModel::ForbiddenAttributesError
  # เนื่องจากกำลังใช้การกำหนดค่าแบบมวลกันโดยไม่มีการอนุญาตเฉพาะ
  def create
    Person.create(params[:person])
  end

  # ส่วนนี้จะผ่านโดยสว่างได้เมื่อมีคีย์ person
  # ในพารามิเตอร์ มิเช่นนั้นจะเกิดข้อยกเว้น ActionController::ParameterMissing
  # ซึ่งจะถูกจับตาม ActionController::Base และเปลี่ยนเป็นข้อผิดพลาด 400 Bad Request
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params)
    redirect_to person
  end

  private
    # การใช้เมธอดส่วนตัวเพื่อแยกพารามิเตอร์ที่อนุญาตได้
    # เป็นแบบแยกเป็นส่วนเพราะเป็นแบบแนวคิดที่ดีเนื่องจากคุณสามารถนำรายการอนุญาตเดียวกันไปใช้ในการสร้างและอัปเดตได้
    # นอกจากนี้คุณยังสามารถกำหนดเมธอดนี้ได้เพื่อตรวจสอบแอตทริบิวต์ที่อนุญาตของผู้ใช้แต่ละคน
    def person_params
      params.require(:person).permit(:name, :age)
    end
end

4.5.1 ค่าสกัดที่ได้รับอนุญาต

การเรียกใช้ permit เช่น:

params.permit(:id)

จะอนุญาตให้คีย์ที่ระบุ (:id) ถูกนำเข้าไปถ้ามีอยู่ใน params และมีค่าสกัดที่ได้รับอนุญาตที่เกี่ยวข้อง มิฉะนั้น คีย์นั้นจะถูกกรองออก ดังนั้นอาร์เรย์ แฮช หรือวัตถุอื่น ๆ ไม่สามารถถูกฉีดเข้าได้

ประเภทสกัดที่ได้รับอนุญาตคือ String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile, และ Rack::Test::UploadedFile.

ในการประกาศว่าค่าใน params ต้องเป็นอาร์เรย์ของค่าสกัดที่ได้รับอนุญาต ให้แมปคีย์ไปยังอาร์เรย์ว่าง:

params.permit(id: [])

บางครั้งไม่สามารถหรือไม่สะดวกที่จะประกาศคีย์ที่ถูกต้องของพารามิเตอร์แบบแฮชหรือโครงสร้างภายในได้ เพียงแค่แมปไปยังแฮชว่าง:

params.permit(preferences: {})

แต่ต้องระมัดระวังเพราะสามารถรับข้อมูลอะไรก็ได้ ในกรณีนี้ permit จะตรวจสอบว่าค่าในโครงสร้างที่ส่งคืนได้เป็นค่าสกัดที่ได้รับอนุญาตและกรองออกทุกอย่างที่ไม่ใช่ ในการอนุญาตให้ใช้งานทั้งหมดของพารามิเตอร์แฮช สามารถใช้เมธอด permit! ได้:

params.require(:log_entry).permit!

การใช้งานนี้จะทำเครื่องหมายให้แฮชพารามิเตอร์ :log_entry และแฮชย่อยใดๆ ของมันเป็นที่อนุญาตและไม่ตรวจสอบสำหรับสเกลาร์ที่อนุญาต สามารถยอมรับอะไรก็ได้ ควรใช้ความระมัดระวังอย่างมากเมื่อใช้ permit! เนื่องจากจะอนุญาตให้มีการกำหนดค่าแบบมวลกันทั้งปัจจุบันและในอนาคตของแอตทริบิวต์โมเดล

4.5.2 พารามิเตอร์ที่ซ้อนกัน

คุณยังสามารถใช้ permit กับพารามิเตอร์ที่ซ้อนกันได้ เช่น:

params.permit(:name, { emails: [] },
              friends: [ :name,
                         { family: [ :name ], hobbies: [] }])

การประกาศนี้อนุญาตให้ใช้งานแอตทริบิวต์ name emails และ friends คาดหวังว่า emails จะเป็นอาร์เรย์ของค่าสเกลาร์ที่อนุญาต และ friends จะเป็นอาร์เรย์ของทรัพยากรที่มีแอตทริบิวต์ที่ระบุ: ต้องมีแอตทริบิวต์ name (อนุญาตให้ใช้ค่าสเกลาร์ที่อนุญาตใดๆ) แอตทริบิวต์ hobbies เป็นอาร์เรย์ของค่าสเกลาร์ที่อนุญาต และแอตทริบิวต์ family จะถูก จำกัด ให้มีแอตทริบิวต์ name (อนุญาตให้ใช้ค่าสเกลาร์ที่อนุญาตใดๆ ที่นี่เช่นกัน)

4.5.3 ตัวอย่างเพิ่มเติม

คุณอาจต้องการใช้แอตทริบิวต์ที่อนุญาตในการดำเนินการ new ของคุณ สิ่งนี้ก่อให้เกิดปัญหาที่คุณไม่สามารถใช้ require กับคีย์รูทได้เนื่องจากโดยปกติแล้ว ไม่มีอยู่เมื่อเรียกใช้ new:

# โดยใช้ `fetch` คุณสามารถกำหนดค่าเริ่มต้นและใช้
# ตัวแอพลิเคชันพารามิเตอร์ที่แข็งแกร่งจากนั้น
params.fetch(:blog, {}).permit(:title, :author)

เมธอดคลาสโมเดล accepts_nested_attributes_for ช่วยให้คุณสามารถอัปเดตและลบระเบียนที่เกี่ยวข้องได้ ซึ่งขึ้นอยู่กับพารามิเตอร์ id และ _destroy:

# อนุญาตให้ใช้งาน :id และ :_destroy
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])

แฮชที่มีคีย์เป็นจำนวนเต็มจะถูกจัดการในลักษณะที่แตกต่างกัน และคุณสามารถประกาศแอตทริบิวต์เหมือนกับลูกโดยตรงได้ คุณจะได้รับพารามิเตอร์เช่นนี้เมื่อคุณใช้ accepts_nested_attributes_for ร่วมกับการสัมพันธ์ has_many:

# อนุญาตให้ข้อมูลต่อไปนี้:
# {"book" => {"title" => "Some Book",
#             "chapters_attributes" => { "1" => {"title" => "First Chapter"},
#                                        "2" => {"title" => "Second Chapter"}}}}

params.require(:book).permit(:title, chapters_attributes: [:title])

จินตนาการถึงสถานการณ์ที่คุณมีพารามิเตอร์ที่แสดงชื่อผลิตภัณฑ์และแฮชข้อมูลอย่างสมบูรณ์ที่เกี่ยวข้องกับผลิตภัณฑ์นั้น และคุณต้องการอนุญาตให้แอตทริบิวต์ชื่อผลิตภัณฑ์และแฮชข้อมูลทั้งหมด:

def product_params
  params.require(:product).permit(:name, data: {})
end

4.5.4 นอกเหนือจากขอบเขตของพารามิเตอร์แบบแข็งแกร่ง

API พารามิเตอร์แบบแข็งแกร่งถูกออกแบบขึ้นเพื่อใช้งานที่พบบ่อยที่สุด มันไม่ได้มีไว้เพื่อใช้แก้ปัญหาการกรองพารามิเตอร์ทั้งหมดของคุณ อย่างไรก็ตาม คุณสามารถผสม API กับโค้ดของคุณเองได้อย่างง่ายดายเพื่อทำการปรับเปลี่ยนให้เหมาะสมกับสถานการณ์ของคุณ

5 เซสชัน

แอปพลิเคชันของคุณมีเซสชันสำหรับแต่ละผู้ใช้ที่คุณสามารถเก็บข้อมูลจำนวนเล็กไว้ที่จะถูกบันทึกระหว่างคำขอ แซสชันจะสามารถใช้ได้เฉพาะในคอนโทรลเลอร์และวิวและสามารถใช้กับหนึ่งในหลายกลไกการจัดเก็บข้อมูลที่แตกต่างกันได้:

  • ActionDispatch::Session::CookieStore - เก็บข้อมูลทั้งหมดบนไคลเอนต์
  • ActionDispatch::Session::CacheStore - เก็บข้อมูลในแคชเซิร์ฟเวอร์ของ Rails
  • ActionDispatch::Session::MemCacheStore - เก็บข้อมูลในคลัสเตอร์ memcached (นี่เป็นการใช้งานเก่า; ควรพิจารณาใช้ CacheStore แทน)
  • ActionDispatch::Session::ActiveRecordStore - เก็บข้อมูลในฐานข้อมูลโดยใช้ Active Record (ต้องใช้ gem activerecord-session_store)
  • การจัดเก็บที่กำหนดเองหรือการจัดเก็บที่ให้มาจากแพ็คเกจบุคคลที่สาม ทุก session store ใช้คุกกี้เพื่อเก็บรหัส ID ที่ไม่ซ้ำกันสำหรับแต่ละเซสชัน (คุณต้องใช้คุกกี้ ราลส์จะไม่อนุญาตให้คุณส่งรหัสเซสชันผ่าน URL เนื่องจากมีความปลอดภัยน้อยกว่า)

สำหรับสโตร์ที่เป็นส่วนใหญ่ รหัส ID นี้ใช้ในการค้นหาข้อมูลเซสชันบนเซิร์ฟเวอร์ เช่นในตารางฐานข้อมูล มีข้อยกเว้นเพียงอย่างเดียว คือสโตร์เซสชันเริ่มต้นและแนะนำ - CookieStore - ซึ่งเก็บข้อมูลเซสชันทั้งหมดในคุกกี้เอง (รหัส ID ยังคงมีอยู่หากคุณต้องการ) สิ่งนี้มีข้อดีที่เป็นเบามากและไม่ต้องการการตั้งค่าใด ๆ เพื่อใช้เซสชันในแอปพลิเคชันใหม่ ข้อมูลคุกกี้ถูกเข้ารหัสด้วยเทคนิคการเซ็นต์เพื่อป้องกันการแก้ไข และยังถูกเข้ารหัสเพื่อให้ผู้ที่มีการเข้าถึงไม่สามารถอ่านเนื้อหาได้ (ราลส์จะไม่ยอมรับหากมีการแก้ไข)

CookieStore สามารถเก็บข้อมูลได้ประมาณ 4 kB - น้อยกว่าสโตร์อื่น ๆ - แต่นี้เป็นปกติพอสมควร ไม่ว่าสโตร์เซสชันใด ๆ ที่แอปพลิเคชันของคุณจะใช้ คุณควรหลีกเลี่ยงการเก็บข้อมูลจำนวนมากในเซสชัน โดยเฉพาะอย่างยิ่งการเก็บวัตถุที่ซับซ้อน (เช่นอินสแตนซ์โมเดล) ในเซสชัน เนื่องจากเซิร์ฟเวอร์อาจไม่สามารถสร้างขึ้นใหม่ระหว่างคำขอได้ซึ่งจะทำให้เกิดข้อผิดพลาด

หากเซสชันผู้ใช้ของคุณไม่เก็บข้อมูลที่สำคัญหรือไม่จำเป็นต้องอยู่เป็นเวลานาน (เช่นหากคุณใช้แฟลชสำหรับการส่งข้อความ) คุณสามารถพิจารณาใช้ ActionDispatch::Session::CacheStore นี้จะเก็บเซสชันโดยใช้การจัดเก็บแคชที่คุณกำหนดค่าไว้สำหรับแอปพลิเคชันของคุณ ข้อดีของสิ่งนี้คือคุณสามารถใช้โครงสร้างแคชที่มีอยู่ในการจัดเก็บเซสชันโดยไม่ต้องการการตั้งค่าหรือการดูแลเพิ่มเติม ข้อเสียของสิ่งนี้คือเซสชันจะเป็นชั่วคราวและอาจหายไปได้ทุกเมื่อ

อ่านเพิ่มเติมเกี่ยวกับการจัดเก็บเซสชันในคู่มือความปลอดภัย

หากคุณต้องการกลไกการจัดเก็บเซสชันที่แตกต่างกัน คุณสามารถเปลี่ยนแปลงได้ในไฟล์เริ่มต้น:

Rails.application.config.session_store :cache_store

ดู config.session_store ใน คู่มือการกำหนดค่าสำหรับข้อมูลเพิ่มเติม

Rails ตั้งค่าคีย์เซสชัน (ชื่อคุกกี้) เมื่อเซ็นต์ข้อมูลเซสชัน สามารถเปลี่ยนแปลงได้ในไฟล์เริ่มต้น:

# ตรวจสอบให้แน่ใจว่าคุณเริ่มเซิร์ฟเวอร์ของคุณใหม่เมื่อคุณแก้ไขไฟล์นี้
Rails.application.config.session_store :cookie_store, key: '_your_app_session'

คุณยังสามารถส่งคีย์ :domain และระบุชื่อโดเมนสำหรับคุกกี้ได้:

# ตรวจสอบให้แน่ใจว่าคุณเริ่มเซิร์ฟเวอร์ของคุณใหม่เมื่อคุณแก้ไขไฟล์นี้
Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"

Rails ตั้งค่า (สำหรับ CookieStore) คีย์ลับลับที่ใช้ในการเซ็นต์ข้อมูลเซสชันใน config/credentials.yml.enc สามารถเปลี่ยนแปลงได้ด้วย bin/rails credentials:edit.

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# ใช้เป็นคีย์ลับหลักสำหรับ MessageVerifiers ทั้งหมดใน Rails รวมถึงคีย์ลับที่ป้องกันคุกกี้
secret_key_base: 492f...

หมายเหตุ: เปลี่ยนแปลง secret_key_base เมื่อใช้ CookieStore จะทำให้เซสชันที่มีอยู่ทั้งหมดเป็นโมฆะ

5.1 เข้าถึงเซสชัน

ในคอนโทรลเลอร์ของคุณ คุณสามารถเข้าถึงเซสชันผ่านเมธอด session ได้ หมายเหตุ: การโหลดเซสชันจะเกิดขึ้นเมื่อมีการเข้าถึงเซสชันในโค้ดของแอ็กชัน ถ้าคุณไม่ได้เข้าถึงเซสชันในโค้ดของแอ็กชัน มันจะไม่ถูกโหลด ดังนั้นคุณไม่จำเป็นต้องปิดการใช้งานเซสชัน แค่ไม่เข้าถึงเซสชันก็เพียงพอ

ค่าเซสชันจะถูกเก็บไว้โดยใช้คีย์/ค่าเหมือนกับแฮช:

class ApplicationController < ActionController::Base
  private
    # ค้นหาผู้ใช้ที่มี ID เก็บไว้ในเซสชันด้วยคีย์ :current_user_id
    # นี่เป็นวิธีที่พบบ่อยในการจัดการการเข้าสู่ระบบของผู้ใช้ในแอปพลิเคชัน Rails
    # เมื่อเข้าสู่ระบบ ค่าเซสชันจะถูกตั้งค่า และเมื่อออกจะถูกลบออก
    def current_user
      @_current_user ||= session[:current_user_id] &&
        User.find_by(id: session[:current_user_id])
    end
end

ในการเก็บข้อมูลในเซสชัน เพียงแค่กำหนดค่าให้กับคีย์เหมือนกับแฮช:

class LoginsController < ApplicationController
  # "สร้าง" เซสชันการเข้าสู่ระบบ หรือ "ล็อกอินผู้ใช้"
  def create
    if user = User.authenticate(params[:username], params[:password])
      # บันทึก ID ของผู้ใช้ในเซสชัน เพื่อใช้ในคำขอถัดไป
      session[:current_user_id] = user.id
      redirect_to root_url
    end
  end
end

ในการลบข้อมูลออกจากเซสชัน เพียงแค่ลบคีย์/ค่าเหมือนกับแฮช:

class LoginsController < ApplicationController
  # "ลบ" เซสชันการเข้าสู่ระบบ หรือ "ล็อกเอาท์ผู้ใช้"
  def destroy
    # ลบ ID ของผู้ใช้ออกจากเซสชัน
    session.delete(:current_user_id)
    # ล้างค่าผู้ใช้ปัจจุบันที่เก็บไว้
    @_current_user = nil
    redirect_to root_url, status: :see_other
  end
end

ในการรีเซ็ตเซสชันทั้งหมด ใช้ reset_session

5.2 แฟลช

แฟลชเป็นส่วนพิเศษของเซสชันที่จะถูกล้างทุกคำขอ นั่นหมายความว่าค่าที่เก็บไว้ในนั้นจะสามารถใช้ได้ในคำขอถัดไป เป็นประโยชน์ในการส่งข้อความข้อผิดพลาด เป็นต้น

แฟลชสามารถเข้าถึงได้ผ่าน flash เช่นเดียวกับเซสชัน แฟลชถูกแทนที่ด้วยแฮช

ให้ใช้การออกจากระบบเป็นตัวอย่าง คอนโทรลเลอร์สามารถส่งข้อความที่จะแสดงให้ผู้ใช้เห็นในคำขอถัดไปได้:

class LoginsController < ApplicationController
  def destroy
    session.delete(:current_user_id)
    flash[:notice] = "คุณได้ล็อกเอาท์สำเร็จแล้ว"
    redirect_to root_url, status: :see_other
  end
end

โปรดทราบว่าคุณยังสามารถกำหนดข้อความแฟลชเป็นส่วนหนึ่งของการเปลี่ยนเส้นทางได้ คุณสามารถกำหนด :notice, :alert หรือ :flash ที่ใช้งานได้ทั่วไป:

redirect_to root_url, notice: "คุณได้ล็อกเอาท์สำเร็จแล้ว"
redirect_to root_url, alert: "คุณติดอยู่ที่นี่!"
redirect_to root_url, flash: { referral_code: 1234 }

แอ็กชัน destroy จะเปลี่ยนเส้นทางไปที่ root_url ของแอปพลิเคชัน ที่นั่นข้อความจะถูกแสดง โปรดทราบว่าการจะใช้หรือไม่ใช้ข้อความจากแฟลชที่ได้จากแอ็กชันก่อนหน้านี้นั้นอยู่ในความตัดสินของแอ็กชันถัดไป มันเป็นที่เป็นปกติที่จะแสดงข้อความแจ้งเตือนหรือข้อความแฟลชจากแฟลชในเลเอาท์ของแอปพลิเคชัน:

<html>
  <!-- <head/> -->
  <body>
    <% flash.each do |name, msg| -%>
      <%= content_tag :div, msg, class: name %>
    <% end -%>

    <!-- เนื้อหาเพิ่มเติม -->
  </body>
</html>

ดังนั้นหากแอ็กชันตั้งค่าข้อความแจ้งเตือนหรือข้อความแฟลช และเลเอาท์จะแสดงข้อความนั้นโดยอัตโนมัติ

คุณสามารถส่งอะไรก็ได้ที่เซสชันสามารถเก็บได้ คุณไม่จำกัดเฉพาะข้อความแจ้งเตือนและข้อความแฟลชเท่านั้น:

<% if flash[:just_signed_up] %>
  <p class="welcome">ยินดีต้อนรับสู่เว็บไซต์ของเรา!</p>
<% end %>

หากคุณต้องการให้ค่า flash ถูกส่งต่อไปยังคำขออื่น ๆ ใช้ flash.keep:

class MainController < ApplicationController
  # สมมติว่าการกระทำนี้สอดคล้องกับ root_url แต่คุณต้องการ
  # ให้คำขอทั้งหมดที่นี่ถูกเปลี่ยนเส้นทางไปยัง UsersController#index
  # หากการกระทำตั้งค่า flash และเปลี่ยนเส้นทางมาที่นี่ ค่าทั้งหมด
  # จะสูญหายเมื่อมีการเปลี่ยนเส้นทางอื่น ๆ แต่คุณสามารถใช้ 'keep' เพื่อ
  # ทำให้ค่านี้ยังคงอยู่สำหรับคำขอถัดไป
  def index
    # จะยังคงค่า flash ทั้งหมด
    flash.keep

    # คุณยังสามารถใช้คีย์เพื่อเก็บเฉพาะประเภทของค่าบางประเภทได้
    # flash.keep(:notice)
    redirect_to users_url
  end
end

5.2.1 flash.now

โดยค่าเริ่มต้นการเพิ่มค่าใน flash จะทำให้พวกเขาสามารถใช้ได้ในคำขอถัดไป แต่บางครั้งคุณอาจต้องการเข้าถึงค่าเหล่านั้นในคำขอเดียวกัน ตัวอย่างเช่น หากการกระทำ create ล้มเหลวในการบันทึกทรัพยากร และคุณแสดงเทมเพลต new โดยตรง นั่นจะไม่ส่งผลให้เกิดคำขอใหม่ แต่คุณอาจต้องการแสดงข้อความโดยใช้ flash ในกรณีนี้คุณสามารถใช้ flash.now ในลักษณะเดียวกับ flash ปกติ:

class ClientsController < ApplicationController
  def create
    @client = Client.new(client_params)
    if @client.save
      # ...
    else
      flash.now[:error] = "Could not save client"
      render action: "new"
    end
  end
end

6 คุกกี้

แอปพลิเคชันของคุณสามารถเก็บข้อมูลจำนวนเล็กบนไคลเอนต์ - ที่เรียกว่าคุกกี้ - ซึ่งจะถูกเก็บไว้ระหว่างคำขอและแม้แต่เซสชัน Rails มีการเข้าถึงคุกกี้ได้อย่างง่ายผ่าน cookies ซึ่ง - เหมือนกับ session - ทำงานเหมือนกับแฮช:

class CommentsController < ApplicationController
  def new
    # กรอกชื่อผู้แสดงความคิดเห็นโดยอัตโนมัติหากมีการเก็บไว้ในคุกกี้
    @comment = Comment.new(author: cookies[:commenter_name])
  end

  def create
    @comment = Comment.new(comment_params)
    if @comment.save
      flash[:notice] = "Thanks for your comment!"
      if params[:remember_name]
        # จำชื่อผู้แสดงความคิดเห็นไว้
        cookies[:commenter_name] = @comment.author
      else
        # ลบคุกกี้ชื่อผู้แสดงความคิดเห็น (หากมี)
        cookies.delete(:commenter_name)
      end
      redirect_to @comment.article
    else
      render action: "new"
    end
  end
end

โปรดทราบว่าในขณะที่สำหรับค่าเซสชันคุณสามารถตั้งค่าคีย์เป็น nil เพื่อลบค่าคุกกี้คุณควรใช้ cookies.delete(:key).

Rails ยังมีตู้คุกกี้ที่ลงชื่อและตู้คุกกี้ที่เข้ารหัสสำหรับการเก็บข้อมูลที่ละเอียดอ่อน ตู้คุกกี้ที่ลงชื่อเพิ่มลายเซ็นต์ทางคริปโตกราฟิกลงในค่าคุกกี้เพื่อปกป้องความถูกต้องของค่าเหล่านั้น ตู้คุกกี้ที่เข้ารหัสเข้ารหัสค่าเพิ่มเติมนอกจากลายเซ็นต์ทางคริปโตกราฟิกเพื่อให้ไม่สามารถอ่านได้โดยผู้ใช้งานสิ้นปี อ้างอิงที่ API documentation สำหรับรายละเอียดเพิ่มเติม

ตู้คุกกี้พิเศษเหล่านี้ใช้ตัวแปรที่เรียกใช้งานเพื่อเซรียลไลซ์ค่าที่กำหนดให้เป็นสตริงและเซรียลไลซ์ค่าเหล่านั้นเป็นออบเจ็กต์ Ruby เมื่ออ่าน คุณสามารถระบุตัวแปรที่จะใช้ได้ผ่าน config.action_dispatch.cookies_serializer

ตัวแปรเริ่มต้นสำหรับแอปพลิเคชันใหม่คือ :json โปรดทราบว่า JSON มีการสนับสนุนจำกัดสำหรับการรอบเดียวกันของออบเจ็กต์ Ruby เช่น Date Time และ Symbol ออบเจ็กต์ (รวมถึงคีย์ Hash) จะถูกเซรียลไลซ์และเซรียลไลซ์เป็นสตริง:

class CookiesController < ApplicationController
  def set_cookie
    cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
    redirect_to action: 'read_cookie'
  end

  def read_cookie
    cookies.encrypted[:expiration_date] # => "2014-03-20"
  end
end

หากคุณต้องการเก็บเหล่านี้หรือวัตถุที่ซับซ้อนมากขึ้นคุณอาจต้องแปลงค่าของพวกเขาด้วยตนเองเมื่ออ่านในคำขอถัดไป

หากคุณใช้การเก็บเซสช่องทางคุกกี้ สิ่งที่กล่าวมาเป็นเช่นเดียวกับ session และ flash hash

7 การเรนเดอร์

ActionController ทำให้การเรนเดอร์ HTML, XML หรือ JSON ง่ายดาย หากคุณสร้างคอนโทรลเลอร์โดยใช้ scaffolding มันจะดูเหมือนนี้:

class UsersController < ApplicationController
  def index
    @users = User.all
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render xml: @users }
      format.json { render json: @users }
    end
  end
end

คุณอาจสังเกตเห็นในโค้ดด้านบนว่าเรากำลังใช้ render xml: @users ไม่ใช่ render xml: @users.to_xml หากวัตถุไม่ใช่สตริง แล้ว Rails จะเรียกใช้ to_xml โดยอัตโนมัติให้เรา

คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับการเรนเดอร์ใน Layouts and Rendering Guide

8 ตัวกรอง

ตัวกรองเป็นเมธอดที่ทำงาน "ก่อน", "หลัง" หรือ "รอบ" การกระทำของคอนโทรลเลอร์

ตัวกรองถูกสืบทอด ดังนั้นหากคุณตั้งค่าตัวกรองใน ApplicationController มันจะถูกเรียกใช้ในทุกคอนโทรลเลอร์ในแอปพลิเคชันของคุณ

ตัวกรอง "ก่อน" จะถูกลงทะเบียนผ่าน before_action. มันอาจหยุดวงจรคำขอ ตัวกรอง "ก่อน" ที่พบบ่อยคือตัวกรองที่ต้องการให้ผู้ใช้เข้าสู่ระบบก่อนที่จะเรียกใช้การกระทำ คุณสามารถกำหนดเมธอดตัวกรองได้ดังนี้:

class ApplicationController < ActionController::Base
  before_action :require_login

  private
    def require_login
      unless logged_in?
        flash[:error] = "คุณต้องเข้าสู่ระบบเพื่อเข้าถึงส่วนนี้"
        redirect_to new_login_url # หยุดวงจรคำขอ
      end
    end
end

เมธอดนี้เพียงแค่เก็บข้อความข้อผิดพลาดในแฟลชและเปลี่ยนเส้นทางไปยังฟอร์มเข้าสู่ระบบหากผู้ใช้ไม่ได้เข้าสู่ระบบ หากตัวกรอง "ก่อน" เรนเดอร์หรือเปลี่ยนเส้นทาง การกระทำจะไม่ทำงาน หากมีตัวกรองเพิ่มเติมที่ต้องการทำงานหลังจากตัวกรองนั้น ๆ มันจะถูกยกเลิกเช่นกัน

ในตัวอย่างนี้ ตัวกรองถูกเพิ่มใน ApplicationController และดังนั้นคอนโทรลเลอร์ทั้งหมดในแอปพลิเคชันจะสืบทอดมัน นี่จะทำให้ทุกอย่างในแอปพลิเคชันต้องการให้ผู้ใช้เข้าสู่ระบบเพื่อใช้งาน ด้วยเหตุผลที่ชัดเจน (ผู้ใช้จะไม่สามารถเข้าสู่ระบบได้ในที่แรก!) ไม่ใช่ทุกคอนโทรลเลอร์หรือการกระทำที่ต้องการสิ่งนี้ คุณสามารถป้องกันตัวกรองนี้จากการทำงานก่อนการกระทำเฉพาะด้วย skip_before_action:

class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

ตอนนี้การกระทำ new และ create ของ LoginsController จะทำงานเหมือนเดิมโดยไม่ต้องเข้าสู่ระบบ ตัวเลือก :only ถูกใช้เพื่อข้ามตัวกรองนี้เฉพาะสำหรับการกระทำเหล่านี้ และยังมีตัวเลือก :except ที่ทำงานในทิศทางตรงกันข้าม ตัวเลือกเหล่านี้สามารถใช้เมื่อเพิ่มตัวกรองด้วย เพื่อให้คุณสามารถเพิ่มตัวกรองที่ทำงานเฉพาะสำหรับการกระทำที่เลือกได้

หมายเหตุ: เรียกใช้ตัวกรองเดียวกันหลายครั้งด้วยตัวเลือกที่แตกต่างกันจะไม่ทำงาน เนื่องจากการกำหนดตัวกรองครั้งสุดท้ายจะเขียนทับคำนิยามก่อนหน้านี้

8.1 ตัวกรองหลังและตัวกรองรอบ

นอกจากตัวกรอง "ก่อน" คุณยังสามารถเรียกใช้ตัวกรองหลังจากการกระทำได้ หรือทั้งก่อนและหลัง "after" filters จะถูกลงทะเบียนผ่าน after_action พวกเขาคล้ายกับ "before" filters แต่เนื่องจาก action ได้รันแล้วพวกเขาสามารถเข้าถึงข้อมูล response ที่กำลังจะส่งให้กับ client ได้ แน่นอนว่า "after" filters ไม่สามารถหยุดการรัน action ได้ โปรดทราบว่า "after" filters จะถูกทำงานเมื่อ action สำเร็จเท่านั้น แต่ไม่เมื่อเกิดข้อผิดพลาดในรอบของคำขอ

"around" filters จะถูกลงทะเบียนผ่าน around_action พวกเขารับผิดชอบในการรัน action ที่เกี่ยวข้องโดยใช้ yield คล้ายกับวิธีการทำงานของ Rack middlewares

ตัวอย่างเช่นในเว็บไซต์ที่มีการอนุมัติการเปลี่ยนแปลง ผู้ดูแลระบบสามารถดูตัวอย่างได้ง่ายๆ โดยการนำมาใช้ใน transaction:

class ChangesController < ApplicationController
  around_action :wrap_in_transaction, only: :show

  private
    def wrap_in_transaction
      ActiveRecord::Base.transaction do
        begin
          yield
        ensure
          raise ActiveRecord::Rollback
        end
      end
    end
end

โปรดทราบว่า "around" filters ยังครอบคลุมการเรนเดอร์ด้วย โดยเฉพาะในตัวอย่างข้างต้น หากมีการอ่านข้อมูลจากฐานข้อมูลในมุมมอง (scope) มันจะทำใน transaction และนำเสนอข้อมูลเพื่อดูตัวอย่าง

คุณสามารถเลือกที่จะไม่ yield และสร้าง response เอง ในกรณีนั้น action จะไม่ถูกรัน

8.2 วิธีการใช้ Filters อื่นๆ

แม้ว่าวิธีที่พบบ่อยที่สุดในการใช้ filters คือการสร้างเมธอด private และใช้ before_action, after_action, หรือ around_action เพื่อเพิ่ม filters แต่ยังมีวิธีอื่นๆ ที่ทำเหมือนกัน

วิธีแรกคือการใช้บล็อกโดยตรงกับเมธอด *_action บล็อกจะได้รับ controller เป็นอาร์กิวเมนต์ เช่น require_login filter จากตัวอย่างข้างต้นสามารถเขียนใหม่ให้ใช้บล็อกได้ดังนี้:

class ApplicationController < ActionController::Base
  before_action do |controller|
    unless controller.send(:logged_in?)
      flash[:error] = "You must be logged in to access this section"
      redirect_to new_login_url
    end
  end
end

โปรดทราบว่า filter ในกรณีนี้ใช้ send เนื่องจากเมธอด logged_in? เป็น private และ filter ไม่ทำงานในขอบเขตของ controller นี้ไม่ใช่วิธีการแนะนำในการสร้าง filter นี้ แต่ในกรณีที่ง่ายกว่าอาจจะเป็นประโยชน์

สำหรับ around_action บล็อกยัง yield ใน action เช่นในตัวอย่างข้างต้น:

around_action { |_controller, action| time(&action) }

วิธีที่สองคือการใช้คลาส (แท้จริงแล้วอ็อบเจ็กต์ใดๆ ที่ตอบสนองต่อเมธอดที่ถูกต้องก็ได้) เพื่อจัดการกับการกรอง นี้เป็นวิธีที่เป็นประโยชน์ในกรณีที่ซับซ้อนมากขึ้นและไม่สามารถนำมาใช้ในวิธีการอื่นๆ ได้อย่างอ่านและใช้ซ้ำได้ เป็นตัวอย่างเช่นคุณสามารถเขียน filter การเข้าสู่ระบบอีกครั้งโดยใช้คลาสได้ดังนี้:

class ApplicationController < ActionController::Base
  before_action LoginFilter
end

class LoginFilter
  def self.before(controller)
    unless controller.send(:logged_in?)
      controller.flash[:error] = "You must be logged in to access this section"
      controller.redirect_to controller.new_login_url
    end
  end
end

อีกครั้งนี้ก็ไม่ใช่ตัวอย่างที่ดีสำหรับ filter นี้ เนื่องจากมันไม่ทำงานในขอบเขตของ controller แต่ได้รับ controller เป็นอาร์กิวเมนต์ คลาส filter ต้องมีเมธอดที่มีชื่อเดียวกับ filter เช่น filter ของ before_action คลาสต้องมีเมธอด before และอื่นๆ อีกเช่นนั้น เมธอด around ต้อง yield เพื่อรัน action

9 การป้องกันการขอข้อมูลโดยการขอข้อมูลที่ปลอมแปลง (Request Forgery Protection)

การขอข้อมูลโดยการขอข้อมูลที่ปลอมแปลงข้ามเว็บ (Cross-site request forgery) เป็นประเภทของการโจมตีที่เว็บไซต์หลอกผู้ใช้ให้ขอข้อมูลจากเว็บไซต์อื่น ๆ โดยอาจเพิ่ม แก้ไข หรือลบข้อมูลบนเว็บไซต์นั้นโดยไม่มีความรู้หรืออนุญาตจากผู้ใช้

ขั้นแรกในการป้องกันคือตรวจสอบให้แน่ใจว่าการกระทำที่เป็น "ทำลาย" (create, update, และ destroy) สามารถเข้าถึงได้เฉพาะด้วยการขอข้อมูลที่ไม่ใช่ GET เท่านั้น หากคุณปฏิบัติตามหลักการ RESTful คุณกำลังทำเช่นนั้นอยู่แล้ว อย่างไรก็ตาม เว็บไซต์ที่มีความประมาณสามารถส่งคำขอที่ไม่ใช่ GET ไปยังเว็บไซต์ของคุณได้อย่างง่ายดาย และนี่คือสิ่งที่การป้องกันการขอข้อมูลโดยการขอข้อมูลที่ปลอมแปลงมาช่วยเสมอ ตามชื่อของมัน มันป้องกันการขอข้อมูลที่ปลอมแปลง

วิธีการทำนี้คือการเพิ่มโทเค็นที่ไม่สามารถคาดเดาได้ซึ่งรู้จักเฉพาะกับเซิร์ฟเวอร์ของคุณเท่านั้นในแต่ละคำขอ ด้วยวิธีนี้หากคำขอเข้ามาโดยไม่มีโทเค็นที่ถูกต้อง คำขอนั้นจะถูกปฏิเสธการเข้าถึง

หากคุณสร้างฟอร์มเช่นนี้:

<%= form_with model: @user do |form| %>
  <%= form.text_field :username %>
  <%= form.text_field :password %>
<% end %>

คุณจะเห็นว่าโทเค็นถูกเพิ่มเป็นฟิลด์ที่ซ่อนอยู่:

<form accept-charset="UTF-8" action="/users/1" method="post">
<input type="hidden"
       value="67250ab105eb5ad10851c00a5621854a23af5489"
       name="authenticity_token"/>
<!-- fields -->
</form>

Rails จะเพิ่มโทเค็นนี้ในทุก ๆ ฟอร์มที่สร้างขึ้นโดยใช้ form helpers ดังนั้นเวลาใดก็ตามที่คุณไม่ต้องกังวลเกี่ยวกับมัน หากคุณเขียนฟอร์มด้วยตนเองหรือต้องการเพิ่มโทเค็นเพื่อเหตุผลอื่น ๆ คุณสามารถเข้าถึงโทเค็นผ่านเมธอด form_authenticity_token:

form_authenticity_token จะสร้างโทเค็นการรับรองที่ถูกต้อง ซึ่งเป็นประโยชน์ในสถานที่ที่ Rails ไม่เพิ่มโดยอัตโนมัติ เช่นในการเรียก Ajax ที่กำหนดเอง

Security Guide มีข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้และเรื่องราวความปลอดภัยอื่น ๆ ที่คุณควรทราบเมื่อพัฒนาแอปพลิเคชันเว็บ

10 อ็อบเจ็กต์คำขอและคำตอบ

ในทุก ๆ คอนโทรลเลอร์ มีเมธอดเข้าถึงสองอ็อบเจ็กต์ที่ชี้ไปที่คำขอและอ็อบเจ็กต์คำตอบที่เกี่ยวข้องกับวงจรคำขอที่กำลังดำเนินการอยู่ในขณะนี้ request เป็นเมธอดที่มีอินสแตนซ์ของ ActionDispatch::Request และ response เป็นเมธอดที่คืนค่าอ็อบเจ็กต์คำตอบที่แสดงถึงสิ่งที่กำลังจะส่งกลับไปยังไคลเอ็นต์

10.1 อ็อบเจ็กต์ request

อ็อบเจ็กต์คำขอมีข้อมูลที่มีประโยชน์เกี่ยวกับคำขอที่เข้ามาจากไคลเอ็นต์ หากต้องการรายการเต็มของเมธอดที่ใช้ได้ โปรดอ้างอิงที่เอพีไอของเรลส์ (https://api.rubyonrails.org/classes/ActionDispatch/Request.html) และเอกสารแร็ก (https://www.rubydoc.info/github/rack/rack/Rack/Request) ระหว่างคุณสามารถเข้าถึงคุณสมบัติต่าง ๆ บนอ็อบเจ็กต์นี้ได้ เช่น:

คุณสมบัติของ request วัตถุประสงค์
host ชื่อโฮสต์ที่ใช้สำหรับคำขอนี้
domain(n=2) ชื่อโฮสต์ n ช่วงแรกของโฮสต์เริ่มต้นจากขวา (TLD)
format ประเภทเนื้อหาที่ไคลเอ็นต์ขอร้อง
method วิธี HTTP ที่ใช้สำหรับคำขอนี้
get?, post?, patch?, put?, delete?, head? คืนค่า true หากวิธี HTTP เป็น GET/POST/PATCH/PUT/DELETE/HEAD
headers คืนค่าแฮชที่มีส่วนหัวที่เกี่ยวข้องกับคำขอ
port หมายเลขพอร์ต (จำนวนเต็ม) ที่ใช้สำหรับคำขอนี้
protocol คืนค่าสตริงที่มีโปรโตคอลที่ใช้พร้อมกับ "://" เช่น "http://"
query_string ส่วนของสตริงคิวรีที่อยู่ใน URL หมายความว่าทุกอย่างหลังจาก "?"
remote_ip ที่อยู่ IP ของไคลเอ็นต์
url URL ทั้งหมดที่ใช้สำหรับคำขอนี้

10.1.1 path_parameters, query_parameters, และ request_parameters

Rails รวบรวมพารามิเตอร์ทั้งหมดที่ส่งมาพร้อมกับคำขอใน params hash ไม่ว่าจะส่งมาเป็นส่วนหนึ่งของ query string หรือ post body อ็อบเจ็กต์คำขอมี accessors สามตัวที่ให้คุณเข้าถึงพารามิเตอร์เหล่านี้ขึ้นอยู่กับที่มาของพารามิเตอร์นั้น ๆ แอ็กเซสเซอร์ query_parameters มี hash ที่มีพารามิเตอร์ที่ส่งมาเป็นส่วนหนึ่งของ query string ในขณะที่แอ็กเซสเซอร์ request_parameters มี hash ที่มีพารามิเตอร์ที่ส่งมาเป็นส่วนหนึ่งของ post body และแอ็กเซสเซอร์ path_parameters มี hash ที่มีพารามิเตอร์ที่รู้จักโดยการเรียกใช้เป็นส่วนหนึ่งของเส้นทางที่นำไปสู่คอนโทรลเลอร์และแอ็กชันนี้เฉพาะ

10.2 อ็อบเจ็กต์ response

อ็อบเจ็กต์ตอบกลับไม่ได้ใช้โดยตรงตามปกติ แต่จะถูกสร้างขึ้นระหว่างการดำเนินการของแอ็กชันและการแสดงข้อมูลที่กำลังส่งกลับไปยังผู้ใช้ แต่บางครั้ง - เช่นในตัวกรองหลัง - อาจมีประโยชน์ที่จะเข้าถึงอ็อบเจ็กต์ตอบกลับโดยตรง บางเมธอด accessor เหล่านี้ยังมี setters ที่อนุญาตให้คุณเปลี่ยนค่าของพวกเขา ในการรับรายการเต็มของเมธอดที่มีอยู่ อ้างอิงที่ เอพีไอ Rails และ เอพีไอ Rack

Property ของ response วัตถุประสงค์
body นี่คือสตริงของข้อมูลที่กำลังส่งกลับไปยังไคลเอ็นต์ ส่วนมากนี้เป็น HTML
status รหัสสถานะ HTTP สำหรับการตอบกลับ เช่น 200 สำหรับคำขอที่ประสบความสำเร็จหรือ 404 สำหรับไฟล์ที่ไม่พบ
location URL ที่ไคลเอ็นต์กำลังเปลี่ยนเส้นทางไปยัง (ถ้ามี)
content_type ประเภทเนื้อหาของการตอบกลับ
charset ชุดอักขระที่ใช้สำหรับการตอบกลับ ค่าเริ่มต้นคือ "utf-8"
headers ส่วนหัวที่ใช้สำหรับการตอบกลับ

10.2.1 ตั้งค่าส่วนหัวที่กำหนดเอง

หากคุณต้องการตั้งค่าส่วนหัวที่กำหนดเองสำหรับการตอบกลับ ให้ใช้ response.headers ส่วนนี้เป็นที่ทำได้ แอตทริบิวต์ส่วนหัวเป็นแฮชที่แมปชื่อส่วนหัวกับค่าของพวกเขา และ Rails จะตั้งค่าบางส่วนให้โดยอัตโนมัติ หากคุณต้องการเพิ่มหรือเปลี่ยนส่วนหัวใด ๆ เพียงแค่กำหนดให้กับ response.headers ดังนี้:

response.headers["Content-Type"] = "application/pdf"

หมายเหตุ: ในกรณีด้านบนนั้นจะเหมาะกว่าที่จะใช้ setter content_type โดยตรง

11 การรับรองความถูกต้องของ HTTP

Rails มาพร้อมกับกลไกการรับรองความถูกต้องของ HTTP ที่มีอยู่สามรูปแบบ:

  • การรับรองความถูกต้องแบบพื้นฐาน (Basic Authentication)
  • การรับรองความถูกต้องแบบ Digest (Digest Authentication)
  • การรับรองความถูกต้องแบบ Token (Token Authentication)

11.1 การรับรองความถูกต้องแบบพื้นฐาน (Basic Authentication)

การรับรองความถูกต้องแบบพื้นฐานของ HTTP เป็นรูปแบบการรับรองความถูกต้องที่รองรับโดยเบราว์เซอร์ส่วนใหญ่และไคลเอ็นต์ HTTP อื่น ๆ เป็นตัวอย่าง เช่นพิจารณาส่วนที่จะใช้สำหรับการป้องกันการเข้าถึงส่วนการดูแลระบบที่จะสามารถใช้ได้โดยการป้อนชื่อผู้ใช้และรหัสผ่านลงในหน้าต่างโดยใช้ HTTP basic การรับรองความถูกต้องที่มีอยู่ในการใช้งานเพียงเพียงเมธอดเดียว http_basic_authenticate_with

class AdminsController < ApplicationController
  http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end

เมื่อมีการตั้งค่านี้ คุณสามารถสร้างคอนโทรลเลอร์ที่มีชื่อเนมเนมสเปซที่สืบทอดจาก AdminsController ตัวกรองจะถูกรันสำหรับแอ็กชันทั้งหมดในคอนโทรลเลอร์เหล่านั้น โดยป้องกันการเข้าถึงด้วยการรับรองความถูกต้องแบบพื้นฐาน

11.2 การรับรองความถูกต้องแบบ Digest (Digest Authentication)

การรับรองความถูกต้องของ HTTP digest authentication มีความเหนือกว่าการรับรองความถูกต้องของ basic authentication เนื่องจากไม่ต้องการให้ไคลเอนต์ส่งรหัสผ่านที่ไม่เข้ารหัสผ่านเครือข่าย (แม้ว่าการรับรองความถูกต้องของ HTTP basic authentication จะปลอดภัยเมื่อใช้งานผ่าน HTTPS) การใช้งาน digest authentication กับ Rails ต้องใช้เพียงเมธอดเดียวคือ authenticate_or_request_with_http_digest

class AdminsController < ApplicationController
  USERS = { "lifo" => "world" }

  before_action :authenticate

  private
    def authenticate
      authenticate_or_request_with_http_digest do |username|
        USERS[username]
      end
    end
end

จากตัวอย่างข้างต้น บล็อก authenticate_or_request_with_http_digest รับอาร์กิวเมนต์เพียงตัวเดียวคือชื่อผู้ใช้ และบล็อกจะส่งค่ารหัสผ่านกลับ การส่งค่า false หรือ nil จาก authenticate_or_request_with_http_digest จะทำให้การรับรองความถูกต้องล้มเหลว

11.3 การรับรองความถูกต้องของ HTTP Token Authentication

HTTP token authentication เป็นรูปแบบการรับรองความถูกต้องที่ใช้ในการใช้งาน Bearer tokens ในส่วนหัว Authorization ของ HTTP มีรูปแบบของ token หลายรูปแบบที่ใช้งานได้ แต่การอธิบายรายละเอียดของแต่ละรูปแบบนั้นอยู่นอกขอบเขตของเอกสารนี้

เป็นตัวอย่างเช่น สมมติว่าคุณต้องการใช้งาน token การรับรองความถูกต้องและการเข้าถึงที่ได้รับล่วงหน้า การใช้งาน token authentication กับ Rails ต้องใช้เพียงเมธอดเดียวคือ authenticate_or_request_with_http_token

class PostsController < ApplicationController
  TOKEN = "secret"

  before_action :authenticate

  private
    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
      end
    end
end

จากตัวอย่างข้างต้น บล็อก authenticate_or_request_with_http_token รับอาร์กิวเมนต์สองตัวคือ token และ Hash ที่มีตัวเลือกที่ถูกแยกจากส่วนหัว Authorization ของ HTTP บล็อกควรส่งค่า true ถ้าการรับรองความถูกต้องสำเร็จ การส่งค่า false หรือ nil จะทำให้การรับรองความถูกต้องล้มเหลว

12 การสตรีมและการดาวน์โหลดไฟล์

บางครั้งคุณอาจต้องการส่งไฟล์ให้กับผู้ใช้แทนการแสดงหน้า HTML ทุกคอนโทรลใน Rails มีเมธอด send_data และ send_file ซึ่งทั้งสองเมธอดจะสตรีมข้อมูลไปยังไคลเอนต์ send_file เป็นเมธอดที่สะดวกให้คุณระบุชื่อไฟล์บนดิสก์และจะสตรีมเนื้อหาของไฟล์นั้นให้คุณ

ในการสตรีมข้อมูลไปยังไคลเอนต์ให้ใช้ send_data:

require "prawn"
class ClientsController < ApplicationController
  # สร้างเอกสาร PDF ที่มีข้อมูลเกี่ยวกับไคลเอนต์และส่งกลับไปให้ผู้ใช้เป็นไฟล์ดาวน์โหลด
  def download_pdf
    client = Client.find(params[:id])
    send_data generate_pdf(client),
              filename: "#{client.name}.pdf",
              type: "application/pdf"
  end

  private
    def generate_pdf(client)
      Prawn::Document.new do
        text client.name, align: :center
        text "ที่อยู่: #{client.address}"
        text "อีเมล: #{client.email}"
      end.render
    end
end

การกระทำ download_pdf ในตัวอย่างข้างต้นจะเรียกเมธอดส่วนตัวที่จริงจัดการสร้างเอกสาร PDF และส่งกลับมาเป็นสตริง สตริงนี้จะถูกสตรีมไปยังไคลเอนต์เป็นไฟล์ดาวน์โหลดและจะมีชื่อไฟล์ที่แนะนำให้ผู้ใช้ บางครั้งเมื่อสตรีมไฟล์ให้กับผู้ใช้ คุณอาจไม่ต้องการให้พวกเขาดาวน์โหลดไฟล์ เช่น รูปภาพที่สามารถฝังลงในหน้า HTML ได้ ในการบอกเบราว์เซอร์ว่าไฟล์ไม่ได้มีไว้สำหรับการดาวน์โหลด คุณสามารถตั้งค่าตัวเลือก :disposition เป็น "inline" ค่าที่ตรงกันข้ามและค่าเริ่มต้นสำหรับตัวเลือกนี้คือ "attachment"

12.1 การส่งไฟล์

หากคุณต้องการส่งไฟล์ที่มีอยู่บนดิสก์ใช้เมธอด send_file ruby class ClientsController < ApplicationController # สตรีมไฟล์ที่เกิดขึ้นและเก็บไว้บนดิสก์ def download_pdf client = Client.find(params[:id]) send_file("#{Rails.root}/files/clients/#{client.id}.pdf", filename: "#{client.name}.pdf", type: "application/pdf") end end

การทำงานนี้จะอ่านและสตรีมไฟล์ 4 kB ในครั้งเดียว โดยไม่ต้องโหลดไฟล์ทั้งหมดเข้าสู่หน่วยความจำในครั้งเดียว คุณสามารถปิดการสตรีมด้วยตัวเลือก :stream หรือปรับขนาดบล็อกด้วยตัวเลือก :buffer_size

หากไม่ระบุ :type จะถูกคาดเดาจากนามสกุลไฟล์ที่ระบุใน :filename หากไม่ได้ลงทะเบียน content-type สำหรับนามสกุลนั้น จะใช้ application/octet-stream

คำเตือน: ควรระมัดระวังเมื่อใช้ข้อมูลที่มาจากไคลเอ็นต์ (params, cookies, เป็นต้น) เพื่อหาไฟล์บนดิสก์ เนื่องจากเป็นความเสี่ยงที่อาจทำให้ผู้ใช้เข้าถึงไฟล์ที่ไม่ได้ตั้งใจ

เคล็ดลับ: ไม่แนะนำให้สตรีมไฟล์สถิติผ่าน Rails หากคุณสามารถเก็บไฟล์ไว้ในโฟลเดอร์สาธารณะบนเว็บเซิร์ฟเวอร์ของคุณได้ การอนุญาตให้ผู้ใช้ดาวน์โหลดไฟล์โดยตรงโดยใช้ Apache หรือเว็บเซิร์ฟเวอร์อื่น ๆ จะเป็นประสิทธิภาพมากกว่า โดยไม่ต้องผ่าน Rails stack ทั้งหมด

12.2 ดาวน์โหลดแบบ RESTful

แม้ว่า send_data จะทำงานได้ดี แต่หากคุณกำลังสร้างแอปพลิเคชันแบบ RESTful การมีการกระทำแยกต่างหากสำหรับการดาวน์โหลดไฟล์ไม่จำเป็นอย่างยิ่ง ในทางปฏิบัติของ REST ไฟล์ PDF จากตัวอย่างข้างต้นสามารถถือว่าเป็นแสดงอีกหนึ่งรูปแบบของทรัพยากรลูกค้า Rails มีวิธีที่ดีในการดาวน์โหลดแบบ "RESTful" นี่คือวิธีที่คุณสามารถเขียนใหม่ตัวอย่างดังกล่าวเพื่อให้การดาวน์โหลด PDF เป็นส่วนหนึ่งของการกระทำ show โดยไม่ต้องสตรีม:

class ClientsController < ApplicationController
  # ผู้ใช้สามารถขอรับทรัพยากรนี้เป็น HTML หรือ PDF
  def show
    @client = Client.find(params[:id])

    respond_to do |format|
      format.html
      format.pdf { render pdf: generate_pdf(@client) }
    end
  end
end

เพื่อให้ตัวอย่างนี้ทำงาน คุณต้องเพิ่มประเภท MIME ของ PDF เข้ากับ Rails นี้สามารถทำได้โดยเพิ่มบรรทัดต่อไปนี้ในไฟล์ config/initializers/mime_types.rb:

Mime::Type.register "application/pdf", :pdf

หมายเหตุ: ไฟล์การกำหนดค่าไม่ได้รีโหลดทุกคำขอ ดังนั้นคุณต้องรีสตาร์ทเซิร์ฟเวอร์เพื่อให้การเปลี่ยนแปลงมีผล

ตอนนี้ผู้ใช้สามารถขอรับเวอร์ชัน PDF ของลูกค้าโดยเพิ่ม ".pdf" ใน URL:

GET /clients/1.pdf

12.3 สตรีมข้อมูลแบบสด

Rails ช่วยให้คุณสามารถสตรีมข้อมูลอื่น ๆ นอกจากไฟล์ได้ ในความเป็นจริงคุณสามารถสตรีมอะไรก็ได้ที่คุณต้องการในออบเจกต์การตอบสนอง โมดูล ActionController::Live ช่วยให้คุณสร้างการเชื่อมต่อที่ยังคงอยู่กับเบราว์เซอร์ โดยใช้โมดูลนี้คุณจะสามารถส่งข้อมูลอะไรก็ได้ไปยังเบราว์เซอร์ในเวลาที่กำหนด

12.3.1 การรวมสตรีมข้อมูลแบบสด

การรวม ActionController::Live ในคลาสคอนโทรลเลอร์ของคุณจะให้ความสามารถในการสตรีมข้อมูลให้กับทุก ๆ การกระทำภายในคอนโทรลเลอร์ คุณสามารถรวมโมดูลได้ดังนี้:

class MyController < ActionController::Base
  include ActionController::Live

  def stream
    response.headers['Content-Type'] = 'text/event-stream'
    100.times {
      response.stream.write "hello world\n"
      sleep 1
    }
  ensure
    response.stream.close
  end
end

โค้ดด้านบนจะเชื่อมต่อความสัมพันธ์ที่มีอยู่ตลอดเวลากับเบราว์เซอร์และส่งข้อความ "hello world\n" 100 ข้อความ ทุกๆ 1 วินาที

มีสองสิ่งที่ควรสังเกตในตัวอย่างด้านบน ต้องตรวจสอบให้แน่ใจว่าปิดสตรีมการตอบสนอง หากลืมปิดสตรีมจะทำให้ซ็อกเก็ตเปิดตลอดไป นอกจากนี้เรายังต้องตั้งค่าประเภทเนื้อหาเป็น "text/event-stream" ก่อนที่เราจะเขียนลงในสตรีมการตอบสนอง นี่เพราะหัวข้อไม่สามารถเขียนได้หลังจากที่การตอบสนองถูกส่งมอบ (เมื่อ response.committed? คืนค่าเป็นค่าที่เป็นจริง) ซึ่งเกิดขึ้นเมื่อคุณเขียนหรือส่งมอบสตรีมการตอบสนอง

12.3.2 การใช้ตัวอย่าง

เราสมมติว่าคุณกำลังทำเครื่องร้องคาราโอเกะและผู้ใช้ต้องการข้อความเนื้อเพลงสำหรับเพลงที่เฉพาะเจาะจง แต่ละ "เพลง" มีจำนวนบรรทัดที่เฉพาะเจาะจงและแต่ละบรรทัดใช้เวลา num_beats เพื่อจบการร้องเสร็จ

หากเราต้องการส่งข้อความเนื้อเพลงในรูปแบบการร้องคาราโอเกะ (ส่งเพลงเมื่อนักร้องเสร็จเพลงก่อนหน้า) เราสามารถใช้ ActionController::Live ได้ดังนี้:

class LyricsController < ActionController::Base
  include ActionController::Live

  def show
    response.headers['Content-Type'] = 'text/event-stream'
    song = Song.find(params[:id])

    song.each do |line|
      response.stream.write line.lyrics
      sleep line.num_beats
    end
  ensure
    response.stream.close
  end
end

โค้ดด้านบนจะส่งบรรทัดถัดไปเมื่อนักร้องเสร็จเพลงก่อนหน้า

12.3.3 ข้อคิดที่ต้องพิจารณาในการสตรีม

การสตรีมข้อมูลที่ไม่แน่นอนเป็นเครื่องมือที่มีประสิทธิภาพอย่างมาก ดังที่แสดงในตัวอย่างก่อนหน้านี้ คุณสามารถเลือกเมื่อและสิ่งที่จะส่งผ่านสตรีมการตอบสนองได้ อย่างไรก็ตามคุณควรทราบเรื่องต่อไปนี้เช่นกัน:

  • แต่ละสตรีมการตอบสนองจะสร้างเธรดใหม่และคัดลอกตัวแปรท้องถิ่นของเธรดต้นฉบับ การมีตัวแปรท้องถิ่นเยอะเกินไปอาจมีผลกระทบต่อประสิทธิภาพในทางลบ อย่างเช่นเดียวกับจำนวนเธรดที่มากเกินไปที่อาจก่อให้เกิดปัญหาเรื่องประสิทธิภาพ
  • หากลืมปิดสตรีมการตอบสนองจะทำให้ซ็อกเก็ตที่เกี่ยวข้องเปิดตลอดไป ตรวจสอบให้แน่ใจว่าคุณเรียกใช้ close เมื่อคุณใช้สตรีมการตอบสนอง
  • เซิร์ฟเวอร์ WEBrick จะเก็บข้อมูลการตอบสนองทั้งหมดไว้ในแคช ดังนั้นการรวม ActionController::Live จะไม่ทำงาน คุณต้องใช้เว็บเซิร์ฟเวอร์ที่ไม่บัฟเฟอร์การตอบสนอง

13 การกรองล็อก

Rails เก็บไฟล์บันทึกสำหรับแต่ละสภาพแวดล้อมในโฟลเดอร์ log นี้เป็นสิ่งที่มีประโยชน์อย่างมากเมื่อตรวจสอบว่าเกิดอะไรขึ้นจริงๆ ในแอปพลิเคชันของคุณ แต่ในแอปพลิเคชันที่ใช้งานจริงคุณอาจไม่ต้องการให้ข้อมูลทุกอย่างถูกเก็บไว้ในไฟล์บันทึก

13.1 การกรองพารามิเตอร์

คุณสามารถกรองพารามิเตอร์ของคำขอที่เป็นความลับออกจากไฟล์บันทึกของคุณโดยเพิ่มพารามิเตอร์เหล่านั้นใน config.filter_parameters ในการกำหนดค่าแอปพลิเคชัน พารามิเตอร์เหล่านี้จะถูกทำเครื่องหมายว่า [FILTERED] ในบันทึก

config.filter_parameters << :password

หมายเหตุ: พารามิเตอร์ที่ให้มาจะถูกกรองด้วยการจับคู่ส่วนหนึ่งของนิพจน์เร็กซ์ Rails เพิ่มตัวกรองเริ่มต้น เช่น :passw, :secret, และ :token, ในไฟล์เริ่มต้นที่เหมาะสม (initializers/filter_parameter_logging.rb) เพื่อจัดการพารามิเตอร์แอปพลิเคชันทั่วไป เช่น password, password_confirmation และ my_token

13.2 การกรองการเปลี่ยนเส้นทาง

บางครั้งคุณอาจต้องการกรองที่อยู่ที่เป็นความลับที่แอปพลิเคชันของคุณกำลังเปลี่ยนเส้นทางไปยัง คุณสามารถทำได้โดยใช้ตัวเลือกการกำหนดค่า config.filter_redirect:

config.filter_redirect << 's3.amazonaws.com'

คุณสามารถตั้งค่าให้เป็น String, Regexp หรืออาร์เรย์ของทั้งคู่ได้

config.filter_redirect.concat ['s3.amazonaws.com', /private_path/]

URL ที่ตรงกันจะถูกทำเครื่องหมายว่า '[FILTERED]'

14 การช่วยเหลือ

สิ่งที่เป็นไปได้ที่สุดคือแอปพลิเคชันของคุณจะมีข้อบกพร่องหรือเกิดข้อยกเว้นที่ต้องจัดการ ตัวอย่างเช่น หากผู้ใช้ตามลิงก์ไปยังทรัพยากรที่ไม่มีอยู่ในฐานข้อมูลแล้ว Active Record จะยกเลิกการทำงานและส่งออกข้อยกเว้น ActiveRecord::RecordNotFound

การจัดการข้อยกเว้นเริ่มต้นของ Rails จะแสดงข้อความ "500 Server Error" สำหรับข้อยกเว้นทั้งหมด หากคำขอถูกทำในเครื่องคอมพิวเตอร์ท้องถิ่น จะแสดง traceback ที่ดีและข้อมูลเพิ่มเติมเพื่อให้คุณสามารถรู้ว่าเกิดอะไรผิดพลาดและจัดการกับมันได้ หากคำขอถูกส่งจากระยะไกล Rails จะแสดงข้อความ "500 Server Error" ที่เรียบง่ายให้แก่ผู้ใช้ หรือ "404 Not Found" หากเกิดข้อผิดพลาดในการเส้นทางหรือไม่พบบันทึก บางครั้งคุณอาจต้องปรับแต่งวิธีการจับข้อผิดพลาดเหล่านี้และวิธีการแสดงผลให้แก่ผู้ใช้ มีระดับการจัดการข้อยกเว้นหลายระดับที่มีให้ใช้ในแอปพลิเคชัน Rails:

14.1 เทมเพลต 500 และ 404 เริ่มต้น

โดยค่าเริ่มต้นในสภาพแวดล้อมการใช้งานจริงแอปพลิเคชันจะแสดงข้อความข้อผิดพลาด 404 หรือ 500 ข้อความ ในสภาพแวดล้อมการพัฒนา ข้อยกเว้นที่ไม่ได้รับการจัดการจะถูกยกเลิกเพียงอย่างเดียว ข้อความเหล่านี้จัดเก็บอยู่ในไฟล์ HTML แบบสถิตที่อยู่ในโฟลเดอร์ public ใน 404.html และ 500.html ตามลำดับ คุณสามารถปรับแต่งไฟล์เหล่านี้เพื่อเพิ่มข้อมูลและสไตล์เพิ่มเติม แต่จำไว้ว่าเป็นไฟล์ HTML แบบสถิต นั่นคือคุณไม่สามารถใช้ ERB, SCSS, CoffeeScript หรือเลเอาท์สำหรับไฟล์เหล่านี้ได้

14.2 rescue_from

หากคุณต้องการทำสิ่งที่ซับซ้อนขึ้นเล็กน้อยเมื่อจับข้อผิดพลาด คุณสามารถใช้ rescue_from ซึ่งจัดการข้อผิดพลาดของประเภทหนึ่ง (หรือหลายประเภท) ในคอนโทรลเลอร์ทั้งหมดและคลาสย่อยของมัน

เมื่อเกิดข้อผิดพลาดที่ถูกจับโดยคำสั่ง rescue_from วัตถุข้อผิดพลาดจะถูกส่งให้กับตัวจัดการ ตัวจัดการสามารถเป็นเมธอดหรือวัตถุ Proc ที่ส่งผ่านให้กับตัวเลือก :with คุณยังสามารถใช้บล็อกโดยตรงแทนวัตถุ Proc แบบชัดเจนได้

นี่คือวิธีการใช้ rescue_from เพื่อดักจับข้อผิดพลาด ActiveRecord::RecordNotFound ทั้งหมดและทำสิ่งใดกับมัน

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private
    def record_not_found
      render plain: "404 Not Found", status: 404
    end
end

แน่นอนว่าตัวอย่างนี้ไม่ได้ซับซ้อนและไม่ปรับปรุงการจัดการข้อผิดพลาดเริ่มต้นเลย แต่เมื่อคุณสามารถจับข้อผิดพลาดเหล่านั้นได้คุณสามารถทำอะไรก็ได้ตามที่คุณต้องการ เช่นคุณสามารถสร้างคลาสข้อผิดพลาดที่กำหนดเองที่จะถูกส่งออกเมื่อผู้ใช้ไม่มีสิทธิ์เข้าถึงส่วนใดส่วนหนึ่งของแอปพลิเคชันของคุณ:

class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :user_not_authorized

  private
    def user_not_authorized
      flash[:error] = "You don't have access to this section."
      redirect_back(fallback_location: root_path)
    end
end

class ClientsController < ApplicationController
  # ตรวจสอบว่าผู้ใช้มีสิทธิ์เข้าถึงไคลเอ็นต์หรือไม่
  before_action :check_authorization

  # โปรดทราบว่าการกระทำไม่ต้องกังวลเกี่ยวกับเรื่องราวที่เกี่ยวข้องทั้งหมด
  def edit
    @client = Client.find(params[:id])
  end

  private
    # หากผู้ใช้ไม่ได้รับอนุญาตให้เพียงแค่โยนข้อยกเว้น
    def check_authorization
      raise User::NotAuthorized unless current_user.admin?
    end
end

คำเตือน: การใช้ rescue_from กับ Exception หรือ StandardError อาจทำให้เกิดผลข้างเคียงที่ร้ายแรงเนื่องจากมันป้องกัน Rails จากการจัดการข้อผิดพลาดอย่างถูกต้อง ดังนั้นไม่แนะนำให้ใช้เว้นแต่จะมีเหตุผลที่แข็งแกร่ง

หมายเหตุ: เมื่อทำงานในสภาพแวดล้อมการผลิต ข้อผิดพลาด ActiveRecord::RecordNotFound ทั้งหมดจะแสดงหน้าข้อผิดพลาด 404 ยกเว้นว่าคุณต้องการพฤติกรรมที่กำหนดเองคุณไม่จำเป็นต้องจัดการด้วย

หมายเหตุ: บางข้อผิดพลาดบางอย่างสามารถถูกกู้คืนได้เฉพาะจากคลาส ApplicationController เท่านั้น เนื่องจากมันถูกเรียกขึ้นก่อนที่คอนโทรลเลอร์จะถูกเริ่มต้นและการดำเนินการจะถูกดำเนินการ

15 บังคับโปรโตคอล HTTPS

หากคุณต้องการให้การสื่อสารกับคอนโทรลเลอร์ของคุณเป็นไปได้เฉพาะผ่าน HTTPS เท่านั้น คุณควรทำเช่นนั้นโดยเปิดใช้งาน middleware ActionDispatch::SSL ผ่าน config.force_ssl ในการกำหนดค่าสภาพแวดล้อมของคุณ

16 จุดตรวจสุขภาพที่มีอยู่แบบในตัว

Rails ยังมาพร้อมกับจุดตรวจสุขภาพที่มีอยู่แบบในตัวที่สามารถเข้าถึงได้ที่เส้นทาง /up จุดตรวจสุขภาพนี้จะส่งคืนรหัสสถานะ 200 หากแอปพลิเคชันเริ่มทำงานโดยไม่มีข้อผิดพลาด และรหัสสถานะ 500 ในกรณีอื่น ๆ

ในการผลิต แอปพลิเคชันหลายรายการต้องรายงานสถานะของตนไปยังที่อยู่ข้างต้น ไม่ว่าจะเป็นการตรวจสอบเวลาทำงานที่จะแจ้งเตือนวิศวกรเมื่อเกิดข้อผิดพลาด หรือโหลดบาลานซ์หรือคอนโทรลเลอร์ Kubernetes ที่ใช้ในการกำหนดสุขภาพของพ็อด จุดตรวจสุขภาพนี้ถูกออกแบบให้เป็นขนาดเดียวที่จะทำงานในสถานการณ์หลาย ๆ

ในขณะที่แอปพลิเคชัน Rails ที่สร้างขึ้นใหม่จะมีจุดตรวจสุขภาพที่ /up คุณสามารถกำหนดเส้นทางให้เป็นอะไรก็ได้ตามที่คุณต้องการใน "config/routes.rb" ของคุณ:

Rails.application.routes.draw do
  get "healthz" => "rails/health#show", as: :rails_health_check
end

จุดตรวจสุขภาพจะสามารถเข้าถึงได้ผ่านเส้นทาง /healthz

หมายเหตุ: จุดตรวจสุขภาพนี้ไม่สะท้อนสถานะของความขึ้นอยู่กับความขึ้นอยู่ของแอปพลิเคชันทั้งหมด เช่นฐานข้อมูลหรือคลัสเตอร์ redis แทน "rails/health#show" ด้วยการกระทำควบคุมของคุณเองหากคุณมีความต้องการที่เฉพาะเจาะจงในแอปพลิเคชันของคุณ

คิดอย่างรอบคอบเกี่ยวกับสิ่งที่คุณต้องการตรวจสอบเนื่องจากมันอาจนำไปสู่สถานการณ์ที่แอปพลิเคชันของคุณถูกเริ่มต้นใหม่เนื่องจากบริการภายนอกที่เสียหาย อย่างไรก็ตามคุณควรออกแบบแอปพลิเคชันของคุณให้สามารถจัดการกับการขัดข้องได้อย่างสุภาพ

ข้อเสนอแนะ

คุณสามารถช่วยปรับปรุงคุณภาพของคู่มือนี้ได้

กรุณาช่วยเพิ่มเติมหากพบข้อผิดพลาดหรือข้อผิดพลาดทางความจริง เพื่อเริ่มต้นคุณสามารถอ่านส่วน การสนับสนุนเอกสาร ของเราได้

คุณอาจพบเนื้อหาที่ไม่สมบูรณ์หรือเนื้อหาที่ไม่ได้อัปเดต กรุณาเพิ่มเอกสารที่ขาดหายไปสำหรับเนื้อหาหลัก โปรดตรวจสอบ Edge Guides ก่อนเพื่อตรวจสอบ ว่าปัญหาได้รับการแก้ไขหรือไม่ในสาขาหลัก ตรวจสอบ คู่มือแนวทาง Ruby on Rails เพื่อดูรูปแบบและกฎเกณฑ์

หากคุณพบข้อผิดพลาดแต่ไม่สามารถแก้ไขได้เอง กรุณา เปิดปัญหา.

และสุดท้าย การสนทนาใด ๆ เกี่ยวกับ Ruby on Rails เอกสารยินดีต้อนรับที่สุดใน เว็บบอร์ดอย่างเป็นทางการของ Ruby on Rails.