2015-09-21 3 views
1

Я ищу чистый способ оценить 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 
    

EDIT

Вот мой конкретный пример (хотя вопрос не обтянутый к нему):

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), затем включение.

+1

Вам не нужна эта вещь 'instance_eval', на первый взгляд. Почему бы просто не «уступить» («я»)? self: nil'? –

+0

Не понятно, какое значение вы хотите, когда условие выполнено. Вы хотите «истинный»? – sawa

+0

Очень странный вопрос. Почему бы не просто 'result = object.test? || default_value'? –

ответ

3

Если вы хотите приемник, когда условие выполнено, то при условии, что test? возвращает truthy значение при выполнении условия и falsy значение иначе:

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

Или, следуя предложению Стефана:

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

Ницца. Я предполагаю, что нет способа избежать необходимости писать лишние слова, такие как 'break if' в вашем случае? –

+1

Вы ожидаете слишком многого. – sawa

+0

Я слишком глубоко вписываюсь в функциональный стиль с одним слоем кода ... –

-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? }.

+1

За исключением того, что его объект не перечислим. –

+0

Фактически, 'ifnone' должен быть вызываемым объектом, а не только некоторым значением по умолчанию. Поэтому ваш последний пример должен выглядеть так: result = object.detect (-> {default_value}, &: test?) '. –

2

Я думаю, что @ предложение Стефана лучше, но если вы настаиваете на том, тупые, вы могли бы написать:

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

Вам даже не нужны скобки, '&&' имеет более высокий приоритет :-) – Stefan

+0

@Stefan, для большей ясности. :) –

+0

Кроме того, я не хочу повторять объект, потому что в моем случае это будет означать: '% w (EUR, USD) .include? (Self.billing_information.try (: address) .try (: country) .try (: currency_code)) && self.billing_information.try (: address) .try (: country) .try (: currency_code) || «EUR'' –

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 
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 
Смежные вопросы