我有一个模型,它有几个属性,在保存模型时是可选的。
我有几个实例方法使用这些属性并执行计算,但我想首先检查它们是否为零,因为我会得到可怕的no method error nil nil class
除了用.present?
乱丢我的代码外还有更好的方法吗?
修改 这是我到目前为止的代码
def is_valid?
(has_expired? === false and has_uses_remaining?) ? true : false
end
def has_expired?
expiry_date.present? ? expiry_date.past? : false
end
def remaining_uses
if number_of_uses.present?
number_of_uses - uses_count
end
end
def has_uses_remaining?
number_of_uses.present? ? (remaining_uses > 0) : true
end
我觉得放入.present?
来执行检查有一个糟糕的代码气味,我已经研究了空对象模式,但它似乎没有意义,因为对象存在,但有些属性是{ {1}}
答案 0 :(得分:1)
在这些情况下,短路通常效果最佳。
在:
if @attribute.present?
@attribute.do_something
end
的短路:
@attribute && @attribute.do_something
通过短路方法,只要Ruby看到&&
运算符的左侧是nil
,它就会停止而不会运行右侧
我也会想到为什么特定属性应该被允许nil
(如乔丹所说)。如果你能想出一种避免这种情况的方法,那可能会更好。
假设您确实希望number_of_users
能够成为nil
,您可以像这样重写has_uses_remaining?
:
def has_uses_remaining?
!number_of_uses || remaining_uses > 0
end
- 注意:您的第一种方法可以简化为:
def is_valid?
!has_expired? && has_uses_remaining?
end
答案 1 :(得分:1)
我认为真正的问题是number_of_uses
可以是nil
,(正如您已经发现的那样)引入了大量的复杂性。尽量先消除这个问题。
如果由于某种原因你不能这样做,你的每一种方法都可以改进:
condition ? true : false
总是代码气味。布尔运算符返回布尔值(ish),所以让他们完成工作:
def is_valid?
!has_expired? && has_uses_remaining?
end
我个人认为使用Rails' Object#try
通常是代码味道,但在这里它非常合适:
def has_expired?
expiry_date.try(:past?)
end
可替换地:
def has_expired?
expiry_date.present? && expiry_date.past?
end
这个版本无法改进很多,但我个人更喜欢早期return
到if
块中包含的方法:
def remaining_uses
return if number_of_uses.nil?
number_of_uses - uses_count
end
你也可以number_of_uses && number_of_uses - uses_count
(甚至number_of_uses.try(:-, uses_count)
,但我认为这更清楚。
如果true
为number_of_uses
位,此方法会返回nil
,这有点奇怪,因为我们可以这样简化它:
def has_uses_remaining?
remaining_uses.nil? || remaining_uses > 0
end
请注意,我致电remaining_uses.nil?
而不是number_of_uses.nil?
;当我们从一个人那里得到相同的结果时,没有必要依赖两者。
经过进一步考虑后,我认为您可以通过引入另一种方法使此代码的目的更清晰:has_unlimited_uses?
:
def has_unlimited_uses?
number_of_uses.nil?
end
def is_valid?
!has_expired? &&
has_unlimited_uses? || has_uses_remaining?
end
def remaining_uses
return if has_unlimited_uses?
number_of_uses - uses_count
end
def has_uses_remaining?
has_unlimited_uses? || remaining_uses > 0
end
这样一来,你所检查的内容就不会有任何含糊之处。这将使下一个阅读它的人(或者从现在起六个月后)的代码更具可读性,并且可以更轻松地追踪错误。
但是,remaining_uses
返回nil
仍困扰着我。事实证明,如果我们返回Float::INFINITY
,has_uses_remaining?
会变成简单的比较:
def remaining_uses
return Float::INFINITY if has_unlimited_uses?
number_of_uses - uses_count
end
def has_uses_remaining?
remaining_uses > 0
end