为什么Rails可以使用' if '作为散列键,而Ruby不能



在纯Ruby irb中,不能输入{if: 1}。语句不会终止,因为irb认为if不是一个符号,而是if语句的开头。

那么为什么Rails可以有before_filter接受如果作为参数?指南有如下代码:

class Order < ApplicationRecord
  before_save :normalize_card_number, if: :paid_with_card?
end

同样的事情也发生在unless

这是irb的问题,不是Ruby的问题。

bash=> ruby -e "puts({if: 1})"
bash=# {:if=>1}

您可以使用pry代替。它将正确读取输入

https://github.com/pry/pry

IRb的解析器众所周知是坏的。(事实上,你遇到的那个bug在几个月前就已经报告了:bug #12177: 在irb控制台使用if:作为哈希中的符号与新的哈希语法不工作。)忽略它。IRb和Ruby在行为上还有其他的区别,语义上的,而不仅仅是语法上的。例如,在顶层定义的方法是隐式的public,而不是隐式的private

IRb试图用自己的解析器解析代码,以确定是否在您点击ENTER时将其提交给引擎,或者在下一行等待您继续代码。然而,由于Ruby的语法极其复杂,要正确解析它是非常困难的,而且IRb的解析器与Ruby的解析器是有偏差的。

其他repl采用不同的方法,例如,Pry实际上使用Ruby的解析器而不是自己的解析器。

示例中的代码是Rails DSL的一部分。你实际上在这里设置的是一个哈希值,它恰好看起来有点像代码。

在内部,Rails将计算这个哈希值,并为before_save调用指定条件。

在一个非常简化的版本中,Rails基本上在保存时这样做:

class ActiveRecord::Base
  @before_save_rules = []
  def self.before_save(method, options={})
    @before_save_rules << [method, options]
  end
  def self.before_save_rules
    @before_save_rules
  end
  def save
    # Evaluate the defined rules and decide if we should perform the
    # before_save action or not
    self.class.before_safe_rules.each do |method, options|
      do_perform = true
      if options.key?(:if)
        do_perform = false unless send(options[:if])
      end
      if options.key?(:unless)
        do_perform = false if send(options[:unless])
      end
      send(method) if do_perform
    end
    # now perform the actual save to the database
    # ...
  end
end

同样,这是非常简化的,只是在实际代码的精神,但这基本上是它的工作方式。

最新更新