0

I have a function like this:

def check_if_correct_type(type, value)
    # nil.test!
    # eval(type.classify(value)) rescue return false
    # true
    case type
    when "integer"
        !!Integer(value) rescue return false
    when "float"
        !!Float(value) rescue return false
    else
        return true
    end
    true
end

A sample would be

 check_if_correct_type("integer", "a")

I tried changing the function like this:

check_if_correct_type(type, value)
  !!(eval(type.classify(value))) rescue return false
  true
end

This is throwing errors. How do I fix this. I am fairly new to meta programming so kind of lost.


Update 1:

"adfadf".kind_of?(String) #=> true
123.kind_of?(String)      #=> false

# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum)   #=> true 

12.3.kind_of?(Float)      #=> true
"sadf".kind_of?(Float)    #=> false
12.kind_of?(Float)        #=> false

The above will not work for me as the kind_of? function will find the type of the object where as for me the answer requires to be like this:

check_if_correct_type("integer", "1221") #=> true
check_if_correct_type("float", "1.24") #=> true
check_if_correct_type("string", "asds12") #=> true
check_if_correct_type("float", "asdasd1.24") #=> false

where as

"1.24".kind_of?(Float) #=> false

That is why conversion works for me. Hope the question is more clear now.


Update 2:

This is what I get if I use public send.

!!public_send("integer".capitalize("1")) ArgumentError: wrong number of arguments (1 for 0) from (pry):4:in capitalize' [5] pry(main)> !!public_send("integer".classify("1")) ArgumentError: wrong number of arguments (1 for 0) from /home/aravind/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/string/inflections.rb:187:inclassify'

Note: classify is a part of Ruby on Rails and not Ruby.

Aravind
  • 1,391
  • 1
  • 16
  • 41
  • Note that `Kernel#Integer` and `Kernel#Float` are *conversion* methods and of limited use for type checking. `Integer(2.5)` returns `2`, although `2.5` is not an integer and `Integer(Time.new)` returns the number of seconds. – Stefan Feb 28 '15 at 19:27
  • Where does `value` come from and what is `check_if_correct_type` supposed to do? How is it used? – Stefan Feb 28 '15 at 20:14
  • @Stefan Check the question now, I have updated. – Aravind Feb 28 '15 at 21:44

5 Answers5

1

I suggest you write your method as follows:

def correct_type?(type, str)
  case type.downcase
  when "integer"
    !!to_integer(str)
  when "float"
    !!to_float(str)
  else
    raise ArgumentError, "type must be 'integer' or 'float'"
  end
end

where to_integer(value) (to_float(value)) is a method that returns value.to_i (value.to_f) if value is the string representation of an integer (a float), else returns nil. The methods to_integer and to_float are useful because they tell you both whether the string can be converted to the given numerical type, and if it can, give you the numerical value.

Before considering how you might implement to_integer and to_float, I would like to call into question the need for correct_type?. Rather than:

str = "33"
if correct_type?("integer", str)
  n = str.to_i
  puts n
else
  ...
end

would it not be better to write:

if (n = to_integer("33"))
  puts n
else
  ...
end

There are basically two ways to write the methods to_integer and to_float. The first is the approach you took:

def to_integer(str)
  raise ArgumentError unless str.is_a? String
  s = str.gsub(/\s/,'')
  Integer(s) rescue nil
end

def to_float(str)
  raise ArgumentError unless str.is_a? String
  s = str.gsub(/\s/,'')
  return nil if to_integer(s)
  Float(s) rescue nil
end

to_integer("3")     #=> 3 
to_integer("-3")    #=> -3 
to_integer("+  3")  #=> 3 
to_integer("cat")   #=> nil 
to_integer("3.14")  #=> nil 
to_integer(:cat)    #=> ArgumentError: ArgumentError

to_float("3.14")    #=> 3.14 
to_float("-3.14")   #=> -3.14 
to_float("+  3.14") #=> 3.14 
to_float("cat")     #=> nil 
to_float("3")       #=> nil 
to_float(:cat)      #=> ArgumentError: ArgumentError

The second approach is to use a regular expression:

def to_integer(str)
  raise ArgumentError unless str.is_a? String
  s = str.gsub(/\s/,'')
  s[/^[+-]?\s*\d+$/] ? s.to_i : nil
end

def to_float(str)
  raise ArgumentError unless str.is_a? String
  s = str.gsub(/\s/,'')
  return nil if to_integer(s)
  s[/^[+-]?\s*\d+\.\d+$/] ? s.to_f : nil
end

to_integer("3")     #=> 3 
to_integer("-3")    #=> -3 
to_integer("+  3")  #=> 3 
to_integer("cat")   #=> nil 
to_integer("3.14")  #=> nil 
to_integer(:cat)    #=> ArgumentError: ArgumentError

to_float("3.14")    #=> 3.14 
to_float("-3.14")   #=> -3.14 
to_float("+  3.14") #=> 3.14 
to_float("cat")     #=> nil 
to_float("3")       #=> nil 
to_float(:cat)      #=> ArgumentError: ArgumentError
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

There is no need to use eval to send a message. You can just use send instead:

def check_if_correct_type(type, value)
  !!send(type.capitalize, value) rescue return false
  true
end

Note: there is no method named classify anywhere in either the Ruby core library or the Ruby standard libraries. Note also that it is a very bad idea, to just blindly catch all exceptions.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 2
    @Aravind: if you have a question about Rails, you should tag your question with [tag:ruby-on-rails], and if you use methods from third-party libraries, you should mention so in your question. Many people who know Ruby don't know Rails, so you exclude a lot of people from helping you if you don't mention something like that. – Jörg W Mittag Mar 06 '15 at 12:26
  • Got it will do it from next time – Aravind Mar 06 '15 at 12:33
  • A last comment: you can remove `true` and `return`, making this a one-liner – Stefan Mar 06 '15 at 12:36
0

I don't see a point of using metaprogramming for this example. You should avoid using it where there's no need for it. Generally speaking, your program's logic should be:

a) Check the type of the value entered. b) Compare the type with the type entered as argument. Or in code:

def check_if_correct_type(type, value)
  actual_type = value.class.name
  return actual_type.downcase == type.downcase
end

p check_if_correct_type('string', 'test') #=> true
p check_if_correct_type('integer', 'test2') #=> false

This could could be made even shorter in one line, but did it in two to demonstrate more clearly what's going on.

daremkd
  • 8,244
  • 6
  • 40
  • 66
0

If you want to check an object's class, the right way is this:

"adfadf".kind_of?(String) #=> true
123.kind_of?(String)      #=> false

# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum)   #=> true 

12.3.kind_of?(Float)      #=> true
"sadf".kind_of?(Float)    #=> false
12.kind_of?(Float)        #=> false

There is no reason to be using the Integer() or Float() methods to check for a type. Those are type conversion methods, they will convert other types to Float or Fixnum. If you do want to try to convert a type is convertable to Float or numeric, that is one way to do it, but there might be better ways.

In general, you should never plan on raising and rescuing an exception as part of ordinary program flow; one reason is because it is very slow. Exceptions should be used for errors and unusual/exceptional conditions, not routine conditions such that exceptions will be frequently raised.

And definitely don't start bringing eval into it, geez why would you do that?

jrochkind
  • 22,799
  • 12
  • 59
  • 74
  • This will not work for me, see the updated question. – Aravind Feb 28 '15 at 21:46
  • Also llook at what avdi has to say about this usage http://stackoverflow.com/a/1235876/2143985 – Aravind Feb 28 '15 at 23:01
  • 1
    One problem with using exceptions for routine flow control is that it is very slow in ruby -- especially in Jruby. It may not matter for your program depending on how often you are doing it, but it is something to be aware of. The theoretical argument "exceptions should not be used for flow control just because" doesn't matter too much to me, but the performance implications are something to be aware of. – jrochkind Mar 01 '15 at 16:17
  • @jrochkind: of course, if more people used exceptions for control flow, implementors would be forced to make them more performant ;-) After all, Ruby itself already codifies usage of `StopIteration` into the language and lilbraries. – Jörg W Mittag Mar 06 '15 at 12:39
0

This is how I have ended up solving my problem

def check_if_correct_type(type, value)
    !!eval("#{type.classify}(value)") rescue return false
    true
end

Sample output for this function is below incase you are wondering if it words or not

[25] pry(main)> value = "1"
=> "1"
[26] pry(main)> !!eval("#{type.classify}(value)")
=> true
[27] pry(main)> value = "a"
=> "a"
[28] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Float(): "a"
from (pry):28:in `eval'
[29] pry(main)> value = "1.4"
=> "1.4"
[30] pry(main)> type = "integer"
=> "integer"
[31] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Integer(): "1.4"
from (pry):31:in `eval'
Aravind
  • 1,391
  • 1
  • 16
  • 41
  • You should avoid using `eval`, see [Jörg W Mittag's answer](http://stackoverflow.com/a/28789815/477037) for an alternative. – Stefan Mar 06 '15 at 12:38