I am migrating an application from Ruby 2.6 to Ruby 3.0 and am running into an issue opening a CSV file for writing.
The following code worked fine in 2.6.
CSV.open(csv_path, "wb", {:col_sep => ";"}) do |csv|
...
end
When I move to 3.0 I am getting the error "wrong number of arguments (given 3, expected 1..2)".
I am not seeing anything in Ruby Docs to indicate a change from 2.6
2.6
open( filename, mode = "rb", **options ) { |faster_csv| ... }
3.0
open(file_path, mode = "rb", **options ) { |csv| ... } → object
I see that the CSV library was updated in Ruby 3.0, but am not seeing what could have changed that would cause this code to no longer work.
Any tips would be greatly appreciated.
Edward
In Ruby 3.0, positional arguments and keyword arguments was separated, with deprecation in Ruby 2.7. That means, that the Ruby 2.7 deprecation warning: Using the last argument as keyword parameters is deprecated, ie. using a hash as argument for **options is no longer supported.
You'll have to call it either as:
# Manual spreading hash
CSV.open(csv_path, "wb", **{:col_sep => ";"}) do |csv|
...
end
# Or as Keyword argument
CSV.open(csv_path, "wb", :col_sep => ";") do |csv|
...
end
EDIT: See also https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
Related
Is there a way to read files encoded in UTF-8 with BOM (Byte order marks) on Ruby v2.5.0?
On Ruby 2.3.1 this used to work:
csv = CSV.open(file_path, encoding: 'bom|utf-8')
However, on 2.5.0 the following error ocurrs:
ArgumentError:
unknown encoding name - bom|utf-8
You can try this as well:
File.open(file_path, "r:bom|utf-8")
You can try this:
require 'file_with_bom'
File.open(file_path, "w:utf-8", :bom => true ) do |csv|
end
it works well
Given the following code:
content = "Hello {{name | default: 'Friend'}}"
Liquid::Template.parse(content).render('name' => '')
The above code should output Hello Friend but instead it is showing Hello
The default filter, whilst it is in master, has not yet been released in a gem (2.6.1 is the latest gem at time of writing). Liquid’s behaviour when seeing an unknown filter seems to be to ignore it and return the string unchanged without reporting an error.
You could use the current master to get the default filter, which would be easy if you’re using Bundler, but you might not want to use unreleased code. Otherwise you could just copy it into your code until there is a release that includes it:
module MyFilters
def default(input, default_value = "")
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
is_blank ? default_value : input
end
end
Liquid::Template.register_filter(MyFilters)
content = "Hello {{name | default: 'Friend'}}"
Liquid::Template.parse(content).render("name" => '')
# => "Hello Friend"
The default filter has been finally added to the liquid gem in version 3.0.0, so it should work now:
https://github.com/Shopify/liquid/pull/267
When using Tempfile Ruby is creating a file with a thread-safe and inter-process-safe name. I only need a file name in that way.
I was wondering if there is a more straight forward approach way than:
t = Tempfile.new(['fleischwurst', '.png'])
temp_path = t.path
t.close
t.unlink
Dir::Tmpname.create
You could use Dir::Tmpname.create. It figures out what temporary directory to use (unless you pass it a directory). It's a little ugly to use given that it expects a block:
require 'tmpdir'
# => true
Dir::Tmpname.create(['prefix-', '.ext']) {}
# => "/tmp/prefix-20190827-1-87n9iu.ext"
Dir::Tmpname.create(['prefix-', '.ext'], '/my/custom/directory') {}
# => "/my/custom/directory/prefix-20190827-1-11x2u0h.ext"
The block is there for code to test if the file exists and raise an Errno::EEXIST so that a new name can be generated with incrementing value appended on the end.
The Rails Solution
The solution implemented by Ruby on Rails is short and similar to the solution originally implemented in Ruby:
require 'tmpdir'
# => true
File.join(Dir.tmpdir, "YOUR_PREFIX-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-YOUR_SUFFIX")
=> "/tmp/YOUR_PREFIX-20190827-1-wyouwg-YOUR_SUFFIX"
File.join(Dir.tmpdir, "YOUR_PREFIX-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-YOUR_SUFFIX")
=> "/tmp/YOUR_PREFIX-20190827-1-140far-YOUR_SUFFIX"
Dir::Tmpname.make_tmpname (Ruby 2.5.0 and earlier)
Dir::Tmpname.make_tmpname was removed in Ruby 2.5.0. Prior to Ruby 2.4.4 it could accept a directory path as a prefix, but as of Ruby 2.4.4, directory separators are removed.
Digging in tempfile.rb you'll notice that Tempfile includes Dir::Tmpname. Inside you'll find make_tmpname which does what you ask for.
require 'tmpdir'
# => true
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname("prefix-", nil))
# => "/tmp/prefix-20190827-1-dfhvld"
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".ext"], nil))
# => "/tmp/prefix-20190827-1-19zjck1.ext"
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".ext"], "suffix"))
# => "/tmp/prefix-20190827-1-f5ipo7-suffix.ext"
Since Dir::Tmpname.make_tmpname was removed in Ruby 2.5.0, this one falls back to using SecureRandom:
require "tmpdir"
def generate_temp_filename(ext=".png")
filename = begin
Dir::Tmpname.make_tmpname(["x", ext], nil)
rescue NoMethodError
require "securerandom"
"#{SecureRandom.urlsafe_base64}#{ext}"
end
File.join(Dir.tmpdir, filename)
end
Since you only need the filename, what about using the SecureRandom for that:
require 'securerandom'
filename = "#{SecureRandom.hex(6)}.png" #=> "0f04dd94addf.png"
You can also use SecureRandom.alphanumeric
I found the Dir:Tmpname solution did not work for me. When evaluating this:
Dir::Tmpname.make_tmpname "/tmp/blob", nil
Under MRI Ruby 1.9.3p194 I get:
uninitialized constant Dir::Tmpname (NameError)
Under JRuby 1.7.5 (1.9.3p393) I get:
NameError: uninitialized constant Dir::Tmpname
You might try something like this:
def temp_name(file_name='', ext='', dir=nil)
id = Thread.current.hash * Time.now.to_i % 2**32
name = "%s%d.%s" % [file_name, id, ext]
dir ? File.join(dir, name) : name
end
I just can't get the 'To a String' example under 'Writing' example in the documentation to work at all.
ruby -v returns:
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin10.8.0]
The example from the documentation I can't working is here:
csv_string = CSV.generate do |csv|
csv << ["row", "of", "CSV", "data"]
csv << ["another", "row"]
end
The error I get is:
wrong number of arguments (0 for 1)
So it seems like I am missing an argument, in the documentation here it states:
This method wraps a String you provide, or an empty default String
But when I pass in a empty string, it gives me the following error:
No such file or directory -
I am not looking to generate a csv file, I just wanted to create a string of csv that I send as text to the user.
Here is code I know works against Ruby 1.9.2 with Rails 3.0.1
def export_csv(filename, header, rows)
require 'csv'
file = CSV.generate do |csv|
csv << header if not header.blank?
rows.map {|row| csv << row}
end
send_data file, :type => 'text/csv; charset=iso-8859-1; header=present', :disposition => "attachment;filename=#{filename}.csv"
end
From the libxml-ruby API docs (http://libxml.rubyforge.org/rdoc/index.html), under LibXML::XML::Document, I tried the following:
filename = 'something.xml'
stats_doc = XML::Document.new()
stats_doc.root = XML::Node.new('root_node')
stats_doc.root << XML::Node.new('elem1')
stats_doc.save(filename, :indent => true, :encoding => 'utf-8')
... which resulted in this error:
parse_stats.rb:26:in `save': can't convert String into Integer (TypeError)
(where the last line in the block above was line 26).
I tried changing the file name to an integer, which gave me this:
parse_stats.rb:26:in `save': wrong argument type Fixnum (expected String) (TypeError)
So I gathered that I need to use a string, but strings don't seem to work. Since I seem to be unable to find any examples of libxml-ruby in action off Google, I'm at a loss. Any help would be very appreciated, or links to any online example where I can see how libxml-ruby is used for creating XML documents.
libxml-ruby 1.1.3
rubygems 1.3.1
ruby 1.8.7
Seems that the problem is with encoding. Try XML::Encoding::UTF_8 instead of "utf-8".
require 'rubygems'
require 'libxml'
filename = 'something.xml'
stats_doc = LibXML::XML::Document.new()
stats_doc.root = LibXML::XML::Node.new('root_node')
stats_doc.root << LibXML::XML::Node.new('elem1')
stats_doc.save(filename, :indent => true, :encoding => LibXML::XML::Encoding::UTF_8)