Asking user for information, and never having to ask again - ruby

I want to ask for user input, but I only want to do it once (possibly save the information within the program), meaning, something like this:
print "Enter your name (you will only need to do this once): "
name = gets.chomp
str = "Hello there #{name}" #<= As long as the user has put their name in the very first
# time the program was run, I want them to never have to put thier name in again
How can I got about doing this within a Ruby program?
This program will be run by multiple users throughout the day on multiple systems. I've attempted to store it into memory, but obviously that failed because from my understand that memory is wiped everytime a Ruby program stops executing.
My attempts:
def capture_user
print 'Enter your name: '
name = gets.chomp
end
#<= works but user has to put in name multiple times
def capture_name
if File.read('name.txt') == ''
print "\e[36mEnter name to appear on email (you will only have to do this once):\e[0m "
#esd_user = gets.chomp
File.open('name.txt', 'w') { |s| s.puts(#esd_user) }
else
#esd_user = File.read('name.txt')
end
end
#<= works but there has to be a better way to do this?
require 'tempfile'
def capture_name
file = Tempfile.new('user')
if File.read(file) == ''
print "\e[36mEnter name to appear on email (you will only have to do this once):\e[0m "
#esd_user = gets.chomp
File.open(file, 'w') { |s| s.puts(#esd_user) }
else
#esd_user = File.read(file)
end
end
#<= Also used a tempfile, this is a little bit over kill I think,
# and doesn't really help because the users can't access their Appdata

You will want to store the username in a file on the local file system. Ruby provides many ways to do this, and we'll explore one in this answer: YAML files.
YAML files are a structured storage file that can store all kinds of different data, and is a good place to store config data. In fact, YAML configuration files are key parts of the largest Ruby projects in existence. YAML gives you a good starting point for supporting future configuration needs, beyond the current one, which is a great way to plan feature development.
So, how does it work? Let's take a look at your requirement using a YAML config:
require 'yaml'
config_filename = "config.yml"
config = {}
name = nil
if file_exists(config_filename)
begin
config = YAML.load_file(config_filename)
name = config["name"]
rescue ArgumentError => e
puts "Unable to parse the YAML config file."
puts "Would you like to proceed?"
proceed = gets.chomp
# Allow the user to type things like "N", "n", "No", "nay", "nyet", etc to abort
if proceed.length > 0 && proceed[0].upcase == "N"
abort "User chose not to proceed. Aborting!"
end
end
end
if name.nil? || (name.strip.length == 0)
print "Enter your name (you will only need to do this once): "
name = gets.chomp
# Store the name in the config (in memory)
config["name"] = name
# Convert config hash to a YAML config string
yaml_string = config.to_yaml
# Save the YAML config string to the config file
File.open(config_filename, "w") do |out|
YAML.dump(config, out)
end
end
Rather than show you the bare minimum to meet your needs, this code includes a little error handling and some simple safety checks on the config file. It may well be robust enough for you to use immediately.
The very first bit simply requires the YAML standard library. This makes the YAML functions work in your program. If you have a loader file or some other common mechanism like that, simply place the require 'yaml' there.
After that, we initialize some variables that get used in this process. You should note that the config_filename has no path information in it, so it will be read from the current directory. You will likely want to store the config file in a common place, such as in ~/.my-program-name/config.yml or C:\Documents and Settings\MyUserName\Application Data\MyProgramName\. This can be done pretty easily, and there's plenty to help, such as this Location to Put User Config Files in Windows and Location of ini/config files in linux/unix.
Next, we check to see if the file actually exists, and if so, we attempt to read the YAML contents from it. The YAML.load_file() method handles all the heavy lifting here, so you just have to ask the config hash that's returned for the key that you're interested in, in this case, the "name" key.
If an error occurs while reading the YAML file, it indicates that the file might possibly be corrupted, so we try to deal with that. YAML files are easy to edit by hand, but when you do that, you can also easily introduce an error that will make loading the YAML file fail. The error handling code here will allow the user to abort the program and go back to fix the YAML file, so that it doesn't simply get overwritten.
After that, we try to see if we've been had a valid name from the YAML config, and if not, we go ahead and accept it from the user. Once they've entered a name, we add it to the config hash, convert the hash to a YAML-formatted string, and then write that string to the config file.
And that's all it takes. Just about anything that you can store in a Ruby hash, you can store in a YAML file. That's a lot of power for storing config information, and if you later need to add more config options, you have a versatile container that you can use exactly for that purpose.
If you want to do any further reading on YAML, you can find some good information here:
YAML in Ruby Tutorial on Robot Has No Heart
Jamming with Ruby YAML on Juixe Techknow
YAML on Struggling with Ruby
While some of these articles are a bit older, they're still very relevant and will give you a jumping off point for further reading. Enjoy!

If you need the name to persist across the user running the script several times, you're going to need to use some sort of data store. As much as I hate flat files, if all you're storing is the user's name, I think this is a valid option.
if File.exist?('username.txt')
name = File.open( 'username.txt', 'r' ) do |file|
name = file.gets
end
else
print "Enter your name (you will only need to do this once): "
name = gets.chomp
File.open( 'username.txt', 'w' ) do |file|
file.puts name
end
end
str = "Hello there #{name}"

Related

Having a CSV file and letting a user edit

In ruby, if I have a CSV like this:
make,model,color,doors,email
dodge,charger,black,4,practice1#whatever.com
ford,focus,blue,5,practice2#whatever.com
nissan,350z,black,2,practice3#whatever.com
mazda,miata,white,2,practice4#whatever.com
honda,civid,brown,4,practice5#whatever.com
corvette,stingray,red,2,practice6#whatever.com
ford,fiesta,blue,5,practice7#whatever.com
bmw,m4,black,2,practice8#whatever.com
audi,a5,blue,2,practice9#whatever.com
subaru,brz,black,2,practice10#whatever.com
lexus,rc,black,2,practice11#whatever.com
I want to allow a user to enter an email and be able to edit any one of the options listed. For example, a user enters the email "practice11#whatever.com" and it will output "lexus,rc,black,2,practice11#whatever.com". Then from here the program will output some message that will tell the user to select to edit by "make,model,color,doors,email", and then be able to change whatever is there. Like lets say they choose "color", then they can change the color from "black" to "blue" of "practice11#whatever.com" line. I believe this can be done using a hash and using key-values but I am not sure how to exactly make the editing part work.
this is my current code:
require "csv"
csv = CSV.read('cars.csv', headers: true)
demo = gets.chomp
print csv.find {|row| row['email'] == demo}
all it does it takes in the csv file and allows a user to enter in an email and it will output that specific line.
So - your question is a bit vague and involves a number of implied questions, such as "how do I write code that can ask for different options and act accordingly" - so it might help if you clarify exactly what you are trying to ask.
From the looks of it, you seem most interested in understanding how to modify the CSV table, and to get info about the CSV fields/table/data etc..
And for this, you have two friends: The ruby 'p' method and the docs.
The 'p' method allows you to inspect objects. "p someObject" is the same as calling 'puts someObject.inspect' - and it's very handy, as is "puts someObject.class" to find out what type of object you're dealing with.
In this case, you can change the last line of your code a bit to get some info:
puts csv.class
got = csv.find {|row| row['email'] == demo}
p got
And suddenly we learn we are dealing with a CSV::Table
This is not surprising, let's head over to the docs. I don't know what version of ruby you're using, but 2.6.1 is current enough to have the info we need and is plenty old at this point, so you probably have access to it:
https://ruby-doc.org/stdlib-2.6.1/libdoc/csv/rdoc/CSV.html
Tells us that if we do the CSV.read using headers:
"If headers specified, reading methods return an instance of CSV::Table, consisting of CSV::Row."
So now we know we have a CSV::Table (which is much like an array/list but with some convenience methods (such as the 'find' that you are using).
And a CSV::Row is basically a hash that maintains it's order and is, as expected, keyed according to the headers.
So we can do:
p got.fields
p got['model']
got['model'] = 'edsel'
p got['model']
p got.fields
And not surprisingly, the CSV::Table has a 'to_s' method that let's us print out the CSV:
puts csv.to_s
You can probably take it from here.

Ruby I/O Existence Issues

I have a decent amount of knowledge about Ruby code, but I do have an issue with my current project. I use Gosu to make 2D games (and once I figure out how, simple 3D games), so I need a resolution.
And this is where my question comes in, why does Ruby keep giving me an error when seeing if the settings file exists? I've been trying to get it working with the file not existing, which keeps giving me the error, "'initialize': No such file or directory # rb_sysopen - ./settings.set (Errno::ENOENT)" which has been annoying me for the last few days. The file gives no issues and actually works as intended when I leave the file created, but I want it to be where if the file gets deleted off of someone's computer, it rebuilds the file and creates a new one using default values.
Here's the area that it keeps crashing at:
settings = []
settings_file_existance = File.file?("settings.set")
if settings_file_existance == true
File.open("settings.set").readlines.each do |line|
settings.push line
end
else
settings_file = File.open("settings.set")
settings_file.write "800"
settings_file.write "\n"
settings_file.write "600"
settings_file.close
end
I have tried looking for fixes on this site, along with many others, but no one so far has been able to help.
You could try this:
settings = []
if File.file?("settings.set")
settings = File.read("settings.set").split("\n")
else
File.open("settings.set", "w") do |file|
file.write "800\n600"
end
end
As a side note, consider that the above code will set settings only if settings.set file exits, otherwise it will remain in an empty array (i.e. []).
If you wish to avoid that, just define settings with the default values, for example:
settings = [800, 600]
Now if settings.set file doesn't exist, then settings will be [800, 600], otherwise it will be overwritten with the values from settings.set.
To avoid writing 800 and 600 twice, you could use settings variable to get the values to be written in the new file, for example:
file.write(settings.join("\n"))
Putting it all together, your code would look like this:
settings = [800, 600]
if File.file?("settings.set")
settings = File.read("settings.set").split("\n")
else
File.open("settings.set", "w") do |file|
file.write(settings.join("\n"))
end
end

How to import a column of a CSV file into a Ruby array?

My goal is to import a one column of a CSV file into a Ruby array. This is for a self-contained Ruby script, not an application. I'll just be running the script in Terminal and getting an output.
I'm having trouble finding the best way to import the file and finding the best way to dynamically insert the name of the file into that line of code. The filename will be different each time, and will be passed in by the user. I'm using $stdin.gets.chomp to ask the user for the filename, and setting it equal to file_name.
Can someone help me with this? Here's what I have for this part of the script:
require 'csv'
zip_array = CSV.read("path/to/file_name.csv")
and I need to be able to insert the proper file path above. Is this correct? And how do I get that path name in there? Maybe I'll need to totally re-structure my script, but any suggestions on how to do this?
There are two questions here, I think. The first is about getting user input from the command line. The usual way to do this is with ARGV. In your program you could do file_name = ARGV[0] so a user could type ruby your_program.rb path/to/file_name.csv on the command line.
The next is about reading CSVs. Using CSV.read will take the whole CSV, not just a single column. If you want to choose one column of many, you are likely better off doing:
zip_array = []
CSV.foreach(file_name) { |row| zip_array << row[whichever_column] }
Okay, first problem:
a) The file name will be different on each run (I'm supposing it will always be a CSV file, right?)
You can solve this problem with creating a folder, say input_data inside your Ruby script. Then do:
Dir.glob('input_data/*.csv')
This will produce an array of ALL files inside that folder that end with CSV. If we assume there will be only 1 file at a time in that folder (with a different name), we can do:
file_name = Dir.glob('input_data/*.csv')[0]
This way you'll dynamically get the file path, no matter what the file is named. If the csv file is inside the same directory as your Ruby script, you can just do:
Dir.glob('*.csv')[0]
Now, for importing only 1 column into a Ruby array (let's suppose it's the first column):
require 'csv'
array = []
CSV.foreach(file_name) do |csv_row|
array << csv_row[0] # [0] for the first column, [1] for the second etc.
end
What if your CSV file has headers? Suppose your column name is 'Total'. You can do:
require 'csv'
array = []
CSV.foreach(file_name, headers: true) do |csv_row|
array << csv_row['Total']
end
Now it doesn't matter if your column is the 1st column, the 3rd etc, as long as it has a header named 'Total', Ruby will find it.
CSV.foreach reads your file line-by-line and is good for big files. CSV.read will read it at once but using it you can make your code more concise:
array = CSV.read(, headers: true).map do |csv_row|
csv_row['Total']
end
Hope this helped.
First, you need to assign the returned value from $stdin.gets.chomp to a variable:
foo = $stdin.gets.chomp
Which will assign the entered input to foo.
You don't need to use $stdin though, as gets will use the standard input channel by default:
foo = gets.chomp
At that point use the variable as your read parameter:
zip_array = CSV.read(foo)
That's all basic coding and covered in any intro book for a language.

Understanding Parsing YAML

I'm trying to understand how YAML works and I'm working from examples in a book to learn it. Here's the code I'm using:
require "yaml"
def savelist(songs, playlist)
File.open playlist, 'w' do |f|
f.write(songs.to_yaml)
end
end
def openlist(playlist)
loadlist = File.read playlist
YAML::load loadlist
end
podcasts = Dir['C:/Users/7/Music/Amazon MP3/*.mp3']
puts "Welcome to the Playlist Maker!"
list = 'playlist.txt'
savelist(podcasts, list)
openlist(list)
I thought the second method, openlist, would actually call the file to the screen and print out its contents. I read it as "create a loadlist object then load the object to the screen with YAML". I expected it to print the file contents (YAML::load).
The program works, but I don't really understand how it works. I'm also not really sure what the second method is there for if it doesn't actually open the file.
Thanks for your insight. I sorta get what YAML does, but I don't really know how or why this works.
Consider this:
require "yaml"
def savelist(songs, playlist)
File.write(playlist, songs.to_yaml)
end
def openlist(playlist)
YAML::load_file(playlist)
end
Learning how YAML works is easier if you look at what it's emitting, and play with it in IRB:
>> require 'yaml'
false
>> songs = ['foo', 'bar']
[
[0] "foo",
[1] "bar"
]
>> puts songs.to_yaml
---
- foo
- bar
nil
>>
>> yaml = songs.to_yaml
"---\n- foo\n- bar\n"
>> YAML::load(yaml)
[
[0] "foo",
[1] "bar"
]
YAML is just text, so writing a YAML file is simple if you use write, as I did above. YAML has a convenient method load_file to read a file, then parse it back into the equivalent Ruby object. If you've already read the file into a variable, then use load to parse it and return the Ruby object.
Playing with YAML this way, by converting an array or hash to YAML, then using load to mess with it, is a great way to learn it. The YAML spec is useful once you start to get an idea how it works, but doing the "round-trip" thing is how to boot-strap yourself. (This is also useful when learning about JSON.)
We actually use a variation of this when initially defining complex YAML files. I'll write a bit of Ruby code with the object defined inside it, then save that to a file. From that point on we can tweak the YAML, or, as I prefer, we tweak the Ruby and re-create the YAML. Using the Ruby code to recreate the file means we've got a fall-back if someone hoses the configuration. At least we can rebuild our default setup.
Let's step through this and see how we might learn what a block of code does.
def openlist(playlist)
loadlist = File.read playlist
YAML::load loadlist
end
For starters we're passing some playlist object to File.read. We can look up the File class but we won't find a read method on it. We will however see that File is a subclass of IO and if we look at IO we will find IO.read
read(name, [length [, offset]] ) → string
So read takes a file name and an optional length and offset and returns a string. loadlist is then just a string containing the contents of this file. Useful but not easy for us to work with yet.
If we look up the YAML module we learn that it is a wrapper around Psych where we can finally find load
load(yaml, filename = nil)
Load yaml in to a Ruby data structure...
So load takes some string and returns a Ruby data structure created by parsing the YAML syntax it contains.
Now we see why this example is using two different methods, one to load the contents of a file and one to parse those into a Ruby data structure. While we're looking at Psych we might also notice the load_file method which gives us another way to do the same thing as #the-tin-man suggested.

Uploading and parsing text document in Rails

In my application, the user must upload a text document, the contents of which are then parsed by the receiving controller action. I've gotten the document to upload successfully, but I'm having trouble reading its contents.
There are several threads on this issue. I've tried more or less everything recommended on these threads, and I'm still unable to resolve the problem.
Here is my code:
file_data = params[:file]
contents = ""
if file_data.respond_to?(:read)
contents = file_data.read
else
if file_data.respond_to?(:path)
File.open(file_data, 'r').each_line do |line|
elts = line.split
#
#
end
end
end
So here are my problems:
file_data doesn't 'respond_to?' either :read or :path. According to some other threads on the topic, if the uploaded file is less than a certain size, it's interpreted as a string and will respond to :read. Otherwise, it should respond to :path. But in my code, it responds to neither.
If I try to take out the if statements and straight away attempt File.open(file_data, 'r'), I get an error saying that the file wasn't found.
Can someone please help me find out what's wrong?
PS, I'm really sorry that this is a redundant question, but I found the other threads unhelpful.
Are you actually storing the file? Because if you are not, of course it can't be found.
First, find out what you're actually getting for file_data by adding debug output of file_data.inspect. It maybe something you don't expect, especially if form isn't set up correctly (i.e. :multipart => true).
Rails should enclose uploaded file in special object providing uniform interface, so that something as simple as this should work:
file_data.read.each_line do |line|
elts = line.split
#
#
end

Resources