strong parameter
ActiveModel::ForbiddenAttributesError
因为active_record不像pojo那样可通过getter/setter来获取自身需要的属性,而是给啥key/value它就拼出啥sql,所以,如果前端给了多余的field,则sql会出现不存在于schema中的column,为免从db报错,应尽早拦截此种错误
另一方面,即使column存在,你也不一定每种动作都为model赋那么多值,所以,params应能提供限制mass_assignment的功能
于是,便有了strong parameter
如果使用params直接给model赋值,最终是会raise ActiveModel::ForbiddenAttributesError的,调用栈如下

代码如下,如果是Parameters,则应permitted,如果不是,就无所谓,例如自己构造个hash来赋值
module ActiveModel
class ForbiddenAttributesError < StandardError
end
module ForbiddenAttributesProtection # :nodoc:
protected
def sanitize_for_mass_assignment(attributes)
if attributes.respond_to?(:permitted?)
raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
attributes.to_h
else
attributes
end
end
alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
end
end
require/permit
使用require/permit来限制mass_assignment,调用栈如下

require所做的就是,调用[],如果返回的是一个Hash,则convert_hashes_to_parameters,将此子集重新包装为一个Parameters,塞回到父集中,并返回这个包装后的子集
如果是返回的是普通字符串或已包装为Parameters,则原样返回
def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value)
@parameters[key] = converted unless converted.equal?(value)
converted
end
def convert_value_to_parameters(value)
case value
when Array
return value if converted_arrays.member?(value)
converted = value.map { |_| convert_value_to_parameters(_) }
converted_arrays << converted
converted
when Hash
self.class.new(value)
else
value
end
end
另外,在调用[]的时候,是会检查该参数是否有值的,如果无,则会抛ParameterMissing。这避免了form嵌套参数或json嵌套,在传错某一嵌套层名称时,报undefine method `[]` for nil
def require(key)
self[key].presence || raise(ParameterMissing.new(key))
end
permit所做的就是,抽取filters所指的key/value,建立一个新的Parameters,并递归设置它及其子集@permitted = true。
(子集也会因为在permitted_scalar_filter中用[]抽取,而convert_hashes_to_parameters。当然非Hash的子集就没有permit!方法了,所以if v.respond_to? :permit!)
def permit(*filters)
params = self.class.new
filters.flatten.each do |filter|
case filter
when Symbol, String
permitted_scalar_filter(params, filter)
when Hash then
hash_filter(params, filter)
end
end
unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
params.permit!
end
def permitted_scalar_filter(params, key)
if has_key?(key) && permitted_scalar?(self[key])
params[key] = self[key]
end
keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
if permitted_scalar?(self[k])
params[k] = self[k]
end
end
end
def permit!
each_pair do |key, value|
Array.wrap(value).each do |v|
v.permit! if v.respond_to? :permit!
end
end
@permitted = true
self
end
如果真的有多余的参数,而又想报警,可设置action_on_unpermitted_parameters来记录或抛异常
def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any?
case self.class.action_on_unpermitted_parameters
when :log
name = "unpermitted_parameters.action_controller"
ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
when :raise
raise ActionController::UnpermittedParameters.new(unpermitted_keys)
end
end
end
def unpermitted_keys(params)
self.keys - params.keys - self.always_permitted_parameters
end
为controller提供的params方法,直接就是使用了Parameters.new :
module StrongParameters
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
# Returns a new ActionController::Parameters object that
# has been instantiated with the request.parameters.
def params
@_params ||= Parameters.new(request.parameters)
end
# Assigns the given +value+ to the +params+ hash. If +value+
# is a Hash, this will create an ActionController::Parameters
# object that has been instantiated with the given +value+ hash.
def params=(value)
@_params = value.is_a?(Hash) ? Parameters.new(value) : value
end
end
然后在action_controller/base.rb中,为controller增加params方法:
MODULES = [
#...
StrongParameters,
#...
]
MODULES.each do |mod|
include mod
end
完整调用栈