Ruby 只是为了好玩-我可以为特定用途编写自定义类吗?

Ruby 只是为了好玩-我可以为特定用途编写自定义类吗?,ruby,null,null-object-pattern,Ruby,Null,Null Object Pattern,在同一对象主题上编辑|或其他问题。我是否可以编写自己的类定义,使以下所有内容都能正常工作 o = WeirdObject.new puts "Object o evaluates as true in boolean expressions" if o # Line never output puts "Object o is nil?" if o.nil? # Line is output 我可以轻松地做nil?这件事,因为这只是一种方法。但不知道如何使它在布尔表达式(包括最基本的表达式“o

在同一对象主题上编辑|或其他问题。我是否可以编写自己的类定义,使以下所有内容都能正常工作

o = WeirdObject.new
puts "Object o evaluates as true in boolean expressions" if o # Line never output
puts "Object o is nil?" if o.nil? # Line is output
我可以轻松地做
nil?
这件事,因为这只是一种方法。但不知道如何使它在布尔表达式(包括最基本的表达式“o”)中作为非真值进行计算

原来的问题如下

更多的是出于好奇,关于Ruby(1.9.2)到底能给我带来多少乐趣

当用户未登录时,我希望能够使用
除非@user
或除非
@user.nil?
等来检测它,但同时,我希望能够在此对象上调用
user
的方法,并让它们返回相同的
NilClass
(ala Objective-C),因为在大多数情况下,如果@usercode,则无需使用样板文件
if@user,而在布尔表达式中仍然像
nil

我发现您不能将
NilClass
子类化,因为它没有
#新的
方法。您可以直接将方法添加到实例中,但它是一个单例,因此如果我只想观察未登录用户的行为,那么这将导致问题

可能吗

我的意思是这样的:

class CallableNil < NilClass
  def method_missing(meth, *args, &block)
    self
  end
end

user = CallableNil.new # or .singleton, or something

puts "Got here" unless user # Outputs
puts "And here" unless user.nil? # also outputs

puts user.username # no-op, returns nil

通常,不建议以这种方式更改
nil
对象--。我建议使用或宝石。有关此主题的更多阅读/链接[为了好玩;)],请签出。

如果您的主要要求是能够调用
nil
值上的方法而不引发异常(或必须检查是否定义了异常),
ActiveSupport
添加了以下功能:

instance = Class.method_that_returns_nil

unless instance
  puts "Yes, we have no instance."
end

instance.try(:arbitrary_method)             # => nil
instance.try(:arbitrary_method, "argument") # => nil

正如你所看到的,使用NilClass有一些有趣的副作用。

我今天正在寻找一个非常类似的东西,普遍的共识是,在不破坏一切的情况下,将“nil”变为“null”是“不可能的”。所以,这就是你做不可能的事情的方式:

class ::BasicObject
  def null!() self end
  def nil!() self end
end

class ::NilClass
  NULL_BEGIN = __LINE__
  def __mobj__caller()
    caller.find do |frame|
      (file, line) = frame.split(":")
      file != __FILE__ || !(NULL_BEGIN..NULL_END).cover?(line.to_i)
    end
  end
  def null?()
    @@null ||= nil
    @@null && @@null == __mobj__caller
  end
  def null!()
    @@null = __mobj__caller
    self
  end
  def nil!
    @@null = nil
    self
  end
  def method_missing(name, *args, &block)
    if null?
      self
    else
      nil!
      self
    end
  end
  NULL_END = __LINE__
end
(如果格式有问题,请原谅,我是在iPhone上做的)

这通常会向nil和all对象添加一个“null!”方法。对于普通对象,这是一个no-op,一切正常(包括在缺少方法时抛出异常)。然而,对于nil,无论链中有多少个方法,它都不会抛出异常,并且它的计算结果仍然与nil完全相同(因为它仍然只是一个正常的nil)。新行为的范围仅限于调用“null!”的单行。之后,nil返回到它的正常行为

if obj.null!.foo.bar.baz
    puts "normal execution if this is valid for obj"
end

obj = nil

if obj.null!.foo.bar.baz
    puts "this will not print, and no exception"
end

if obj.foo.bar.baz
    puts "this will throw an exception as usual"
end
它通过遍历调用树并标记不是它自己代码的第一个文件/行来实现这一点,并使用它作为标志来指示它现在处于“null”模式。只要文件/行不更改,它将继续充当“null”。一旦它看到解释器已经移动,它就会将自身重置为正常行为

if obj.null!.foo.bar.baz
    puts "normal execution if this is valid for obj"
end

obj = nil

if obj.null!.foo.bar.baz
    puts "this will not print, and no exception"
end

if obj.foo.bar.baz
    puts "this will throw an exception as usual"
end
这种方法的注意事项包括:

  • 你的陈述必须全部放在一行上

  • 您的语句可能必须有一个实际的文件和行号(我还没有在IRB中测试它,但我怀疑它不会工作)。。没有这些,它无法判断它仍然在同一行上执行,因此它可能会立即恢复正常

  • 新行为将继续影响其他NIL,直到行尾。不过,您可以在稍后的命令行中调用“nil!”来强制它再次正常工作。例如:

    obj.null!。foo.bar.nil!||正常行为

  • 成功的方法调用在它们自己的主体中所做的任何事情,比如对nil调用不存在的方法或者调用“null!”都可能会重置行为,因为这些行为发生在其他行号上,但是,理论上,这不应该是个问题,除非您正在做非常奇怪的事情

如果你喜欢这种“有趣”的东西,我在gemcutter上有一个名为“mobj”的宝石,上面有一堆古怪的黑客,或者从这里获取来源:

我有另一个gem可以以不同的方式实现这一点,如果您想要一个更健壮的解决方案(并且不介意将东西包装成块):

然后,在任何你想要这种行为的地方

obj = nil

...

ctx :null do
    if obj.foo.bar
        puts "inside ctx block, acts like 'null!' from above, but extends to everything up and down the stack that inside of the ctx scope"
    end
end

if obj.foo.bar
    puts "outside ctx block, should be back to normal again"
end
我还没有测试最后一个,但它可能会工作。如果您对此感兴趣,您可以在此处找到ctx:


干杯

另一种可能的解决方案(非线程安全):


我没看过《代码》#试试看
,谢谢。不完全相同,但它的存在仍然很有趣。您也可以执行
实例。任意_方法(“参数”)rescue nil
,工作方式类似(在基本Ruby中)
obj = nil

...

ctx :null do
    if obj.foo.bar
        puts "inside ctx block, acts like 'null!' from above, but extends to everything up and down the stack that inside of the ctx scope"
    end
end

if obj.foo.bar
    puts "outside ctx block, should be back to normal again"
end
def null!
  @@null_line = caller.first
end

class NilClass
  def method_missing(name, *args, &block)
    if caller.first == @@null_line
      nil
    else
      super
    end
  end
end


null!; nil.a.b.c.d.e # returns nil
nil.a # throws an error