How to do user-inputted string templating in Ruby? - ruby

I know writing like
a=23
p "the value of a is #{a}"
it will print: the value of a is 23.
but now I am actually receiving this string as a parameter like
def evaluate string
a=23
puts string
end
calling method pass that string as a parameter
evaluate "the value of a is #{a}"
Is there any way to evaluate this string inside the method? puts string has to interpolate the value a=23.
Edit:
I have to read and execute the program from Excel.
At the first line,
Excel entry is,
"id=something" setvalue a
So now corresponding program will read the value from locator id=something and set it into the instance variable #a.
and user's next excel entry would be
"the value of a is 23" compare "the value of a is #{a}"
Now the program will read "the value of a is 23" and this "the value of a is #{a}" for comparison, but before it compares, it has to replace the value a. That's all I want. I hope now my question is very clear.

For ruby you can change how you "format" your strings in Excel, than you can use "classic" formatting
a = 23
s = 'the value of a is %s'
def evaluate(text, value)
puts text % value
end
You can use different formatting keys, for example %d for integers, %f for float numbers
You can use named arguments
dynamic_text = 'the value of the %<product_name>s is %<product_price>0.2f'
def evaluate(text, args)
puts text % args
end
name = "Product"
price = 78.99
evaluate dynamic_text, product_name: name, product_price: price
Without names, use order of the given values
dynamic_text = 'the value of the %s is %0.2f'
def evaluate(text, args)
puts text % args
end
name = "Product"
price = 78.99
evaluate dynamic_text, [name, price]

You can make a block and then evaluate the string:
def evaluate &block
a=23
block.call(a)
end
evaluate { |a| "the value of a is #{a}" } #=> "the value of a is 23"

It's a very odd thing you're attempting to do. When you have some sort of a pattern with placeholders, you do it like:
def evaluate(string)
a=23
format string, a: a
end
evaluate "the value of a is %{a}"
String interpolation with #{..} is not meant for the case you're describing as the value is evaluated at the time of constructing the string, not later. You could do some regexp matching and replace the #{..} with %{..} as a workaround.

There's a few ways:
"Code" Dynamic
lazy evaluation with lambdas:
def evaluate(str_template)
a = 23
str_template.call(a)
end
user_input = gets
my_lambda = lambda do |str|
user_input.size > 10 ? "dynamic 1 #{str}" : "dynamic 2 #{str}"
end
evaluate(my_lambda)
# => "dynamic 1/2 23"
This is "code dynamic", but not "input dynamic", i.e. you can't receive the string template from the user.
"Input" Dynamic 1
ERB templating:
require 'erb'
user_input_erb = gets
puts user_input_erb # "Hello <%= name %>"
name = gets # also user input, e.g. "World"
ERB.new(user_input_erb).result
# => "Hello World"
Note that in general, getting string templates from the user and evaluating them is a potential security vulnerability. If there's any possibility user input can be adversarial, you'll want to see if you can find a "guaranteed to be safe against all user input" string templating library.
"Input" Dynamic 2
user_input_template = gets
puts user_input_template # "Hello %s"
name = gets # also user input, e.g. "World"
user_input_template % name
# => "Hello World"
"Input" Dynamic 3
Really dangerous, but:
user_input_ruby_code = gets
puts user_input_ruby_code # '"Hello #{name}"'
name = gets # also user input, e.g. "World"
eval user_input_ruby_code # DANGER
# => "Hello World"

Related

Modifying arguments in Ruby

I apologize for the excessive length, I just wanted to make sure I actually understand what's going on here. This is a follow up on my previous two questions Does 'upcase!' not mutate a variable in Ruby? and Destructive and non destructive methods in Ruby.
def changeMe(string)
string + "?"
end
phrase = "how are you"
puts changeMe(phrase) #how are you?
puts phrase #how are you
When changeMe is invoked with the phrase passed in as an argument, the parameter string points to the same object as phrase. When we change the line to string + "?" we are creating a new object different from the one the string parameter points to, the same if we assigned the newly created object to a variable.
def changeMe(string)
string += "?"
end
phrase = "how are you"
puts changeMe(phrase) #how are you?
puts phrase #how are you
If I do this -
def changeMe(string)
string + "?"
string.capitalize!
end
phrase = "how are you"
puts changeMe(phrase) #How are you
puts phrase #How are you
When changeMe is invoked with phrase passed in as an argument, the string + "?" creates a new object different from the one #capitalize! is called on in the next line. #capitalize! is called on the object that the variable phrase is referencing, the same object the string parameter points to but not the same object returned by string + ?. If we reassign it to a variable,
def changeMe(string)
string += "?"
string.capitalize!
end
phrase = "how are you"
puts changeMe(phrase) #How are you?
puts phrase #how are you
string += "?" will create a new object that is assigned to a variable called string. That new object has #capitalize! called on it. The method is invoked with phrase passed in as an argument and returns a new object different from the one the variable phrase references so the original value for the variable phrase is unchanged.
Are there flaws or misconceptions in my logic. Am I accurately explaining/understanding things?
That's largely correct, but perhaps a more complicated path to understanding than necessary. In Ruby one thing that helps a lot is calling object_id on a given object to see which object it is. Every object has a unique object_id.
For example:
"test" == ("te" + "st")
# => true
"test".object_id == ("te" + "st").object_id
# => false
Or more specifically for a method that creates a new copy:
x = 'test'
y = x + '?'
x.object_id == y.object_id
# => false
You can see how in-place modifications work:
x = 'test'
y = x << '?'
x.object_id == y.object_id
# => true
Where this allows you to differentiate between in-place modifications and methods that produce new objects or copies.
Remember that every Ruby expression returns an object. If this object is not captured into a variable or used as an argument will often can be discarded if not already used.
In other words there's a huge difference between this:
def add
1 + 2 # Computed and discarded
:three # The actual return value
end
And this:
def add
1 + 2 # Computed and returned
end
Though this depends on that return value being captured, as calling the function computes the value and throws out the results again unless it's captured or used.

ArgumentError: wrong number of arguments (given 0, expected 1) Ruby

ArgumentError: wrong number of arguments (given 0, expected 1).
The code opens the file and looks at the paragraph and counts, the error
is in the center of the code. An error occurs when a method is called(1).
I can’t understand how to pass the argument methods.
#books = "You can use this knowledge to create small tools that might help."
require "colorize"
class Filecalculation
def select
loop do
puts "# Will we search : calculation_lines paragraph(1)".cyan
print "\n>>>>>> ".yellow
input = gets.chomp
search_method = "calc_#{input}"
if (respond_to?(search_method))
I can’t understand how to pass the argument to this place.
contents = send(search_method, #books)
else
puts "Unknown input: #{input.inspect}, method #{search_method} not defined."
end
end
end
# =================== calc_1 сounting words in Text File
def calc_1 paragraph
word_count = paragraph.split.length
puts "#{word_count} words"
end
end
Filecalculation.new.select
If you call send(search_method) you call a method without arguments. To pass arguments to the method being called, you need to pass them as next send args:
send(search_method, arg1, arg2)
in your case
send(search_method, paragraph)
Docs

Passing Ruby Hash into Classes

I ran into a study drill problem, and I couldn't figure it out.
Here's the link to the exercise. https://learnrubythehardway.org/book/ex40.html
Below are my work. On Study Drill 2, I passed in variables and it worked.
However, at study drill 3, I broke my code. I realized I wasn't passing in variable, but a hash. And because my class takes in 2 arguments, I couldn't figure out how to pass a dictionary as 2 arguments.
class Song
def initialize(lyrics, singer)
#lyrics = lyrics
#singer = singer
end
def sing_along()
#lyrics.each {|line| puts line}
end
def singer_name()
puts "The song is composed by #{#singer}"
end
def line_reader(lineNum)
line = #lyrics[lineNum-1]
puts "The lyrics line #{lineNum} is \"#{line}\"."
end
end
# The lyrics are arrays, so they have [] brackets
practiceSing = Song.new(["This is line 1",
"This is line 2",
"This is line 3"],"PracticeBand")
practiceSing.sing_along()
practiceSing.singer_name()
practiceSing.line_reader(3)
puts "." * 20
puts "\n"
# Variable for passing. Working on dictionary to pass the singer value.
lovingThis = {["Don't know if I'm right",
"but let's see if this works",
"I hope it does"] => 'TestingBand'}
# Everything after this line is somewhat bugged
# Because I was using a variable as an argument
# I couldn't figure out how to use dictionary or function to work with
this
practiceVariable = Song.new(lovingThis,lovingThis)
practiceVariable.sing_along()
practiceVariable.singer_name()
practiceVariable.line_reader(3)
Here's the Output. What it should do is return the singer/band, and return requested lyrics line.
I'm new to coding, please advise how to pass hashes into classes?
How to pass lovingThis hash into Song.new() and read as 2 arguments?
you can pass hash to constructor of class in the same way as we pass any other variable, But for that you need to change your constructor definition to take variable number of arguments i.e def initialize(*args)
class Song
def initialize(*args)
if args[0].instance_of? Hash
#lyrics = args[0].keys.first
#singer = args[0].values.first
else
#lyrics = args[0]
#singer = args[1]
end
end
def sing_along()
#lyrics.each {|line| puts line}
end
def singer_name()
puts "The song is composed by #{#singer}"
end
def line_reader(lineNum)
line = #lyrics[lineNum-1]
puts "The lyrics line #{lineNum} is \"#{line}\"."
end
end
# The lyrics are arrays, so they have [] brackets
practiceSing = Song.new(["This is line 1",
"This is line 2",
"This is line 3"],"PracticeBand")
practiceSing.sing_along()
practiceSing.singer_name()
practiceSing.line_reader(3)
puts "." * 20
puts "\n"
# Variable for passing. Working on dictionary to pass the singer value.
lovingThis = {["Don't know if I'm right",
"but let's see if this works",
"I hope it does"] => 'TestingBand'}
practiceVariable = Song.new(lovingThis)
practiceVariable.sing_along()
practiceVariable.singer_name()
practiceVariable.line_reader(3)

Ruby set an argument variable in an object

I need a quick hack for a ruby script which parse a json file for some variables. It stores in #{name} the name string from the json file where the id is the input for the argument.
The issue is that I cannot set the ID argument which is #{b} in the object data-on_day_you_want. If I put it in "#{b}" it won't match with the value from the json file.If I hardcoded it with 281 it works perfectly.
ps:Please find below the a small part from the json.
#!/usr/bin/env ruby
require 'rubygems'
require 'json'
a, b = ARGV #(environment and ID arguments)
file = File.read('file.json')
data_hash = JSON.parse(file)
data_on_day_you_want = data_hash["tenantAuthzProfiles"].detect{ |h| h["id"] == #{b} } # HERE IS THE ISSUE
name = data_on_day_you_want["name"]
puts "NAME is: #{name}"
the json:
{
"name": "Production Environment",
"roles": ["Host Admin"],
"id": 281
}
Do you have any suggestions?
I believe you want to parse b as integer:
data_on_day_you_want = data_hash["tenantAuthzProfiles"].detect do |h|
h["id"] == b.to_i
end
In your code, b receives the value "281" which is a string. When you compare string to integer, it'll always be false:
"281" == 281
# => false
The to_i method parses the string, and returns the integer value it represents:
"281".to_i == 281
# => true
(note that, contrary to other languages' parse methods, to_i is very lenient, so you might get surprising results "surprise".to_i == 0)
The notation of #{} is relevant to String interpolation, and is only valid inside a string (surrounded by double-quoutes)

How to Add Values to a Hash in Ruby

I have done much research on this topic, but in every circumstance I attempt, the values appear to be replaced in the hash. After the person opts to enter a new ID, I would like the next person's name and age to be added to the hash. Could someone explain to me why the keys and values are being replaced?
class Person
def initialize(name, age)
if name != nil || age != nil
if #people != nil
#people[name.__id__] = age.__id__
else
#people = {name => age}
end
else
puts "Invalid credentials."
end
end
attr_reader :people
end
class MainInit
def initialize()
options = ["y", "yes", "n", "no"]
choice = "y"
while choice.downcase == "y" || choice.downcase == "yes"
p "Enter Name:"
inputname = gets.chomp
p inputname
p "Enter Age:"
inputage = gets.chomp
p inputage
person = Person.new(inputname, inputage)
p person.people
p "Enter another ID?"
choice = gets.chomp
until options.include? choice.downcase
p "Invalid Choice"
p "Enter another ID?"
choice = gets.chomp
end
end
end
end
MainInit.new
I think the reason the key-value pairs are being replaced is this:
The statement in your initialize method
if #people != nil
will always evaluate to false. initialize is called when you create a new object, so by default #people has not been defined or set yet, so each time you call
person = Person.new(inputname, inputage)
it creates a new Person rather than adding the new person to an exiting Hash (which is what I think you are trying to do).
It might work if you make people a class variable (##people), but it seems like you just want to create a Hash in your main program and then add the new entries in there.
So something like this
people = Hash.new # Or even just people = {}
Then when you have a new name / age entry to add
people[name] = age
I have not tried it, but I think your entire program should be reduced to something like this:
people = Hash.new
options = ["y", "yes", "n", "no"]
choice = "y"
while choice.downcase == "y" || choice.downcase == "yes"
p "Enter Name:"
inputname = gets.chomp
p inputname
p "Enter Age:"
inputage = gets.chomp
p inputage
#person = Person.new(inputname, inputage)
people[inputname] = inputage
person = people[inputname]
p person.people
p "Enter another ID?"
choice = gets.chomp
until options.include? choice.downcase
p "Invalid Choice"
p "Enter another ID?"
choice = gets.chomp
end
Let me both explain why you are having the problem you describe and also offer some suggestions for how you might change your code.
class Person
In class Person, you need to save your list of persons at the class level, which means the use of either a class instance variable (e.g., #people) or a class variable (e.g., ##people). I am with the majority of Rubiests in prefering the former. (The reasons are beyond the scope of this answer, but you will find a lot written on the subject by simply Googling, "Ruby 'class instance variables' versus 'class variables'". The inner quotes--the only ones you enter--help narrow the search.)
To define a class instance variable, #people, we just enter it as follows:
class Person
#people = {}
class << self
attr_accessor :people
end
def initialize(name, age)
self.class.people[name] = age
end
end
The # means it is an instance variable. As soon as Ruby reads class Person, it sets self to Person. It then reads #people = {} and makes that an instance variable of Person. By contrast, if you were to initialize #person within, say, an initialize method, self would at that time be an instance of Person, so #person would be a normal instance variable. (Aside: we could have both a class instance variable #person and an instance variable #person, and Ruby would treat them as differently as it would #night and #day.)
In order for objects to access #people we define an accessor. If we just entered attr_accessor :person, Ruby would create an accessor for a regular instance variable #person. Instead we enter class << self, which directs Ruby to associate what follows, until end is reached, with the class.
Each time a new instance of Person is created, for a given name and age,
self.class.people[name] = age
adds an element to the hash #person, since self.class is Person and people is the accessor.
Now look at the class MainInit
class MainInit
class MainInit
def initialize
loop do
name = nil
loop do
print 'Enter Name: '
name = gets.strip
break unless name.empty?
end
puts "name is #{name}"
age = nil
loop do
print 'Enter Age: '
age = gets.strip
case age
when /^\d+$/ && ('10'..'120')
break
else
puts 'age must be between 10 and 120'
end
end
puts "age is #{age}"
person = Person.new(name, age)
puts "people is now #{Person.people}"
loop do
print "Enter another ID? "
case gets.chomp.downcase
when 'n', 'no'
return
when 'y', 'yes'
break
else
puts 'Invalid choice'
end
end
end
end
end
loop do...end
You see that in several places I have used loop do...end with break to exit a loop. I'm a big fan of this construct, as compared to loop while... or or until..., in part because it avoids the need to enter a starting condition to get into the loop and then repeat the same condition withing the loop. I also just think it looks cleaner.
Any variables created within the loop cease to exist when you leave the loop, so if you want a variable's value (e.g., name and age), you must reference the variable outside of the beginning of the loops. That is why (and the only reason) I have name = nil and age = nil. It didn't have to be nil; I could have initialized them to anything.
Use of case statement
The loop for getting age uses this case statement:
case age
when /^\d+$/ && ('10'..'120')
...
end
This requires some explanation. The case statement uses String#===, rather than String#== to obtain truthy values. Therefore when /^\d+$/ is equivalent to:
/^\d+$/ === age
which is the same as
/^\d+$/ =~ age
The regex simply ensures that all characters of age are digits (e.g., "39).
Similarly,
('10'..'120') === age
is the same as
('10'..'120').cover?(age)
Odds and Ends
I used String#strip in place of String#chomp. Both remove ending newline characters, but strip also removes spaces the user may have entered at the beginning or end of the input string.
For strings, I mostly used single quotes, but double-quotes are needed for string interpolation. For example, I initially wrote puts 'name is #{name}'. That printed name is #{name}. After changing that to puts "name is #{name}", it correctly printed name is Debra.
Example
MainInit.new
Enter Name: Debra
name is Debra
Enter Age: 29
age is 29
people is now {"Debra"=>"29"}
Enter another ID? y
Enter Name: Billy-Bob
name is Billy-Bob
Enter Age: 58
age is 58
people is now {"Debra"=>"29", "Billy-Bob"=>"58"}
Enter another ID? u
Invalid choice
Enter another ID? n

Resources