保持对象

时间:2015-09-21 14:06:14

标签: ruby functional-programming

我正在寻找一种简洁的方法来评估object并在条件得到验证时返回object,否则nil以便我可以使用默认值。类似的东西:

result = object.verify?{ |object| object.test? } || default_value

我可以看到实现这个的几种方法,但我希望有一种内置的方法来实现这一点。例如:

  • 转到Array级别

    def verify?(&block)
      Array(self).filter(block).first
    end
    
  • 使用instance_eval

    def verify?(&block)
      self.instance_eval{ |object| yield(object) ? object  : nil}
    end
    

修改

这是我的实际例子(虽然这个问题没有被提到):

class User < ActiveRecord::Base
  def currency
    self.billing_information.try(:address).try(:country).try(:currency_code).instance_eval{ |currency| Finance::CURRENCIES.include?(currency) ? currency : nil} || 'EUR'
  end
end

我知道这很丑陋,但我确实喜欢它的逻辑:如果我正在寻找的对象存在,那就去找下一个。第一个条件是存在(使用try),然后是包含。

5 个答案:

答案 0 :(得分:4)

关于您的实际问题 - Rails提供presence_in

  

如果接收器包含在参数中,则返回接收器,否则返回nil

'EUR'.presence_in %w(EUR USD) #=> "EUR"
'JPY'.presence_in %w(EUR USD) #=> nil

我可能会将实际货币与经过验证的货币分开(因此您仍然可以访问前一种货币):

class User < ActiveRecord::Base
  def currency
    billing_information.try(:address).try(:country).try(:currency_code)
  end

  def verified_currency
    Finance::CURRENCIES.include?(currency) ? currency : 'EUR'
  end
end

并移动检查货币的逻辑并将默认货币提供给Finance

class User < ActiveRecord::Base
  def currency
    billing_information.try(:address).try(:country).try(:currency_code)
  end

  def verified_currency
    Finance.verified_currency(currency)
  end
end

module Finance
  CURRENCIES = %w(EUR USD)
  DEFAULT_CURRENCY = 'EUR'

  def self.verified_currency(currency)
    CURRENCIES.include?(currency) ? currency : DEFAULT_CURRENCY
  end
end

这也避免了必须两次评估User#currency

try - 链可以替换为delegate

class User < ActiveRecord::Base
  delegate :currency, to: billing_information, allow_nil: true

  def verified_currency
    Finance.verified_currency(currency)
  end
end

class BillingInformation < ActiveRecord::Base
  delegate :currency, to: address, allow_nil: true
end

class Address < ActiveRecord::Base
  delegate :currency, to: country, allow_nil: true
end

答案 1 :(得分:3)

如果在条件满足时需要接收器,则假设test?在满足条件时返回真值,否则返回假值:

result = object.tap{|object| break unless object.test?} || default_value

或者,按照Stefan的建议:

result = object.tap{|object| break default_value unless object.test?}

答案 2 :(得分:2)

我认为@Stefan的建议是最好的,但是如果你坚持要迟钝,你可以写:

(object.test? && object) || default

答案 3 :(得分:0)

如果您认为需要可以处理默认值的Object#try版本,则可以对其进行修补。

require "rails"

class Object
    alias_method :old_try, :try
    def try(params, default = nil, &block)
        old_try(params, &block) || default
    end
end

object = {}
default = "I'm default"

p result = object.try(:test, default)
#=> I'm default

答案 4 :(得分:-1)

我认为您正在寻找Enumerable#detect

  

detect(ifnone = nil) { |obj| block } → obj or nil   将enum中的每个条目传递给block。返回block不是false的第一个。 如果没有匹配的对象,则调用ifnone并在指定时返回其结果,否则返回nil

(1..10).detect   { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
(1..100).find    { |i| i % 5 == 0 and i % 7 == 0 }   #=> 35

所以你可以按照自己的要求做result = object.detect { |object| object.test? } || default_value,或者更加惯用result = object.detect(default_value) { |object| object.test? }