Passing Ruby Hash into Classes - ruby

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)

Related

How to do user-inputted string templating in 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"

Syntax error, unexpected tIDENTIFIER, expecting ')' Ruby

I get the following error when running a simple method that takes in a proper noun string and returns the string properly capitalized.
def format_name(str)
parts = str.split
arr = []
parts.map do |part|
if part[0].upcase
else part[1..-1].downcase
arr << part
end
end
return arr.join(" ")
end
Test cases:
puts format_name("chase WILSON") # => "Chase Wilson"
puts format_name("brian CrAwFoRd scoTT") # => "Brian Crawford Scott"
The only possibility that the above code returns a blank output is because your arr is nil or blank. And the reason your arr is blank(yes it is blank in your case) because of this line of code:
if part[0].upcase
in which the statement would always return true, because with every iteration it would check if the first element of the part string can be upcased or not, which is true.
Hence, your else block never gets executed, even if this got executed this would have returned the same string as the input because you are just putting the plain part into the array arr without any formatting done.
There are some ways you can get the above code working. I'll put two cases:
# one where your map method could work
def format_name(str)
parts = str.split
arr = []
arr = parts.map do |part|
part.capitalize
end
return arr.join(" ")
end
# one where your loop code logic works
def format_name(str)
parts = str.split
arr = []
parts.map do |part|
arr << "#{part[0].upcase}#{part[1..-1].downcase}"
end
return arr.join(" ")
end
There are numerous other ways this could work. I'll also put the one I prefer if I am using just plain ruby:
def format_name(str)
str.split(' ').map(&:capitalize)
end
You could also read more about the Open Classes concept to put this into the String class of ruby
Also, checkout camelize method if you're using rails.

List in Ruby gem cli

I am making a ruby cli that outputs a list of game deals scraped from a site.
The list prints out promptly using
def games_sales
Deal.all.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
My problem comes when asking the user to select an item from the list.
def game_selection(input)
deal = Deal.find_by_index(input)
#binding.pry
deal.each do |deal|
puts "#{deal.index}"
puts " Name: #{deal.title}"
puts " Price: #{deal.price}"
puts " Store: #{deal.store}"
puts " Expiration: #{deal.expiration}"
end
deal
end
It returns the int input but only the first item on the list every time.
I forgot my find_by_index method:
def self.find_by_index(input)
all.select do |deal|
end
end
which is incomplete
Not 100% sure if I got your question right and if you're using Rails, but Deals.all let me think of this.
I had to replace Deals.all with DEALS for testing as I haven't got a rails app running. So I used an Array of OpenStructs to fake your Model result.
# this fakes Deals.all
require 'ostruct'
DEALS = [
# add any more properties the same way as title, separated by comma
OpenStruct.new(title: 123),
OpenStruct.new(title: 456)
]
def games_sales
DEALS.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
def game_selection(input)
deal = DEALS.at(input-1)
p deal[:title]
end
def self.find_by_index(input)
all.select do |deal|
deal.index == input
end
end
games_sales
Result when choosing 1 is 123, choosing 2 you'll get 456, due to p deal[:title] above in the code.
I think your find_by_index need to get the right index and in my example I had to use at(index) as at(input-1) in order to get the right result.
I really hope this helps somehow and I suggest that you add the expected result to your question, in case my answer does not help you.

Resigning an Array in an if block that is never ran makes it nil

Ok, so I have a function, which if a variable ends up not being an array,
makes it an array, with the value of the original variable as the first and
only element
iv written the code as following
variable = userInputOfSorts()
puts JSON.pretty_generate(variable) # Prints expected output, array with 2 values
if (!variable.is_a?(Array)
variable = [variable]
end
puts variable # nil
Now, I have put debug statements inside this if block(puts statements) and can confirm that it is not running.
With the debug statements inside the if block, which I am certain is not running, I tried to comment out the reassignment statement
# variable = [variable])
shouldn't matter, that statement is never ran anyway. But by god, it worked. Obviously, I need that statement there for when the user input is not an array.
So my problem is, the reassignment statement that is never ran, makes the variable null.
I have an example written, that one can run to verity this. It seems to have something to do with classes/attr_reader, as it worked when just writing it outside a class, and running it.
require 'JSON'
class MainClass
attr_reader :mock_input
def initialize(input)
puts "hello"
#mock_input = input
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
if (!mock_input.is_a?(Array))
puts "Converting to array..."
mock_input = [mock_input]
end
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
end
end
MainClass.new ["hello", "world"]
A slightly shorter implementation to convert input to an array if it isn't already one:
def input_to_array(input)
[*input]
end
Using your original code:
variable = userInputOfSorts()
puts JSON.pretty_generate(variable) # Prints expected output, array with 2 values
variable = [*variable]
puts variable # nil
I filed a bug report, and got an answer from one of the developers. Apparently, in ruby, commenting out code in if blocks that are evaluated to false should be able to affect your program. AKA, this is not a bug, its a "feature"
But,
I found a solution
So, the original problem is
require 'JSON'
class MainClass
attr_reader :mock_input
def initialize(input)
puts "hello"
#mock_input = input
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
if (!mock_input.is_a?(Array))
puts "Converting to array..."
mock_input = [mock_input]
end
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
end
end
MainClass.new ["hello", "world"]
changing the line
mock_input = [mock_input]
to
#mock_input = [#mock_input]
Fixes it
PS: as #Wayne Conrad pointed out, I could have just used Array(mock_input) instead of the if statement entirely.

Function calls in hash come up empty in Ruby

I've been sifting through the prior questions and answers on stackoverflow, and I have gotten most of my question figured out. I figured out that I can't place a function call within a hash, without placing it within a proc, or a similar container.
What I'm ultimately trying to do is have a menu displayed, grab user input, and then iterate through the hash, and run the specified function:
def Main()
menu_titles = {"Answer1" => Proc.new{Choice1()}}
Menu(menu_titles)
end
def Choice1()
puts "Response answer"
end
def Menu(menu_titles)
menu_titles.each_with_index do |(key, value),index|
puts "#{index+1}. #{key}"
end
user_input = 0
menu_titles.each_with_index do |(key, value), index|
if index.eql?(user_input)
menu_titles[value]
break
end
end
end
Main()
The issue I'm having right now is that I'm not entering the functions that my hash calls for. Whether I use a return or a "puts", I either get a blank line or nothing at all. If anyone has other recommendations about my code, I'm all ears also. To be honest, I don't like using procs, but that's mostly because I don't entirely know how they work and where to use them.
Right now for my menus I have:
user_input = 1
if user_input == 1
Choice1()
...
end
Here's how I would refactor this:
class Menu
attr_reader :titles
# initialize sets up a hard-coded titles instance variable,
# but it could easily take an argument.
def initialize
#titles = {
"Answer1" => Proc.new{ puts "choice 1" },
"Answer2" => Proc.new{ puts "choice 2" }
}
end
# This is the only public instance method in your class,
# which should give some idea about what the class is for
# to whoever reads your code
def choose
proc_for_index(display_for_choice)
end
private
# returns the index of the proc.
def display_for_choice
titles.each_with_index { |(key,value), index| puts "#{index + 1}. #{key}" }
gets.chomp.to_i - 1 # gets will return the string value of user input (try it in IRB)
end
# first finds the key for the selected index, then
# performs the hash lookup.
def proc_for_index(index)
titles[titles.keys[index]]
end
end
If you're serious about Ruby (or object-oriented programming in general), I would highly recommend learning about the advantages of packaging your code into behavior-specific classes. This example allows you to do this:
menu = Menu.new
proc = menu.choose
#=> 1. Answer1
#=> 2. Answer2
2 #(user input)
proc.call
#=> choice 2
And you could actually run it on one line:
Menu.new.choose.call

Resources