重构eval(some_variable).is_a?(Proc)以不使用eval

我有一些看起来像旧的代码:

some_variable = "-> (params) { Company.search_by_params(params) }"
if eval(some_variable).is_a?(Proc)
...

Rubocop is complaining about the use of eval. Any ideas on how to remove the usage of eval?

I don't really understand Procs so any guidance on that would be appreciated.

评论
  • 手拿香烟
    手拿香烟 回复

    I am going to answer the question "If I want to run code at a later time, What is the difference between using a proc and a eval'd string?" (which I think is part of your question and confusion):

    What eval does is take a string and parses it to code, and then runs it. This string can come from anywhere, including user input. But eval is very unsafe and problematic, especially when used with raw user input.

    The problems with eval are usually:

    • 几乎总有一种更好的方法
    • 非常危险和不安全
    • 使调试困难

    Using eval allows full control of the ruby process, and if you have high permissions given to the ruby process, potentially even root acmes to the machine. So the general recommendation is use 'eval' only if you absolutely have no other options, and especially not with user input.

    Procs/lambdas/blocks also let you save code for later, (and solve most of the problems with eval, they are the "better way") but instead of storing arbitrary code as a string to read later, they are code already, already parsed and ready to go. In someways, they are methods you can pass around later. Making a proc/lambda gives you an object with a #call method. Then when you later want to run the proc/block/lambda, you call call([arguments...]). What you can't do with procs though is let users write arbitrary code (and generally that's good). You have to write the code for the proc in a file ruby loads (most of the time). Eval does get around that, but you really should rethink if you really want that to be possible.

    Your code sample oddly combines both these methods: it evaluates a string to a lambda. So what's happening here is eval is running the code in the string right away and returning the (last) result, which in this case happens to be a lambda/proc. (Note that this would happen every time you ran eval, which would result in multiple copies of the proc, with different identities, but the same behavior). Since the code inside the string happens to make a lambda, the value returned is a Proc which can later be #call'd. So when eval is run, the code is parsed, and a new lambda is created, with the code in the lambda stored to be run at a later time. If the code inside the string did not create a lambda, the all that code would be run immediately when eval was called with the string.

    可能需要这种行为,但是可能有更好的方法来执行此操作,这绝对是步枪:如果您不太谨慎,则至少有六种微妙的方法可以执行此代码的意外操作。

  • oad
    oad 回复

    Simple. Don't define your variable object as a string but as a lambda Proc

    my_lamda = -> (params) { Company.search_by_params(params) }
    if my_lambda.is_a?(Proc)
      #do stuff
    end
    

    But why would you instantiate a string object which contains what appears to be a normal lambda which is a Proc, when you can define a Proc instead?