Rails 实例方法取决于所设置的属性
Rails instance methods conditional depending on attributes being set
我有一个模型,它有几个属性,在保存模型时这些属性是可选的。
我有几个使用这些属性并执行计算的实例方法,但我想先检查它们是否不为零,因为我会得到可怕的 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?
来执行检查有不好的代码味道,我已经研究了空对象模式,但它在这里似乎没有意义,因为对象存在但一些属性是 nil
Short-circuiting 通常在这些情况下效果最好。
之前:
if @attribute.present?
@attribute.do_something
end
Short-circuiting:
@attribute && @attribute.do_something
使用short-circuiting方法,一旦Ruby看到&&
运算符的左侧是nil
,它就会停止而不是[=38] =]右边
我也会认真思考为什么应该允许某个特定属性 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
我认为这里真正的问题是 number_of_uses
可以是 nil
,这(如您所见)引入了大量的复杂性。先尝试解决这个问题。
如果由于某种原因你不能这样做,你的每个方法都可以改进:
condition ? true : false
总是 代码味道。布尔运算符 return 布尔值,所以让它们完成它们的工作:
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)
但我认为这样更清楚。
这个方法 returns true
if number_of_uses
is 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
returns nil
仍然困扰着我。事实证明,如果我们 return 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
我有一个模型,它有几个属性,在保存模型时这些属性是可选的。
我有几个使用这些属性并执行计算的实例方法,但我想先检查它们是否不为零,因为我会得到可怕的 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?
来执行检查有不好的代码味道,我已经研究了空对象模式,但它在这里似乎没有意义,因为对象存在但一些属性是 nil
Short-circuiting 通常在这些情况下效果最好。
之前:
if @attribute.present?
@attribute.do_something
end
Short-circuiting:
@attribute && @attribute.do_something
使用short-circuiting方法,一旦Ruby看到&&
运算符的左侧是nil
,它就会停止而不是[=38] =]右边
我也会认真思考为什么应该允许某个特定属性 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
我认为这里真正的问题是 number_of_uses
可以是 nil
,这(如您所见)引入了大量的复杂性。先尝试解决这个问题。
如果由于某种原因你不能这样做,你的每个方法都可以改进:
condition ? true : false
总是 代码味道。布尔运算符 return 布尔值,所以让它们完成它们的工作: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)
但我认为这样更清楚。这个方法 returns
true
ifnumber_of_uses
isnil
有点奇怪,因为它确实如此,我们可以像这样简化它: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
returns nil
仍然困扰着我。事实证明,如果我们 return 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