ทำความเข้าใจกับ Mass Assignment และ Strong parameter ของ Ruby on Rails

Mass assignment เป็นลักษณะการทำงานอย่างหนึ่งของ Rails (ผมมองว่ามันเป็น feature เลย) ที่ช่วยอำนวยความสะดวกให้เรา เวลาที่ต้องการสร้างหรืออัพเดต Active Record อ็อบเจก (ORM อ็อบเจก)

เมธอด new, create หรือ update ของ Active Record สามารถรับค่าของ params แล้วเอาข้อมูลในนั้นไปดำเนินการต่อได้เลย โดย params นั้นเป็น hash ที่เก็บข้อมูลที่รับเข้ามาจาก web application ภายนอก ทั้งในรูปของ query string หรือในรูปของ Http POST request เข้าไปในเมธอด

แทนที่เราจะต้องกำหนดค่าของแอตทริบิวท์ที่ละตัวให้กับอ็อบเจกแบบนี้

article = Article.new
article.title = First Topic
article.body = This is article content
article.status = draft

Mass assignment ทำให้เราสามารถทำแบบนี้แทนได้

params = {title: First Topic, body: this is content, status: :draft}
Article.new(params)
# => #<Article id: 1, title: “First Topic”, body: “this is content”, status: “draft” >

Mass assignment เป็นไอเดียที่ดี แต่ก็มีช่องโหว่ที่อาจทำให้เราโดนเล่นงานจากผู้ไม่หวังดีได้เหมือนกัน สมมติว่าเวลาเราอัพเดตข้อมูลของ user เราใช้ mass assignment ในการอัพเดต โดยป้อน hash เข้าไปในเมธอด new แบบนี้

@article.update_attributes({ username: worrawutp })

ทีนี้ วันดีคืนดีเกิดมีผู้ไม่หวังดี พยายามป้อนข้อมูลเป็น hash เข้ามาเพื่ออัพเดต user เหมือนกัน โดยป้อนเข้ามาแบบนี้

@article.update_attributes({ username: worrawutp, password: newpass, password_confirmation: newpass, admin: true })

วิธีนี้ทำให้ผู้ไม่หวังดี สามารถทำการเปลี่ยน password ของเราได้ และยังเปลี่ยนตัวเองให้เป็น admin อีกด้วย จะเห็นว่าถ้า mass assignment ยอมรับ hash ที่มีข้อมูลปลอมๆ อย่างนี้เข้ามาล่ะก็ งานเข้าเราชุดใหญ่เลยทีเดียว ด้วยเหตุนี้ จึงมีการคิดกลไกที่เรียกว่า strong parameter ขึ้นมา เพื่อป้องกันการหลอกใส่ข้อมูลเข้ามาโดยไม่ได้รับอนุญาต

โหลดข้อมูลให้ชัวร์ด้วย Strong parameters strong parameter เป็นเงื่อนไขที่ Rails กำหนดขึ้นเพื่อให้ dev อย่างเรา ต้องทำการ “คัดกรอง” โดยต้องระบุเอง ว่ามีพารามิเตอร์อะไรบ้างที่แอพของเรารอให้เบราเซอร์ submit เข้ามา และมีพารามิเตอร์อะไรบ้างที่แอพยอมรับให้นำไปใช้ในการทำ mass assignment ซึ่งกระบวนการที่ว่านี้จะทำได้ด้วยกาูลรใช้เมธอด require และ permit ซึ่งจะเรียกใช้ต่อจาก เมธอด params อีกทีนึง

Note: params เป็นเมธอดของโมดูล ActionController::StrongParameters ซึ่งรีเทิร์นค่าออกมาเป็น อ็อบเจกต์ของคลาส ActionController::Parameters ค่าพารามิเตอร์ที่ส่งมาจากเว็บเบราเซอร์ด้วยวิธีต่างๆ ไม่ว่าจะมาจาก query string, เป็นส่วนหนึ่งของ url หรือมาจาก form data ค่าทั้งหลายจะถูกเก็บเป็น ค่าของ hash เอาไว้ในไอ้เจ้า params ตัวนี้

เพื่อความปลอดภัยจากกรณีที่ยกตัวอย่างมานั้น Rails ตั้งแต่ version 4 จึงกำหนดว่าถ้าเราจะทำ mass assignment เราจะต้องทำผ่าน strong parameter เท่านั้น โดยตัวอย่างของการระบุ และอนุญาตพารามิเตอร์ นั้นเขียนเป็นโค้ดได้แบบนี้

class User < ApplicationController
    def create
        User.create(user_params)
    end
    def update
        user = User.find(params[:id])
        user.update(user_params)
    end

    private
    def user_params
        params.require(:user).permit(:username, :age)    
    end 
end

โค้ดข้างต้นแทบจะเรียกได้ว่าเป็นฟอร์มมาตรฐานของการ ระบุพารามิเตอร์เพื่อเอาไปใช้ mass assignment เลยก็ว่าได้ เราจะเห็นว่าข้อมูลของ hash ที่จะถูกป้อนเข้าไปใน User.create นั้นเป็นการทำ mass assignment ซึ่ง การกรองพารามิเตอร์หรือ strong parameter นั้นจะถูกแยกมาทำเป็น private เมธอด user_params เราจะเห็นว่าเราเรียกใช้เมธอด require(:user) ก่อน หมายความว่าใน hash ที่จะเอามาใส่ mass assignment นั้นต้องมีคีย์ชื่อ user ไม่มีไม่ได้นะ ซึ่งค่าที่คืนมาจากเมธอดนี้ก็คือ hash ในตำแหน่งคีย์ user นั่นเอง ส่วนเมธอด permit(:username, :age) ที่เรียกใช้ต่อมา หมายความว่าพารามิเตอร์ที่ชื่อ username และ age เท่านั้นที่แอพจะอนุญาติให้ใช้สำหรับ mass assignment ได้

ยกตัวอย่างเช่น ถ้า hash user ที่ require เข้ามามีคีย์ลูกอยู่หลายตัวเช่น user: { username: “worrawutp”, password: “xxx”, age: 30, address: “bangkok” } โค้ด permit(:username, :age) จะเป็นตัวบอกว่าค่าของพารามิเตอร์ username และ address เท่านั้นที่สามารถใช้ในการทำ mass assignment ได้

Strong parameter กับ nested attribute

อาจมีบางครั้งที่เราต้องการผ่านค่าจาก form ของแอพเราเข้ามาโดยที่พารามิเตอร์จาก form ของเราอยู่ในรูปแบบของ form ซ้อน form หรือที่เรียกว่า nested attribute ซึ่งเวลา submit ข้อมูลจากเบราเซอร์เข้ามาที่แอพเรา สังเกตที่ console log ของ rails server เราจะเห็นว่าพารามิเตอร์ในลักษณะดังกล่าวจะมีหน้าตาประมาณนี้

Parameters: {"utf8"=>"✓", "authenticity_token"=>"SYCcVGP6C/eD8oB2kOqmC1vH77uwI4fZJEwnZMyEIxWvOZRKaIgF3PVA3/RD0a1A9yGca4KfHimSNSlkNUV6www==", "user"=>{"username"=>"user1", "email"=>"user1@sample.com", "profile_attributes"=>{"fullname"=>"my fullname", "nickname"=>"john", "mobile"=>"ftc8ms6", "line"=>"j9t55n1", "facebook"=>"q8p3gul", "instragram"=>"edzfn15"}}, "commit"=>"New User"}

เราจะเห็นว่ามีพารามิเตอร์ที่ชื่อ profile_attributes อยู่ใน params ด้วย ซึ่งพารามิเตอร์ที่มีชื่อลงท้ายด้วย _attributes นี่เองที่เป็น พารามิเตอร์แบบ nested attribute หรือพารามิเตอร์ซ้อนe

ทีนี้ถ้ามี nested attribute วิ่งเข้ามาแล้วเราต้องการทำ mass assignment ไม่ว่าจะเป็นที่แอกชั่น create หรือ update ก็ตาม เราก็จะต้องทำการกรองพารามิเตอร์ด้วยเมธอด require และ permit ด้วย โดยในกรณีของ nested attribute จะต่างกันนิดนึง

สมมติว่า params ที่วิ่งเข้ามามีหน้าตาเหมือนกับพารามิเตอร์ตัวอย่างข้างต้น หน้าตาของโค้ดของเราก็จะเป็นแบบนี้

class User < ActiveRecord::Base
    has_one :profile
    accepts_nested_attributes_for :profile
end

เรากำหนดให้ User และ Profile มีความสัมพันธ์แบบ has_one และระบุ accepts_nested_attributes_for :profile เพื่อให้ Rails รับรู้ว่าเราจะป้อนข้อมูลและส่งพารามิเตอร์แบบ nested attribute

class UsersController < ApplicationController
    def create
        user = User.create(user_param)
    end

    private
    def user_param
        params.require(:user).permit(:username, :email, profile_attributes: [ :fullname, :nickname, :mobile, :line, :facebook, :instragram ]
    end
end

จากโค้ดจะเห็นว่า ที่เมธอด user_param จะมีเรียกใช้ require(:user) ก่อน นั่นคือเราบอก Rails ว่า hash params ที่จะเอามาทำ mass assignment นั้นต้องมีคีย์ที่ชื่อ user นะ จากนั้นจะเป็นการใช้เมธอด permit ต่อมา ซึ่งเป็นการระบุว่า พารามิเตอร์ที่อนุญาตให้ใช้กับ mass assignment ได้ก็คือ พารามิเตอร์ชื่อ username, email และ profile_attributes

เนื่องจากตัวของ profile_attributes มันเป็น hash ดังนั้นเราจะต้องระบุลงไปด้วยว่าพารามิเตอร์ภายใน profile_attributes ตัวไหนบ้างที่เรายอมให้ใช้กับ mass assignment ซึ่งจะเห็นว่าเราระบุพารามิเตอร์ของ profile ทั้งหมดเลยคือ fullname, nickname, mobile, line, facebook และ instragram

พอเรากำหนดเงื่อนไขการกรอง (ทำ strong parameter) ในเมธอด user_params เสร็จแล้วว ก็จะเห็นว่าเราสามารถเอา user_params ผ่านเข้าไปเป็นพารามิเตอร์ของ User.create ในแอกชั่น create ได้เลย ซึ่ง Rails จะทำ mass assignment ให้เราโดยอัตโนมัติ นั่นคือกำหนดค่าแอตทริบิวท์ของทั้ง user และ profile ให้เลย ซึ่งตรงนี้ถือว่าเป็นข้อดีของ mass assignment เนื่องจากโค้ดของเราจะค่อนข้างเรียบร้อยและอ่านง่ายกว่าการมากำหนดค่าให้แอตทริบิวท์ทีละตัว

แชร์โพสได้จากลิ้งด้านล่าง ขอบคุณครับ