2013-10-27 7 views
2

Я пишу программу в Ruby с классом Product. У меня есть некоторые исключения, возникающие всякий раз, когда Продукт инициализируется с неправильным типом аргументов. Есть ли способ, которым я могу СУХОЙ по моим возвышенным исключениям (я даже правильно говорю об этом?) Я ценю помощь. Код ниже:Как высушить мои исключения ruby ​​в методе инициализации?

class Product 
    attr_accessor :quantity, :type, :price, :imported 

    def initialize(quantity, type, price, imported) 
    raise ArgumentError.new("Type must be a string") if type.class != String 
    raise ArgumentError.new("Quantity must be greater than zero") if quantity <= 0 
    raise ArgumentError.new("Price must be a float") if price.class != Float 

    @quantity = quantity 
    @type  = type 
    @price = price.round(2) 
    @imported = imported 
    end 
end 

ответ

4

идиоматических способ не делать проверки типа на всех, и вместо того, чтобы принуждать переданные объекты (используя to_s, to_f и т.д.):

class Product 
    attr_accessor :quantity, :type, :price, :imported 

    def initialize(quantity, type, price, imported) 
    raise ArgumentError.new("Quantity must be greater than zero") unless quantity > 0 

    @quantity = quantity 
    @type  = type.to_s 
    @price = price.to_f.round(2) 
    @imported = imported 
    end 
end 

Затем вы получите соответствующий String/Float/etc. представление переданных объектов и если они не знают, как принуждаться к этим типам (потому что они не отвечают на этот метод), то вы соответственно получите NoMethodError.

Что касается проверки на количество, которое выглядит очень похоже на проверки, которые вы можете захотеть, чтобы вытащить в отдельный метод (особенно, если получает быть много из них):

class Product 
    attr_accessor :quantity, :type, :price, :imported 

    def initialize(quantity, type, price, imported) 
    @quantity = quantity 
    @type  = type.to_s 
    @price = price.to_f.round(2) 
    @imported = imported 

    validate! 
    end 

    private 

    def validate! 
    raise ArgumentError.new("Quantity must be greater than zero") unless @quantity > 0 
    end 
end 
+0

Спасибо Andrew - супер быстрый ответ и очень полезно! – Brian

+0

Эй, @AndrewMarshall, мне было интересно, если бы вы могли объяснить причину, почему мы включаем удар по методу 'validate'? – Brian

+0

@Brian Просто потому, что он вызывает исключение в случае «false». Эта конвенция была взята из Rails, но обычно используется там (я думаю), когда есть версия без запрета. Ядро Ruby делает то же самое, но с использованием деструктивных (мутирующих) методов. В конечном счете предпочтение, так как нет никакого функционального эффекта. –

1
class Product 
    attr_accessor :quantity, :type, :price, :imported 

    def initialize(quantity, type, price, imported) 
    raise ArgumentError.new "Type must be a string" unless type.is_a?(String) 
    raise ArgumentError.new "Quantity must be greater than zero" if quantity.zero? 
    raise ArgumentError.new "Price must be a float" unless price.is_a?(Float) 

    @quantity, @type, @price, @imported = quantity, type, price.round(2), imported 
    end 
end 
+0

Это действительно здорово, я не знал, что могу установить такие переменные! Спасибо за помощь – Brian

+0

Да, вы можете :) Это параллельное назначение. – kiddorails

1

вы могли бы сделать что-то вроде следующего, хотя я ожидаю, что драгоценные камни, которые делают это и многое другое, и сделать это лучше:

module ArgCheck 
    def type_check(label, arg, klass) 
    raise_arg_err label + \ 
     " (= #{arg}) is a #{arg.class} object, but should be be a #{klass} object" unless arg.is_a? klass 
    end 

    def range_check(label, val, min, max) 
    raise_arg_err label + " (= #{val}) must be between #{min} and #{max}" unless val >= min && val <= max 
    end 

    def min_check(label, val, min) 
    puts "val = #{val}, min = #{min}" 
    raise_arg_err label + " (= #{val}) must be >= #{min}" unless val >= min 
    end 

    def max_check(val, min) 
    raise_arg_err label + " (= #{val}) must be <= #{max}" unless val <= max 
    end  

    # Possibly other checks here 

    private 

    def raise_arg_err(msg) 
    raise ArgumentError, msg + "\n backtrace: #{caller_locations}" 
    end 
end 

class Product 
    include ArgCheck 
    attr_accessor :quantity, :type, :price, :imported 

    def initialize(quantity, type, price) 
    # Check arguments 
    min_check 'quantity', quantity, 0 
    type_check 'type',  type,  String 
    type_check 'price', price, Float 

    @quantity = quantity 
    @type  = type 
    @price = price.round(2) 
    end 
end 

product = Product.new(-1, :cat, 3) 
# => arg_check.rb:23:in `raise_arg_err': quantity (= -1) must be >= 0 (ArgumentError) 
# backtrace: ["arg_check.rb:11:in `min_check'", "arg_check.rb:33:in `initialize'", \ 
#  "arg_check.rb:43:in `new'", "arg_check.rb:43:in `<main>'"] 

product = Product.new(1, :cat, 3) 
# => arg_check.rb:26:in `raise_arg_err': type (= cat) is a Symbol object, \ 
#  but should be be a String object (ArgumentError) 
#  backtrace: ["arg_check.rb:3:in `type_check'", "arg_check.rb:34:in `initialize'", \ 
#   "arg_check.rb:48:in `new'", "arg_check.rb:48:in `<main>'"] 

product = Product.new(1, "cat", 3) 
# => arg_check.rb:23:in `raise_arg_err': price (= 3) must be a Float object (ArgumentError) 
#  backtrace: ["arg_check.rb:3:in `type_check'", "arg_check.rb:35:in `initialize'", \ 
#  "arg_check.rb:53:in `new'", "arg_check.rb:53:in `<main>'"] 

product = Product.new(1, "cat", 3.00) # No exception raised 

Обратите внимание, что при запуске в IRB, Kernel#caller_locations приносит в большом количестве вещей вы не» t хотите, что вы не получите при запуске из командной строки.