Strong parameter 參數與 Array 之:老師我可以換位置嗎?
在故事開始前
在Rails 多對多關聯設定與存取的文章中,我偷懶的在未測試的狀況下將Strong parameter 的參數位置進行對調,結果被王綠島抓包沒辦法使用XD。
params.require(:post).permit( #other_stuff, category_ids:[]) # 正確的params.require(:post).permit( category_ids:[], #other_stuff) # 改錯的
為什麼只是改參數位置就不能用?
找了資料後發現 permit
會把全部的參數當作 Array 去操作,所以 permit(:a, :b, #other stuff)
的輸入會被轉換成 [:a, :b, #other stuff]
,也就是可以把 permit(:a, :b, #other stuff)
與permit([:a, :b, #other stuff])
當作是同樣的東西。
我們知道可以用[]
作為建構子,裡面放 Object 並用逗號隔開就能產生 Array,下面測試看看:
$ [:a, :b, c: []] #=> [:a, :b, {:c=>[]}]$ [:a, b: [], :c] #syntax error, unexpected ']', expecting =>
[:a, b: [], :c]
^$ [:a, b: [], c: []] #=> [:a, {:b=>[], :c=>[]}]$ [:a, {b: []}, :c] #=> [:a, {:b=>[]}, :c]
第一行使用 [:a, :b, c: []]
會得到一個 Array ,Array 內包含了三個Object,分別是 :a
跟 :b
兩個 Symbol 以及一個 Hash {:c=>[]}
,代表可以用這個方法直接建立一個包含 Hash 的 Array。
第二行使用 [:a, b: [], :c]
會得到 syntax error ,而且告訴我們 :c
後面應該要是 =>
而非 ]
,所以代表程式預期 :c
的位置不單單只是個 Symbol,而是要一組 key-value pair。
第三行依照剛剛的推論輸入[:a, b: [], c: []]
後發現,雖然在 []
建構子中用逗號隔開 :a
、b: []
與 c: []
,但得到的並不是包含一個symbol跟兩個 Hash 的Array,而是他把b: [], c: []
視為一個 Hash。
不過只要用大括號明確定義你的 Hash 的範圍就不會有這個困擾,例如第四行的結果。把這個作法套用到 permit
的參數內,params.require(:post).permit(category_ids:[], #other_stuff)
只要改成params.require(:post).permit({category_ids:[]}, #other_stuff)
就能夠使用了!
到這邊應該算是解決了 Strong parameter 的參數換位問題。 但又點出了另一個疑問…
Ruby 怎麼解讀程式碼
為什麼用 [:a, b: [], :c]
會 syntax error,用[:a, b: [], c: []]
會得到[:a, {:b=>[], :c=>[]}]
,我認為有兩個原因:
- 編譯器解讀程式碼時是由左向右讀
- Ruby 的語法糖衣:括號可省略
首先是編譯器由左向右解讀程式碼,同樣以[:a, b: [], c: []]
舉例來說:
第一階段:遇到[
,所以認為現在是要建立一個 Array ,而且預期接下來會碰到的是 index 為 0 時所對應的值 / Object,然後最後會以 ]
來結束 Array 宣告。
:a,
,並預期會有描述下一個 index 對應值的語法出現。第二階段:遇到:a,
。因為 Array 是以逗號隔開各個 index 對應的內容,所以可以確定 index 為0時所對應的值為 :a
,並預期接下來會碰到的是 index 為 1 時所對應的值 / Object,然後最後會以 ]
來結束 Array 宣告。
b: ,發現是個 Hash ,
並預期會有描述 Hash value 的語法出現。第三階段:遇到 b:
, 這語法代表 Ruby 中 Hash 的 key ,表示現在在描述的是一個 Hash,也就是 index 為 1 時所對應的 Object 是一個 Hash!既然 key 出現了那後面應該要有 value,所以預期接下來會碰到的是 b:
所對應的value。然後要等 Hash 的東西講完了才會回到 Array ,所以最後會以 ]
來結束 Array 宣告。
[],
,發現後面全部都會變成 Hash的!第四階段:遇到 [],
,因為 Hash 也是以逗號隔開內容,所以可以確定 b:
所對應的value為 []
。問題是 Hash 跟 Array 一樣都是以逗號隔開內容,又因為在前面省略了 {
,沒辦法以 }
來結束 Hash 的宣告,導致後面的東西都會被認為是 Hash 的內容物! 所以程式會預期接下來會碰到的是 Hash 裡面的另外一組 key-value pair 。直到最後碰到 ]
來結束 Array ,才會順便終結 Hash 的宣告。以[:a, b: [], c: []]
的案例來說後面全部都符合預期,所以平安無事。
現在再回去看前面跑出 syntax error 的語法:
$ [:a, b: [], :c] #syntax error, unexpected ']', expecting =>
[:a, b: [], :c]
^
:c
的部分符合第四階段預期的 key-value pair 中的 key,但是沒有出現指派 value 的 =>
運算子,反而是出現 ]
直接結束掉 Array,不符合程式預期的結果,就噴錯誤了。
再看看有使用大括號的作法:
$ [:a, {b: []}, :c] #=> [:a, {:b=>[]}, :c]
因為有使用大括號,所以能夠以 {}
來限制 Hash 的範圍,才能夠結束定義 Hash 的步驟,重新回到定義 Array 內容的流程上。
所以下次用 []
建立 Array ,又不想把 Hash 放最後,記得要用大括號 {}
包起來,不然後面的東西會被 Hash 搶光光囉!
註:上面編譯器解讀方式舉例是簡化過的,省略了一些東西,詳細內容可參閱參考資料2。(老實說我沒能力全部理解內容,有講錯還請多指教)
參考資料
- strong_parameters.rb:
permit
會把全部的參數當作 Array 去操作 - Ruby Hacking Guide-Chapter 9:說明編譯器中 Parser 與 Scanner 的運作模式,也就是程式在解讀程式碼的方法。
- How Ruby Parses and Compiles Your Code:跟參考資料2講的是差不多的東西,不過有附圖舉例說明。
- Rails 多對多關聯設定與存取:本篇前傳(誤)