Ruby how to write to Tempfile? - ruby

I am trying to create a Tempfile and write some text into it. But I get this strange behavior in console
t = Tempfile.new("test_temp") # => #<File:/tmp/test_temp20130805-28300-1u5g9dv-0>
t << "Test data" # => #<File:/tmp/test_temp20130805-28300-1u5g9dv-0>
t.write("test data") # => 9
IO.read t.path # => ""
I also tried cat /tmp/test_temp20130805-28300-1u5g9dv-0 but the file is empty.
Am I missing anything? Or what's the proper way to write to Tempfile?
FYI I'm using ruby 1.8.7

You're going to want to close the temp file after writing to it. Just add a t.close to the end. I bet the file has buffered output.

Try this
run t.rewind before read
require 'tempfile'
t = Tempfile.new("test_temp")
t << "Test data"
t.write("test data") # => 9
IO.read t.path # => ""
t.rewind
IO.read t.path # => "Test datatest data"

close or rewind will actually write out content to file. And you may want to delete it after using:
file = Tempfile.new('test_temp')
begin
file.write <<~FILE
Test data
test data
FILE
file.close
puts IO.read(file.path) #=> Test data\ntestdata\n
ensure
file.delete
end

It's worth mentioning, calling .rewind is a must otherwise any subsequent .read call will just return empty value

Related

How would you close this file descriptor?

Let's say you have the following code:
from_file, to_file = ARGV
puts "Copying from #{from_file} to #{to_file}"
#in_file = open(from_file)
#indata = in_file.read
indata = open(from_file).read # Combined in_file and indata.
puts "The input file is #{indata.length} bytes long."
puts "Does the output file exist? #{File.exist?(to_file)}"
puts "Ready, hit RETURN to continue or CTRL-C to abort."
$stdin.gets
out_file = open(to_file, 'w')
out_file.write(indata)
puts "Alright, all done."
out_file.close
#in_file.close
How would you close the file descriptor invoked by indata? You will need to close File open, but indata is really a (File open).read.
P.S. Since it's a script, it will be closed automatically upon exit. Let's assume that we're running a general, consistently running backend service. And we don't know whether garbage collector will kick in, so we will need to explicitly close it. What would you do?
If you are just copying the file...
you could just use FileUtils#cp:
FileUtils.cp("from_file", "to_file")
or even shell-out to the operating system and do it with a system command.
Let's suppose you want to do something to the input file before writing it to the output file.
If from_file is not large,...
you could "gulp it" into a string using IO.read:
str = IO.read(from_file)
manipulate str as desired, to obtain new_str, then then blast it to the output file using IO#write:
IO.write("to_file", new_str)
Note that for the class File:
File < IO #=> true # File inherits IO's methods
which is why you often see this written File.read(...) and File.write(...).
If from_file is large, read a line, write a line...
provided the changes to be made are done for each line separately.
f = File.open("to_file", "w") # or File.new("to_file", "w")
IO.foreach("from_file") do |line|
# < modify line to produce new_line >
f.puts new_line
end
f.close
foreach closes "from_file" when it's finished. If f.close is not present, Ruby will close "to_file" when the method containing the code goes out of scope. Still, it's a good idea to close it in case other work is done before the code goes out of scope.
Passing File.open a block is generally a nice way to go about things, so I’ll offer it up as an alternative even if it doesn’t seem to be quite what you asked.
indata = File.open(from_file) do |f|
f.read
end

Taking json data and converting it to a CSV file

Okay... so new to Ruby here but loving it so far. My problem is I cannot get the data to go into the CSV files.
#!/usr/bin/env ruby
require 'date'
require_relative 'amf'
require 'json'
require 'csv'
amf = Amf.new
#This makes it go out 3 days
apps = amf.post( 'Appointments.getBetweenDates',
{ 'startDate' => Date.today, 'endDate' => Date.today + 4 }
)
apps.each do |app|
cor_md_params = { 'appId' => app['appID'], 'relId' => 7 }
cor_md = amf.post( 'Clinicians.getByAppIdAndRelId', cor_md_params ).first
#this is where it breaks ----->
CSV.open("ile.csv", "wb") do |csv|
csv << ["column1", "column2", "etc.", "etc.."]
csv << ([
# if added puts ([ I can display the info and then make a csv...
app['patFirstName'],
app['patMiddleName'],
app['patLastName'],
app['patBirthdate'],
app['patHin'],
app['patPhone'],
app['patCellPhone'],
app['patBusinessPhone'],
app['appTime'],
app['appID'],
app['patPostalCode'],
app['patProvince'],
app['locName'],
# note that this is not exactly accurate for follow-ups,
# where you have to replace the "1" with the actual value
# in weeks, days, months, etc
#app[ 'bookName' ], => not sure this is needed
cor_md['id'],
cor_md['providerCode'],
cor_md['firstName'],
cor_md['lastName']
].join(', '))
end
end
Now, if I remove the attempt to make the ile.cvs file and just output it with a puts, all the data shows. But I don't want to have to go into the terminal and create a csv file... I would rather just run the .rb program and have it created. Also, hopefully I am making the columns correctly as well...
The thought occurred to me that I could just add another puts above the output.
Or, better, insert a row into the array before I output it...
Really not sure what is best practice here and standards.
This is what I have done and attempted. How can I get it to cleanly output to a CSV file since my attempts are not working
Also, to clarify where it breaks, it does add the column names just not the JSON info that is parsed. I could also be completely doing this the wrong way or a way that isn't possible. I just do not know.
What kind of error do you get? Is it this one:
<<': undefined methodmap' for "something":String (NoMethodError)
I think, you should remove the .join(', ')
The << method of CSV accepts an array, but not a String
http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV.html#method-i-3C-3C
So instead of:
cor_md['lastName']
].join(', '))
rather:
cor_md['lastName']
])
The problem with the loop (why it writes only 1 row of data)
In the body of your loop, you always reopen the file, and always rewrite what you added before. What you want to do, is probably this:
CSV.open("ile3.csv", "wb") do |csv|
csv << ["column1", "column2", "etc.", "etc.."]
apps.each do |app|
cor_md_params = { 'appId' => app['appID'], 'relId' => 7 }
cor_md = amf.post( 'Clinicians.getByAppIdAndRelId', cor_md_params ).first
#csv << your long array
end
end

CSV.generate and converters?

I'm trying to create a converter to remove newline characters from CSV output.
I've got:
nonewline=lambda do |s|
s.gsub(/(\r?\n)+/,' ')
end
I've verified that this works properly IF I load a variable and then run something like:
csv=CSV(variable,:converters=>[nonewline])
However, I'm attempting to use this code to update a bunch of preexisting code using CSV.generate, and it does not appear to work at all.
CSV.generate(:converters=>[nonewline]) do |csv|
csv << ["hello\ngoodbye"]
end
returns:
"\"hello\ngoodbye\"\n"
I've tried quite a few things as well as trying other examples I've found online, and it appears as though :converters has no effect when used with CSV.generate.
Is this correct, or is there something I'm missing?
You need to write your converter as as below :
CSV::Converters[:nonewline] = lambda do |s|
s.gsub(/(\r?\n)+/,' ')
end
Then do :
CSV.generate(:converters => [:nonewline]) do |csv|
csv << ["hello\ngoodbye"]
end
Read the documentation Converters .
Okay, above part I didn't remove, as to show you how to write the custom CSV converters. The way you wrote it is incorrect.
Read the documentation of CSV::generate
This method wraps a String you provide, or an empty default String, in a CSV object which is passed to the provided block. You can use the block to append CSV rows to the String and when the block exits, the final String will be returned.
After reading the docs, it is quite clear that this method is for writing to a csv file, not for reading. Now all the converters options ( like :converters, :header_converters) is applied, when you are reading a CSV file, but not applied when you are writing into a CSV file.
Let me show you 2 examples to illustrate this more clearly.
require 'csv'
string = <<_
foo,bar
baz,quack
_
File.write('a',string)
CSV::Converters[:upcase] = lambda do |s|
s.upcase
end
I am reading from a CSV file, so :converters option is applied to it.
CSV.open('a','r',:converters => :upcase) do |csv|
puts csv.read
end
output
# >> FOO
# >> BAR
# >> BAZ
# >> QUACK
Now I am writing into the CSV file, converters option is not applied.
CSV.open('a','w',:converters => :upcase) do |csv|
csv << ['dog','cat']
end
CSV.read('a') # => [["dog", "cat"]]
Attempting to remove newlines using :converters did not work.
I had to override the << method from csv.rb adding the following code to it:
# Change all CR/NL's into one space
row.map! { |element|
if element.is_a?(String)
element.gsub(/(\r?\n)+/,' ')
else
element
end
}
Placed right before
output = row.map(&#quote).join(#col_sep) + #row_sep # quote and separate
at line 21.
I would think this would be a good patch to CSV, as newlines will always produce bad CSV output.

Basic Way to Open Files in Ruby

I'm trying to open local xml file and output its content in terminal.
I've tried this;
puts File.new('file.xml', 'r')
and this;
puts File.open('file.xml', 'r')
output from both is, instead of printing xml file to the screen;
#<File:0x00000000....>
Try this
puts File.read('file.xml')
or
puts File.open('file.xml').read
Documentation: IO.read, IO#read
I would suggest you to use block with File#open method. As with block,you don't need to close the file explicitly.Perform all your task inside the block with the file. The file will be closed automatically,when block will be terminated.
File.open('doc.txt','r') do |file|
puts file.read
end
# >> ,"11: Agriculture, Forestry, Fishing and Hunting",,
# >> ,,"111: Crop Production",
# >> ,,,"111110: Soybean Farming"
# >> ,,,"111120: Oilseed (except Soybean) Farming"

How can I copy the contents of one file to another using Ruby's file methods?

I want to copy the contents of one file to another using Ruby's file methods.
How can I do it using a simple Ruby program using file methods?
There is a very handy method for this - the IO#copy_stream method - see the output of ri copy_stream
Example usage:
File.open('src.txt') do |f|
f.puts 'Some text'
end
IO.copy_stream('src.txt', 'dest.txt')
For those that are interested, here's a variation of the IO#copy_stream, File#open + block answer(s) (written against ruby 2.2.x, 3 years too late).
copy = Tempfile.new
File.open(file, 'rb') do |input_stream|
File.open(copy, 'wb') do |output_stream|
IO.copy_stream(input_stream, output_stream)
end
end
As a precaution I would recommend using buffer unless you can guarantee whole file always fits into memory:
File.open("source", "rb") do |input|
File.open("target", "wb") do |output|
while buff = input.read(4096)
output.write(buff)
end
end
end
Here my implementation
class File
def self.copy(source, target)
File.open(source, 'rb') do |infile|
File.open(target, 'wb') do |outfile2|
while buffer = infile.read(4096)
outfile2 << buffer
end
end
end
end
end
Usage:
File.copy sourcepath, targetpath
Here is a simple way of doing that using ruby file operation methods :
source_file, destination_file = ARGV
script = $0
input = File.open(source_file)
data_to_copy = input.read() # gather the data using read() method
puts "The source file is #{data_to_copy.length} bytes long"
output = File.open(destination_file, 'w')
output.write(data_to_copy) # write up the data using write() method
puts "File has been copied"
output.close()
input.close()
You can also use File.exists? to check if the file exists or not. This would return a boolean true if it does!!
Here's a fast and concise way to do it.
# Open first file, read it, store it, then close it
input = File.open(ARGV[0]) {|f| f.read() }
# Open second file, write to it, then close it
output = File.open(ARGV[1], 'w') {|f| f.write(input) }
An example for running this would be.
$ ruby this_script.rb from_file.txt to_file.txt
This runs this_script.rb and takes in two arguments through the command-line. The first one in our case is from_file.txt (text being copied from) and the second argument second_file.txt (text being copied to).
You can also use File.binread and File.binwrite if you wish to hold onto the file contents for a bit. (Other answers use an instant copy_stream instead.)
If the contents are other than plain text files, such as images, using basic File.read and File.write won't work.
temp_image = Tempfile.new('image.jpg')
actual_img = IO.binread('image.jpg')
IO.binwrite(temp_image, actual_img)
Source: binread,
binwrite.

Resources