Чи є спосіб отримати доступ до аргументів методу в Ruby?


102

Новачок у Ruby та ROR і люблю це кожен день, тому ось моє питання, оскільки я не маю ідеї, як його гугл (і я спробував :))

у нас є метод

def foo(first_name, last_name, age, sex, is_plumber)
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{SOMETHING}"    
end

Тому я шукаю спосіб передати всі аргументи методу, не перераховуючи кожного. Оскільки це Рубі, я припускаю, що існує спосіб, якби ява, я б просто перерахував їх :)

Вихід буде:

Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}

1
Reha kralj svegami!
мураха

1
Я думаю, що всі відповіді повинні вказувати на те, що якщо "якийсь код" змінить значення аргументів до запуску методу виявлення аргументів, він покаже нові значення, а не значення, які були передані. Отже, ви повинні захопити їх правильно далеко, щоб бути впевненим. Це означає, що мій улюблений method(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }
одноплановий

Відповіді:


159

У Ruby 1.9.2 та пізніших версіях ви можете використовувати parametersметод на методі, щоб отримати список параметрів цього методу. Це поверне список пар із зазначенням імені параметра та того, чи потрібен він.

напр

Якщо ти зробиш

def foo(x, y)
end

тоді

method(:foo).parameters # => [[:req, :x], [:req, :y]]

Ви можете використовувати спеціальну змінну, __method__щоб отримати ім'я поточного методу. Тож у межах методу імена його параметрів можна отримати за допомогою

args = method(__method__).parameters.map { |arg| arg[1].to_s }

Потім ви можете відобразити ім'я та значення кожного параметра за допомогою

logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')

Примітка: оскільки ця відповідь спочатку була написана, у поточних версіях Ruby evalвже не можна називати символом. Для вирішення цього питання to_sбуло додано явне під час створення списку імен параметрів, тобтоparameters.map { |arg| arg[1].to_s }


4
Мені знадобиться деякий час, щоб розшифрувати це :)
Харіс Крайна

3
Дайте мені знати, які біти потрібно розшифрувати, і я додам трохи пояснень :)
mikej

3
+1 це дійсно дивовижно та елегантно; безумовно, найкраща відповідь.
Ендрю Маршалл

5
Я спробував з Ruby 1.9.3, і вам потрібно зробити # {eval arg.to_s}, щоб він працював, інакше ви отримаєте TypeError: не в змозі перетворити символ на String
Javid Jamae

5
Тим часом покращився і мої навички, і зараз розумію цей код.
Харіс Крайна

55

Починаючи з Ruby 2.1, ви можете використовувати obvez.local_variable_get для зчитування значення будь-якої локальної змінної, включаючи параметри методу (аргументи). Завдяки цьому ви можете покращити прийняту відповідь, щоб цього не булозло eval.

def foo(x, y)
  method(__method__).parameters.map do |_, name|
    binding.local_variable_get(name)
  end
end

foo(1, 2)  # => 1, 2

2.1 найраніший?
uchuugaka

@uchuugaka Так, цей метод недоступний у <2.1.
Якуб Жирутка

Дякую. Це робить це приємним: метод logger.info __ + "# {args.inspect}" ( _method ) .parameters.map do | , назва | logger.info "# {name} =" + obvez.local_variable_get (ім'я) кінець
Мартін Клівер

Це шлях.
Арді Арам

1
Також потенційно корисно - перетворення аргументів на названий хеш:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba

19

Один із способів вирішити це:

def foo(*args)
    first_name, last_name, age, sex, is_plumber = *args
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{args.inspect}"    
end

2
Працюючи і буде проголосовано як прийняте, якщо немає кращих відповідей, моя єдина проблема з цим - я не хочу втрачати підпис методу, дехто з них буде сенсом Inteli, і мені б не хотілося його втрачати.
Харіс Крайна

7

Це цікаве питання. Можливо, використовуєте local_variables ? Але повинен бути інший спосіб, крім використання eval. Я дивлюсь у Кернел док

class Test
  def method(first, last)
    local_variables.each do |var|
      puts eval var.to_s
    end
  end
end

Test.new().method("aaa", 1) # outputs "aaa", 1

Це не так вже й погано, чому це зле рішення?
Haris Krajina

Непогано в цьому випадку - використання eval () іноді може призвести до зазорів у безпеці. Просто я думаю, що може бути і кращий спосіб :), але я визнаю, Google не є нашим другом у цій справі
Раффаеле,

Я збираюся піти з цим, недоліком є ​​те, що ви не можете зробити помічника (модуля), який би про це піклувався, оскільки як тільки він покине оригінальний метод, він не може зробити оцінку місцевих вар. Дякую всім за інформацію.
Харіс Крайна

Це дає мені "TypeError: не вдається перетворити Symbol в String", якщо я не зміню його на eval var.to_s. Також застереження до цього є те, що якщо ви визначите будь-які локальні змінні перед запуском цього циклу, вони будуть включені на додаток до параметрів методу.
Ендрю Маршалл

6
Це не найвишуканіший і безпечний підхід - якщо ви визначите локальну змінну всередині свого методу, а потім зателефонуєте local_variables, вона поверне аргументи методу + всі локальні змінні. Це може спричинити помилки, коли ваш код.
Аліаксей Ключнікау

5

Це може бути корисно ...

  def foo(x, y)
    args(binding)
  end

  def args(callers_binding)
    callers_name = caller[0][/`.*'/][1..-2]
    parameters = method(callers_name).parameters
    parameters.map { |_, arg_name|
      callers_binding.local_variable_get(arg_name)
    }    
  end

1
Замість цієї злегка хитрої callers_nameреалізації ви також можете пройти __method__разом із binding.
Том Лорд

3

Ви можете визначити константу, наприклад:

ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"

І використовуйте його у своєму коді, наприклад:

args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)

2

Перш ніж піти далі, ти передаєш занадто багато аргументів у дурний. Схоже, всі ці аргументи є атрибутами моделі, правда? Ви дійсно повинні передавати сам об’єкт. Кінець виступу.

Ви можете використовувати аргумент "splat". Це зсуває все в масив. Це виглядатиме так:

def foo(*bar)
  ...
  log.error "Error with arguments #{bar.joins(', ')}"
end

Не погоджуючись з цим, підпис методу важливий для читабельності та повторної використання коду. Сам об’єкт прекрасний, але вам потрібно десь створити екземпляр, тому перед тим, як u викликати метод чи метод. На мій погляд, краще в методі. наприклад, створити метод користувача.
Haris Krajina

Якщо цитувати розумнішої людини, ніж я, Боб Мартін, у своїй книзі «Чистий код» ідеальна кількість аргументів для функції - нуль (niladic). Далі йде один (моноадичний), за ним уважно два (діадичний). Три аргументи Потрібно уникати (триадичного), де це можливо. Більше трьох (поліадичних) вимагає дуже спеціального обґрунтування - і тоді його не слід використовувати ». Він продовжує говорити те, що я сказав, багато споріднених аргументів повинні бути загорнуті в клас і передані як об'єкт. Це гарна книга, я дуже рекомендую її.
Том Л

Не ставте на це занадто точну крапку, але врахуйте це: якщо ви виявите, що вам потрібно більше / менше / різних аргументів, ви порушили свій API і доведеться оновлювати кожен виклик цього методу. З іншого боку, якщо ви передаєте об’єкт, інші частини вашої програми (або споживачі вашої послуги) можуть весело поспішати.
Том Л

Я погоджуюся з вашими пунктами, і, наприклад, на Яві я завжди б застосовував ваш підхід. Але я думаю, що з ROR по-іншому, і ось чому:
Харіс Крайна

Я погоджуюся з вашими пунктами, і, наприклад, на Яві я завжди б застосовував ваш підхід. Але я думаю, що з ROR - це інше, і ось чому: Якщо ви хочете зберегти ActiveRecord в БД, і у вас є метод, який його зберігає, вам доведеться зібрати хеш, перш ніж я передам його для збереження методу. Для прикладу користувача ми встановлюємо ім’я, прізвище, ім’я користувача і т.д., а потім передаємо хеш для збереження методу, який би щось зробив і збереже. І ось проблема, як кожен розробник знає, що поставити в хеш? Це активний запис, тому вам доведеться переглядати схему db, ніж збирати хеш, і будьте дуже обережні, щоб не пропустити жодних символів.
Haris Krajina

2

Якщо ви змінили б підпис методу, ви можете зробити щось подібне:

def foo(*args)
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{args}"    
end

Або:

def foo(opts={})
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{opts.values}"    
end

У цьому випадку інтерпольований argsабо opts.valuesбуде масив, але ви можете, joinякщо на коми. Ура


2

Схоже, що це питання намагається досягти з дорогоцінним каменем, який я щойно випустив, https://github.com/ericbeland/exception_details . Він перелічить локальні змінні та vlaues (і змінні екземпляри) від врятованих винятків. Можливо, варто подивитися ...


1
Це хороший дорогоцінний камінь, для користувачів Rails я також рекомендував би better_errorsдорогоцінний камінь.
Харіс Крайна

1

Якщо вам потрібні аргументи як Hash, і ви не хочете забруднювати тіло методу складним вилученням параметрів, використовуйте це:

def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
  args = MethodArguments.(binding) # All arguments are in `args` hash now
  ...
end

Просто додайте цей клас до свого проекту:

class MethodArguments
  def self.call(ext_binding)
    raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
    method_name = ext_binding.eval("__method__")
    ext_binding.receiver.method(method_name).parameters.map do |_, name|
      [name, ext_binding.local_variable_get(name)]
    end.to_h
  end
end

0

Якщо функція знаходиться в якомусь класі, то ви можете зробити щось подібне:

class Car
  def drive(speed)
  end
end

car = Car.new
method = car.method(:drive)

p method.parameters #=> [[:req, :speed]] 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.