Rails参数方法:为什么可以像散列一样访问它?



查看此代码:

params[:id]

Params被认为是一个方法。如果我错了,请纠正我。但这就像从散列中读取一样。所以,我现在很困惑。

如果params是一个方法:显示的代码示例如何工作?

你是正确的,params是一个方法,但在这里params方法返回ActionController::Parameters的实例,我们调用哈希访问器方法#[]

这是ruby中对返回对象调用方法的常见模式。让我们看一个简单的例子:

def params
{
id: 101,
key: 'value',
foo: 'bar'
}
end
params[:id] # => 101
params[:foo] # => 'bar'

在示例中可以看到,方法params返回一个哈希对象,我们在返回的对象上调用哈希访问器方法#[]

参考railsparams方法:https://github.com/rails/rails/blob/5e1a039a1dd63ab70300a1340226eab690444cea/actionpack/lib/action_controller/metal/strong_parameters.rb#L1215-L1225

def params
@_params ||= begin
context = {
controller: self.class.name,
action: action_name,
request: request,
params: request.filtered_parameters
}
Parameters.new(request.parameters, context)
end
end

ruby初学者注意:在ruby中,我们可以调用不带括号的方法。因此,上面的调用相当于params()[:id]

这些被称为方括号访问器,您可以通过实现[][]=方法将它们添加到任何对象中。

class Store
def initialize(**kwargs)
kwargs.each { |k,v| instance_variable_set("@#{k}", v) }
end

def [](key)
instance_variable_get("@#{key}")
end
def []=(key, value)
instance_variable_set("@#{key}", value)
end
end

store = Store.new(foo: 1, bar: 2, baz: 3)
store[:foo] # 1
store[:foo] = 100
store[:foo] # 100

同样,当你调用params[:id]时,params方法将首先被调用,所以你在ActionController::Parameters的实例上调用[],就像下面这个简单的例子:

def foo
Store.new(bar: 1)
end
foo[:bar] # 1

由于父类是可选的,它相当于调用params()[:id]

在控制器的上下文中,params确实是一个方法。假设我们有一个OrganizationsController,它在restful端点中公开#index操作。我将使用pry gem添加一个断点,以便我们可以更好地理解params是什么:

class OrganizationsController < ApplicationController
def index
binding.pry  # Runtime will stop here
render json: Organization.all
end
end

让我们访问以下URL:

http://localhost: 3000/organizations.json吗?foo = bar

我们实际上可以通过显式调用()来验证params是一个方法:

> params()
=> #<ActionController::Parameters {"foo"=>"bar", "controller"=>"organizations", "action"=>"index", "format"=>"json"} permitted: false>

或者询问Ruby该方法在哪里定义:

> method(:params).source_location
=> ["/home/myuser/.rvm/gems/ruby-3.0.2@myproject/gems/actionpack-6.1.4.1/lib/action_controller/metal/strong_parameters.rb", 1186]

调用params返回的对象不是Hash,而是ActionController::Parameters:

> params.class
=> ActionController::Parameters
然而,我们可以调用:[]方法,因为它实际上是在ActionController::Parameters类中定义的(见代码)

这使得它看起来像是一个Hash,但实际上它不是。例如,我们不能调用Hash方法params反转,因为它没有定义:

> params.invert
NoMethodError: undefined method `invert' for #<ActionController::Parameters {"foo"=>"bar", "controller"=>"organizations", "action"=>"index", "format"=>"json"} permitted: false>