Хеши в качестве аргументов и значения аргументов по умолчанию - PullRequest
0 голосов
/ 29 мая 2018

Я изучаю некоторые из внутренних компонентов Rails, особенно метод has_many.Здесь показаны следующие примеры:

  # Option examples:
  #   has_many :comments, -> { order "posted_on" }
  #   has_many :comments, -> { includes :author }
  #   has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
  #   has_many :tracks, -> { order "position" }, dependent: :destroy
  #   has_many :comments, dependent: :nullify
  #   has_many :tags, as: :taggable
  #   has_many :reports, -> { readonly }
  #   has_many :subscribers, through: :subscriptions, source: :user
  def has_many(name, scope = nil, options = {}, &extension)
    reflection = Builder::HasMany.build(self, name, scope, options, &extension)
    Reflection.add_reflection self, name, reflection
  end

Рассмотрим, в частности, этот пример:

has_many :subscribers, through: :subscriptions, source: :user

Второй аргумент - хеш.И я заметил, что это присваивается локальной переменной options.Но почему он не был назначен переменной области, поскольку переменная области является вторым аргументом в списке аргументов?Почему при назначении была пропущена переменная области видимости?Нет, он устанавливает значение по умолчанию nil, если второй аргумент не передан, но фактически второй аргумент был передан.

Ответы [ 2 ]

0 голосов
/ 29 мая 2018

Во-первых, определение has_many изменилось между Rails 4 и Rails 5. Начнем с Rails 4.

Rails 4

Вы правы, что здесь происходит что-то подозрительное.Rails обманывает.Я собираюсь догадаться о обратной совместимости со старыми версиями Ruby, которые не имели такой богатый синтаксис аргументов, как сейчас.

name, scope и options - это позиционные аргументы.Мы можем определить has_many, чтобы увидеть, что происходит.

def has_many(name, scope = nil, options = {}, &extension)
  puts "name: #{name}"
  puts "scope: #{scope}"
  puts "options: #{options}"
  puts "extension: #{extension}"
end

Если мы запустим has_many :subscribers, through: :subscriptions, source: :user ...

name: subscribers
scope: {:through=>:subscriptions, :source=>:user}
options: {}
extension: 

Ну, это не так. Давайте посмотрим на его источник ...

def has_many(name, scope = nil, options = {}, &extension)
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
  Reflection.add_reflection self, name, reflection
end

Аргументы передаются в Builder::HasMany.build, который вызывает create_builder model, name, scope, options, &block.Это вызывает new(model, name, scope, options, &block), чтобы создать новый экземпляр. В его инициализаторе мы находим это ...

def initialize(model, name, scope, options)
  # TODO: Move this to create_builder as soon we drop support to activerecord-deprecated_finders.
  if scope.is_a?(Hash)
    options = scope
    scope   = nil
  end
  ...

Так что это просто обман.Если scope является хешем, он переключается на options.Это довольно неприятный хак, видя, как неправильные аргументы проходят через несколько уровней вызовов методов, прежде чем они исправлены.

Rails 5

Rails 5 изменил сигнатуру has_many.

def has_many(name, scope = nil, **options, &extension)
  puts "name: #{name}"
  puts "scope: #{scope}"
  puts "options: #{options}"
  puts "extension: #{extension}"
end

has_many :subscribers, through: :subscriptions, source: :user

И теперь все работает как положено.

name: subscribers
scope: 
options: {:through=>:subscriptions, :source=>:user}
extension: 

** преобразует аргументы ключевых слов в хеш , и это заставляет его работать.

Когда вы звоните has_many :subscribers, through: :subscriptions, source: :user, вот что происходит.

  1. Руби нужен первый позиционный name, поэтому он :subscribers.
  2. Ruby требуется второй позиционный scope, но он исчерпан позиционным, остальные аргументы являются ключевыми словами, поэтому он использует значение по умолчанию nil.
  3. Ruby sluПоднимает ключевые слова и помещает их в хэш options.
  4. extension является аргументом блока, и это хорошо, если они не включены.

Вы можете прочитать больше окак происходит обработка аргументов в Ruby в документе Calling Methods .

0 голосов
/ 29 мая 2018

Хэш был назначен параметру scope, поскольку он был передан в качестве второго параметра, таким образом, оставляя options, получить значение по умолчанию {}.Но ActiveRecord меняет параметры, если вы передаете хеш в качестве второго значения.Это обрабатывается в Builder::Association (который используется, потому что Builder::HasMany наследуется от Builder:: CollectionAssociation, который, в свою очередь, наследуется от Builder::Association).Метод класса build вызывает create_builder, который затем передает аргументы на initialize, который проверяет

if scope.is_a?(Hash)
  options = scope
  scope   = nil
end

Так что нетвстроенная рубиновая закулисная магия, ActiveRecord просто решила сделать проверку, чтобы проверить, передали ли вы Hash в качестве второго параметра и «исправить» его для вас, вместо того, чтобы заставлять вас передавать nil.


CollectionAssociation переопределяет метод initialize, но вы можете видеть, что они вызывают super в начале метода, убедившись, что вызывается Association#initializeпервый и происходит этот обмен парами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...