Способ определения параметров рельсов: Почему к нему можно получить доступ как к хэшу?

#ruby-on-rails #ruby

Вопрос:

Просмотр этого кода:

params[:id]

Парам считается методом. Поправьте меня, если я ошибаюсь. Но это все равно что читать по хэшу. Итак, в настоящее время я в замешательстве.

Если параметры-это метод: как работает приведенный пример кода?

Ответ №1:

Вы правы, что params это метод, но здесь params метод возвращает экземпляр ActionController::Parameters , и мы вызываем метод доступа к хэшу #[] для него.

Это распространенный шаблон в ruby для вызова методов возвращаемого объекта. Давайте посмотрим на это на простом примере:

 def params
  {
    id: 101,
    key: 'value',
    foo: 'bar'
  }
end

params[:id] # => 101
params[:foo] # => 'bar'
 

Как вы можете видеть в примере, метод params возвращает хэш-объект, и мы вызываем метод доступа к хэшу #[] для возвращенного объекта.

Ссылка на params метод рельсов: 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] .

Комментарии:

1. Отличный ответ, может быть, будет полезно добавить, что он эквивалентен params()[:id]

Ответ №2:

Они известны как средства доступа в квадратных скобках, и вы можете добавить их к любому объекту, реализовав методы [] и. []=

 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::Параметры, как в этом упрощенном примере:

 def foo
  Store.new(bar: 1)
end

foo[:bar] # 1
 

Поскольку родители являются необязательными, это равносильно вызову params()[:id] .

Ответ №3:

В контексте контроллера params это действительно метод. Допустим, у нас есть OrganizationsController объект, который раскрывает #index действие в спокойной конечной точке. Я добавлю точку останова, используя драгоценный камень pry, чтобы мы могли лучше понять, что params такое:

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

 

И давайте перейдем по следующему адресу:

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 , является не a Hash , а ActionController::Parameters вместо этого:

 > params.class
=> ActionController::Parameters
 

Однако мы можем вызвать метод :[] на нем, потому что он фактически определен в ActionController::Parameters классе (см. Код).

Это заставляет его выглядеть так , как будто это на самом деле а Hash , но на самом деле это не так. Например, мы не можем вызвать Hash метод invert on params , так как он не определен:

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