【问题标题】:Is there a way to access method arguments in Ruby?有没有办法在 Ruby 中访问方法参数?
【发布时间】:2012-03-01 23:30:54
【问题描述】:

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

所以我正在寻找将所有参数传递给方法的方法,而不列出每个参数。由于这是 Ruby,我认为有一种方法 :) 如果是 java 我会列出它们 :)

输出将是:

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

【问题讨论】:

  • Reha kralj svegami!
  • 我认为所有答案都应该指出,如果“某些代码”在运行参数发现方法之前更改了参数的值,它将显示新值,而不是传递的值因此,您应该立即抓住它们以确保安全。也就是说,我最喜欢的单线(归功于之前的答案)是:method(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }

标签: ruby-on-rails ruby


【解决方案1】:

一种处理方法是:

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

【讨论】:

  • 工作,除非有更好的答案,否则将被投票为接受,我唯一的问题是我不想丢失方法签名,有些会有 Inteli 感觉,我不想失去它。
【解决方案2】:

这是一个有趣的问题。也许使用local_variables?但是除了使用eval之外,必须有其他方法。我在看内核doc

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

【讨论】:

  • 这还不错,为什么是这个邪恶的解决方案?
  • 在这种情况下还不错 - 使用 eval() 有时会导致安全漏洞。只是我认为可能有更好的方法:) 但我承认在这种情况下谷歌不是我们的朋友
  • 我打算这样做,缺点是你不能制作可以解决这个问题的助手(模块),因为一旦它离开原始方法,它就不能对本地变量进行评估。谢谢大家的信息。
  • 这给了我“TypeError: cannot convert Symbol to String”,除非我把它改成eval var.to_s。此外,需要注意的是,如果您在运行此循环之前定义任何局部变量,它们将被包含在方法参数之外。
  • 这不是最优雅和最安全的方法——如果你在方法中定义局部变量然后调用local_variables,它将返回方法参数+所有局部变量。这可能会导致您的代码出错。
【解决方案3】:

在我更进一步之前,您向 foo 传递了太多参数。看起来所有这些参数都是模型的属性,对吗?你真的应该传递对象本身。演讲结束。

您可以使用“splat”参数。它把所有东西都塞进一个数组中。它看起来像:

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

【讨论】:

  • 不同意这一点,方法签名对于代码的可读性和可重用性很重要。对象本身很好,但你必须在某处创建实例,所以在你调用方法之前或在方法中。我认为方法更好。例如创建用户方法。
  • 引用一个比我更聪明的人 Bob Martin 在他的书 Clean Code 中的话,“一个函数的理想参数数量是零(niladic)。接下来是一个(monoadic),紧随其后由两个(二元)。三个参数(三元)应尽可能避免。超过三个(多元)需要非常特殊的理由 - 然后无论如何都不应该使用。他继续说我所说的,许多相关的参数应该包装在一个类中并作为一个对象传递。这是一本好书,我强烈推荐。
  • 不要过分强调这一点,但请考虑一下:如果您发现需要更多/更少/不同的参数,那么您将破坏您的 API,并且必须更新每个调用方法。另一方面,如果您传递一个对象,那么您的应用程序的其他部分(或您的服务的消费者)可以愉快地进行。
  • 我同意你的观点,例如在 Java 中,我将始终执行您的方法。但我认为 ROR 是不同的,原因如下:
  • 我同意你的观点,例如在 Java 中,我将始终执行您的方法。但我认为 ROR 是不同的,这就是为什么:如果你想将 ActiveRecord 保存到 DB 并且你有保存它的方法,你必须在我将它传递给 save 方法之前组装散列。对于用户示例,我们设置了名字、姓氏、用户名等,然后将哈希传递给保存方法,该方法将执行某些操作并保存它。这里的问题是每个开发人员如何知道在哈希中放入什么?它是活动记录,因此您必须查看数据库架构而不是组装哈希,并且要非常小心不要错过任何符号。
【解决方案4】:

如果你想改变方法签名,你可以这样做:

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

在这种情况下,插入的argsopts.values 将是一个数组,但如果使用逗号,您可以使用join。干杯

【讨论】:

    【解决方案5】:

    在 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 }

    【讨论】:

    • 我需要一些时间来破译这个:)
    • 让我知道哪些位需要解密,我会添加一些解释:)
    • +1 这真的很棒而且很优雅;绝对是最好的答案。
    • 我尝试使用 Ruby 1.9.3,你必须执行 #{eval arg.to_s} 才能让它工作,否则你会得到一个 TypeError: can't convert Symbol into String
    • 同时,我的技能越来越好,现在理解这段代码了。
    【解决方案6】:

    这个问题试图完成的似乎可以用我刚刚发布的 gem 来完成,https://github.com/ericbeland/exception_details。它将从已获救的异常中列出局部变量和变量(以及实例变量)。可能值得一看...

    【讨论】:

    • 这是一个不错的 gem,对于 Rails 用户我也推荐better_errors gem。
    【解决方案7】:

    您可以定义一个常量,例如:

    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)
    

    【讨论】:

      【解决方案8】:

      从 Ruby 2.1 开始,您可以使用binding.local_variable_get 读取任何局部变量的值,包括方法参数(参数)。多亏了这一点,您可以改进 accepted answer 以避免 evil 评估。

      def foo(x, y)
        method(__method__).parameters.map do |_, name|
          binding.local_variable_get(name)
        end
      end
      
      foo(1, 2)  # => 1, 2
      

      【讨论】:

      • @uchuugaka 是的,这个方法在中不可用
      • 谢谢。这使得这很好: logger.info method__+ " #{args.inspect}" method(_method).parameters.map do |, name| logger.info "#{name} ="+binding.local_variable_get(name) end
      • 这是要走的路。
      • 也可能有用 - 将参数转换为命名哈希:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
      【解决方案9】:

      这可能会有所帮助...

        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
      

      【讨论】:

      • 除了callers_name 的这种略显老套的实现,您还可以将__method__binding 一起传递。
      【解决方案10】:

      如果您需要作为 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
      

      【讨论】:

        【解决方案11】:

        如果函数在某个类中,那么你可以这样做:

        class Car
          def drive(speed)
          end
        end
        
        car = Car.new
        method = car.method(:drive)
        
        p method.parameters #=> [[:req, :speed]] 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-11-24
          • 2015-10-22
          • 2022-11-03
          • 1970-01-01
          • 2014-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多