When Is 'Eval' in Ruby Justified

When is `eval` in Ruby justified?

The only case I know of (other than "I have this string and I want to execute it") is dynamically dealing with local and global variables. Ruby has methods to get the names of local and global variables, but it lacks methods to get or set their values based on these names. The only way to do AFAIK is with eval.

Any other use is almost certainly wrong. I'm no guru and can't state categorically that there are no others, but every other use case I've ever seen where somebody said "You need eval for this," I've found a solution that didn't.

Note that I'm talking about string eval here, by the way. Ruby also has instance_eval, which can take either a string or a block to execute in the context of the receiver. The block form of this method is fast, safe and very useful.

Is 'eval' supposed to be nasty?

If you are evaling a string submitted by, or modifiable by the user, this is tantamount to allowing arbitrary code execution. Imagine if the string contained an OS call to rm -rf / or similar. That said, in situations where you know the strings are appropriately constrained, or your Ruby interpreter is sandboxed appropriately, or ideally both, eval can be extraordinarily powerful.

The problem is analogous to SQL injection, if you're familiar. The solution here is similar to the solution to the injection problem (parameterized queries). That is, if the statements you would like to eval are known to be of a very specific form, and not all of the statement need be submitted by the user, only a few variables, a math expression, or similar, you can take in these small pieces from the user, sanitize them if necessary, then evaluate the safe template statement with the user input plugged in in the appropriate places.

Using eval function in Ruby to call other functions

In this case you may safely use send instead of eval, like in this example:

def processQuestion(question)
return send("get#{question.command}", question)
end

Just be aware that send may be as dangerous as eval if you do not sanitize your input (question.command in this case).

If possible, do a white-list filtering before calling send (or eval), otherwise someone could pass a command which does something you do not want to do.

eval certain regex from file to replace chars in string

While you can write something to parse that file, it rapidly gets complicated because you have to parse regular expressions. Consider /\/foo\\/.

There are a number of incomplete solutions. You can split on whitespace, but this will fail on /foo bar/.

re, replace = line.split(/\s+/, 2)

You can use a regex. Here's a first stab.

match = "/3/ 4".match(%r{^/(.*)/\s+(.+)})

This fails on escaped /, we need something more complex.

match = '/3\// 4'.match(%r{\A / ((?:[^/]|\\/)*) / \s+ (.+)}x)

I'm going to guess it was not your teacher's intent to have you parsing regexes. For the purposes of the assignment, splitting on whitespace is probably fine. You should clarify with your teacher.


This is a poor data format. It is non-standard, difficult to parse, and has limitations on the replacement. Even a tab-delimited file would be better.

There's little reason to use a non-standard format these days. The simplest thing is to use a standard data format for the file. YAML or JSON are the most obvious choices. For such simple data, I'd suggest JSON.

[
{ "re": "e", "replace": "3" },
{ "re": "l", "replace": "1" }
]

Parsing the file is trivial, use the built-in JSON library.

require 'json'
specs = JSON.load("test.json")

And then you can use them as a list of hashes.

specs.each do |spec|
# No eval necessary.
re = Regexp.new(spec["re"])

# `gsub!` replaces in place
result.gsub!(re, spec["replace"])
end

The data file is extensible. For example, if later you want to add regex options.

[
{ "re": "e", "replace": "3" },
{ "re": "l", "replace": "1", "options": ['IGNORECASE'] }
]

While the teacher may have specified a poor format, pushing back on bad requirements is good practice for being a developer.

Justify string ruby

Assuming that you want to add space first to the last word, then to the first, then to second last etc:

def justify(str, length)
words = str.split(/\s+/)
cnt = words.size - 1
return str if cnt < 1
return str if str.size >= length

diff = length - str.size
base = diff / cnt + 1
rest = diff % cnt

cnt.times.each.with_index do |word, i|
r = [i + 1, cnt - i - 1].min * 2 <= rest && rest > 0 ? 1 : 0
rest -= r
words[i] << " " * (base + r)
end
words.join
end

justify("There is an example to make Justify the text us", 50)
#=> "There is an example to make Justify the text us"

justify("One more example", 50)
#=> "One more example"

justify("Or this", 50)
#=> "Or this"

justify("Example", 50)
#=> "Example"

justify("There is an example to make Justify the very long text us", 50)
#=> "There is an example to make Justify the very long text us"

How to store and retrieve values using Ruby?

eval is a nasty way of accessing data ( When is `eval` in Ruby justified? ). You should consider moving things into an object.

I have improved the code slightly, storing the cities in a hash, which gets rid of the evals. I have stubbed out the generate_costs logic but you can assign it by doing:

cities[:new_york][0] = rand(10)

Ideally, the code should be re-written in an object-oriented syntax. If I get some time then I'll knock up an example for you.

Here is the code:

#Initializing variables. Current_location should be changed to random 
#in the future.

current_location = :omaha
train = []
cities = {
:new_york => [],
:chicago => [],
:omaha => [],
:dallas => [],
:seattle => []
}

def prompt()
print "> "
end

#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.

def selection()
puts "Do you want to travel, buy, sell or get info?"

prompt; selection = gets.chomp

if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end

#This generates a new cost for each good at the start of each turn.
def generate_costs(cities)
cities.each do |key,city|
0.upto(2) do |i|
city[i] = rand(10)
end
end
end


# This is my main() loop. It drives the game forward.
for i in (0..5)
generate_costs(cities)

turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."

p cities

puts "{ ___________________________ }"
fish = cities[current_location][0]
coal = cities[current_location][1]
cattle = cities[current_location][2]
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"

change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end

end

String of int and range to array of int (Looking for improvement)

s = "10,12...15,17" 
s.split(',').map { |n| n['...'] ? Range.new(*n.split('...', 2)).to_a : n}.flatten
#=> ["10", "12", "13", "14", "15", "17"]


Related Topics



Leave a reply



Submit