Home > Enterprise >  Rails params method: Why can it be accessed like a hash?
Rails params method: Why can it be accessed like a hash?

Time:10-10

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>

  • Related