[英]Rails instance methods conditional depending on attributes being set
I have a model that has several attributes that are optional when the model is being saved. 我有一个模型,该模型具有几个在保存模型时可选的属性。
I have several instance methods that use these attributes and perform calculations but I would like to check first if they are not nil as I will get the dreaded no method error nil nil class
我有几个使用这些属性并执行计算的实例方法,但我想先检查一下它们是否不是nil,因为我将得到可怕的no method error nil nil class
Apart from littering my code with .present?
除了使用.present?
我的代码.present?
is there a better way of doing this? 有更好的方法吗?
EDIT: Here is my code so far 编辑:这是到目前为止我的代码
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
I feel like dropping in .present?
我觉得要加入.present?
to perform checks has a bad code smell, I have looked into the null object pattern but it doesnt seem to make sense here as the object is present but some of the attributes are nil
执行检查有不好的代码味道,我已经研究了null对象模式,但是这里似乎没有任何意义,因为存在对象,但是某些属性为nil
Short-circuiting usually works best in these situations. 在这种情况下,短路通常最有效。
Before: 之前:
if @attribute.present?
@attribute.do_something
end
Short-circuiting: 短路:
@attribute && @attribute.do_something
With the short-circuiting approach, as soon as Ruby sees that the left side of the &&
operator is nil
, it will stop and not run the right side 使用短路方法,一旦Ruby看到&&
运算符的左侧为nil
,它将停止并且不运行右侧
I would also think hard about why a particular attribute should ever be allowed to be nil
(as Jordan asked). 我还要认真考虑为什么应该允许将特定属性设置nil
(如乔丹所问)。 If you can think of a way to avoid this, that may be better. 如果您想出一种避免这种情况的方法,那可能更好。
Assuming that you do want number_of_users
to be able to be nil
, you could rewrite has_uses_remaining?
假设您确实希望number_of_users
可以为nil
,则可以重写has_uses_remaining?
like this: 像这样:
def has_uses_remaining?
!number_of_uses || remaining_uses > 0
end
-Side note: your first method can be simplified to this: 旁注:您的第一种方法可以简化为:
def is_valid?
!has_expired? && has_uses_remaining?
end
I think the real issue here is that number_of_uses
can be nil
, which (as you've discovered) introduces a ton of complexity. 我认为这里的真正问题是number_of_uses
可以为nil
,这(您已经发现)引入了大量的复杂性。 Try to eliminate that issue first. 尝试首先消除该问题。
If for some reason you can't do that, each of your methods can be improved: 如果由于某种原因您不能执行此操作,则可以改进每种方法:
condition ? true : false
condition ? true : false
is always a code smell. condition ? true : false
始终是代码气味。 Boolean operators return boolean(ish) values, so let them do their jobs: 布尔运算符返回布尔值(ish),所以让他们完成工作:
def is_valid? !has_expired? && has_uses_remaining? end
Personally I think using Rails' Object#try
is usually a code smell, but here it's a pretty good fit: 我个人认为使用Rails的Object#try
通常是一种代码味道,但是这里非常合适:
def has_expired? expiry_date.try(:past?) end
Alternatively: 或者:
def has_expired? expiry_date.present? && expiry_date.past? end
This one can't be improved a whole lot, but personally I prefer an early return
to a method wrapped in an if
block: 不能完全改善这一点,但就我个人而言,我更喜欢尽早return
包装在if
块中的方法:
def remaining_uses return if number_of_uses.nil? number_of_uses - uses_count end
You could also do number_of_uses && number_of_uses - uses_count
(or even number_of_uses.try(:-, uses_count)
but I think this is clearer. 您也可以做number_of_uses && number_of_uses - uses_count
(甚至number_of_uses.try(:-, uses_count)
但我认为这更清楚。
It's a little weird that this method returns true
if number_of_uses
is nil
bit since it does we can simplify it like so: 如果number_of_uses
为nil
bit,此方法返回true
number_of_uses
因为这样做确实可以简化它,如下所示:
def has_uses_remaining? remaining_uses.nil? || remaining_uses > 0 end
Note that I call remaining_uses.nil?
请注意,我打电话给remaining_uses.nil?
instead of number_of_uses.nil?
而不是number_of_uses.nil?
; ; there's no need to depend on both when we can get the same result from one. 当我们可以从一个中获得相同的结果时,就不必依赖两者。
Upon further consideration I think you can make the intent of this code clearer by introducing another method: has_unlimited_uses?
经过进一步考虑,我认为您可以通过引入另一种方法has_unlimited_uses?
来使代码的意图更清楚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
This way there's never any ambiguity about what you're checking for. 这样,您所检查的内容就不会有任何歧义。 This will make the code more readable for the next person who reads it (or you six months from now) and make tracking down bugs easier. 这将使下一个阅读该代码的人(或您现在六个月后)更容易阅读该代码,并使查找错误更加容易。
It still bothers me, though, that remaining_uses
returns nil
. 但是, remaining_uses
uses返回nil
仍然困扰着我。 It turns out that if instead we return Float::INFINITY
, has_uses_remaining?
事实证明,如果相反,我们返回Float::INFINITY
, has_uses_remaining?
turns into a simple comparison: 变成一个简单的比较:
def remaining_uses
return Float::INFINITY if has_unlimited_uses?
number_of_uses - uses_count
end
def has_uses_remaining?
remaining_uses > 0
end
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.