编辑|或者另一个问题,在同一个对象主题上。我可以编写自己的类定义,这会导致以下全部工作吗?
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)获得多少乐趣......
当用户未登录时,我希望能够使用unless @user
或@user.nil?
等来检测它,但与此同时,我希望能够在这个对象上调用User
的方法并让它们返回相同的NilClass
(ala Objective-C),因为在大多数地方这样就不需要样板if @user
代码了,在布尔表达式中表现为nil
。
我发现你不能继承NilClass
,因为它没有#new
方法。您可以直接向实例添加方法,但它是一个单例,因此如果我想要观察此行为的唯一地方是未登录用户,则会导致问题。
有可能吗?
我的意思是,是这样的:
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
这基本上是Objective-C处理nil的方式,在某些情况下它很有用。
我想我不能继承NilClass,只是实现会导致它在布尔表达式中为假的方法?
编辑|哈哈,如果重新定义顶级NilClass
,你真的会非常破坏Ruby ......它会停止响应任何方法调用。
>> class NilClass
>> def method_missing(meth, *args, &block)
>> self
>> end
>> end
>>
?> nil.foo
>>
?>
?> puts "here"
>> quit
>> # WTF? Close dammit!
答案 0 :(得分:3)
如果您的主要要求是能够在nil
值上调用方法而不引发异常(或者必须检查它是否已定义),ActiveSupport
会添加Object#try
能够做到这一点:
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
进行操作会产生一些有趣的副作用。
答案 1 :(得分:2)
通常,建议不要以这种方式更改nil
对象 - fail-fast。我建议使用egonil或andand gem。有关此主题的更多阅读/链接[和乐趣;)],请结帐this blog post。
答案 2 :(得分:2)
另一种可能的解决方案(非线程安全):
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
答案 3 :(得分:0)
我今天正在寻找一个非常类似的事情,普遍的共识是,在不破坏一切的情况下将“零”变为“无效”是“不可能的”。所以,这就是你不可能做到的事情:
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上这样做了)
这会添加“null!”一般来说,nil和所有对象的方法。对于普通对象,它是一个无操作,并且正常工作(包括在缺少方法时抛出异常)。但是,对于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”一样。一旦它看到解释器已经移动,它就会将自身重置为正常行为。
这种方法的警告是:
您的陈述必须全部符合一行。
你的陈述可能必须有一个实际的文件和行号(我没有在IRB中测试它,但我怀疑它不会工作)..没有这些它不能告诉它它仍在执行同一行,所以它可能会立即恢复正常。
新行为将继续影响其他nils,直到行尾。你可以通过调用“nil”强制它再次正常行动。但是后来就行了。例如:
obj.null!.foo.bar.nil! || normal.nil.behavior
成功方法调用的任何内容都在自己的实体中执行,这些实体会针对nil调用非existant方法或调用“null!”可能会重置行为,因为那些行为发生在其他行号,但理论上,这不应该是一个问题,除非你做的很奇怪。
如果你喜欢这种“有趣”的东西,我在gemcutter上有一个名为“mobj”的宝石,带有一堆这种古怪的hackery,或从这里拉出来源:
https://github.com/gnovos/mobj
我有另一个可以以不同方式执行此操作的gem,如果您需要更强大的解决方案(并且不介意将内容包装在块中):
require "ctx"
class NilClass
ctx :null do
def missing_method(*) self end
end
end
然后,在任何你想要这种行为的地方;
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:
干杯!