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>
