在纯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
代替。它将正确读取输入
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
同样,这是非常简化的,只是在实际代码的精神,但这基本上是它的工作方式。