views:

78

answers:

2

Developing a little survey webapp, ran into problem that deals with ranges for rating type questions. So a rating's range could be:

1..10
-5..0
-5..5
'a'..'z'
'E'..'M'

and so on

The range is stored as a pair of varchars in database (start and end of range). So range always starts off as a string input.

What is the best way to take these string values and build a Ruby Range accordingly. I can't just go value.to_i as this won't work for string iteration. Having a bunch of if's seems ugly. Any better way?

Not as important, but worth asking: Also what if I wanted to make it all work with reversed range? Say 5-to-0 or G-to-A. I know that Ruby doesn't support reverse range (since it uses succ() to iterate). What would be the best way here?

Thanks in advance!

Update:

Based on Wouter de Bie's suggestion I've settled for this:

def to_int_or_string(str)
  return str.match(/^-?\d+$/) ? str.to_i : str.strip
end

def ratings_array(from, to)
  from = to_int_or_string(from)
  to = to_int_or_string(to)
  from > to ? Range.new(to, from).to_a.reverse : Range.new(from, to).to_a
end

Any thoughts?

A: 

You can do something like the following.

str = 'Z..M'

v1 = str[0,str.index('.')]
v2 = str[str.index('.')+2, str.length]

unless v1.to_i == 0
  v1 = v1.to_i
  v2 = v2.to_i
end

if v2>v1
  final_arr = (v1..v2).to_a
else
  final_arr = (v2..v1).to_a.reverse
end

puts final_arr

This takes care of both the positive and the negative ranges

Bragboy
+2  A: 

Use Range.new:

Range.new("a","z")
=> "a".."z"

Range.new(-5,5)
=> -5..5

If you're varchars contain quotes, you can use eval to get the right ranges:

from = "'a'"
to = "'z'"
eval("Range.new(#{from},#{to})")

Otherwise you could use value.to_i to figure out if it was a number or a string in the varchar:

a = "x"
a = (a.to_i == 0 && a != "0") ? a : a.to_i
=> "x"

a = "5"
a = (a.to_i == 0 && a != "0") ? a : a.to_i
=> 5

Which of course can be nicely extracted into a method:

def to_int_or_string(value)
  return (value.to_i == 0 && value != "0") ? value : value.to_i
end

def to_range(from, to)
  return Range.new(to_int_or_string(from), to_int_or_string(to))
end

To reverse your range, you have to convert it to an array first:

Range.new("a","g").to_a.reverse
=> ["g", "f", "e", "d", "c", "b", "a"]
Wouter de Bie
a = (a.to_i == 0 In case a = "1,234" the result will be 1.
steenslag
good point. but i don't expect numbers that are that large
Swartz
1,234 == 1234 or 1.234? In the first case, I think that your design is bad if you store integers with a separator in your database and your input should be sanitized.If you do anyway, the fix is easy to remove all comma's from the string before casting them to an integer (gsub('.','')). If it's a float, check for a dot in your string and call .to_f on it. (although ranges with floats don't make a lot of sense in the use case above)
Wouter de Bie