Viewing this code:
params[:id]
Params is considered to be a method. Correct me if I'm wrong there. But that's like reading from a hash. So, I'm currently confused.
If params is a method: How does the shown code-example work?
CodePudding user response:
You are correct that params
is a method, but here the params
method returns an instance of ActionController::Parameters
and we call hash accessor method #[]
on it.
This is a common pattern in ruby to call methods on the returned object. Let's see it by a simple example:
def params
{
id: 101,
key: 'value',
foo: 'bar'
}
end
params[:id] # => 101
params[:foo] # => 'bar'
As you can see in the example, method params
returns a hash object and we call hash accessor method #[]
on the returned object.
Reference to rails params
method: 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
Note for ruby beginners: In ruby, we can call methods without parenthesis. So, above call is equivalent to params()[:id]
.
CodePudding user response:
Those are known as square bracket accessors and you can add them to any object by implementing the []
and []=
methods.
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
Also when you call params[:id]
- the params
method will be called first so you're calling []
on an instance of ActionController::Parameters just like in this simplefied example:
def foo
Store.new(bar: 1)
end
foo[:bar] # 1
Since parens are optional its equivilent to calling params()[:id]
.
CodePudding user response:
In the context of a Controller, params
is indeed a method. Let's say we have an OrganizationsController
that is exposing the #index
action in a restful endpoint. I will add a breakpoint using the pry gem so that we can better understand what params
is:
class OrganizationsController < ApplicationController
def index
binding.pry # Runtime will stop here
render json: Organization.all
end
end
And let's visit the following URL:
http://localhost:3000/organizations.json?foo=bar
We can actually verify that params
is a method by explicitly calling it with ()
:
> params()
=> #<ActionController::Parameters {"foo"=>"bar", "controller"=>"organizations", "action"=>"index", "format"=>"json"} permitted: false>
or by actually asking Ruby where that method is defined:
> 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]
The object returned by calling params
is not a Hash
, but an ActionController::Parameters
instead:
> params.class
=> ActionController::Parameters
However, we can call the method :[]
on it because it is actually defined in the ActionController::Parameters
class (see code)
This makes it look like it's actually a Hash
, but it is not, actually. For example, we cannot call the Hash
method invert on params
, as it is not defined:
> params.invert
NoMethodError: undefined method `invert' for #<ActionController::Parameters {"foo"=>"bar", "controller"=>"organizations", "action"=>"index", "format"=>"json"} permitted: false>