简体   繁体   English

有条件的Rails实例方法取决于所设置的属性

[英]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: 如果由于某种原因您不能执行此操作,则可以改进每种方法:

  1. 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 
  2. 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 
  3. 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)但我认为这更清楚。

  4. 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_usesnil 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. 当我们可以从一个中获得相同的结果时,就不必依赖两者。

Further improvements 进一步改进

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::INFINITYhas_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM