Upgade to Ruby 3.1 breaks code when using CSV class from standard library - ruby

I'm upgrading a Project written for JRuby 1.7 (corresponding on the language level to Ruby 1.9) to JRuby 9.4 (corresponding to Ruby 3.1.0). In this code, we have
require 'csv'
....
CSV.parse(string, csv_options) { .... }
where string is of class String and csv_optionsis of class Hash. This statement produces, when run under the new Ruby version, the error
ArgumentError:
wrong number of arguments (given 2, expected 1)
I found in the Ruby docs the following difference in the definition of parse:
Old version:
def parse(str, options={}, &block)
New version
def parse(str, **options, &block)
I understand that in the new Ruby, I would have to invoke parse as
CSV.parse(string, **csv_options) {....}
However, I would like to keep the code compatible for both versions, at least for some transition period, but the old JRuby does not understand **variable (I would get a syntax error, unexpected tPOW).
Is there a way to write the invocation of CSV.parse in such a way, that it preserves the original semantics and can run under Ruby 1.9 and Ruby 3.1? Currently the best solution for this problem which I can think of, is to write something like turning the block argument into a proc and writing
if RUBY_VERSION < '2'
CSV.parse(string, csv_options, &myproc)
else
# Without the eval, the compiler would complain about
# the ** when compiled with Ruby 1.9
eval "CSV.parse(string, **csv_options, &myproc)"
end
which looks pretty awful.

Not sure exactly what you are passing as csv_options but all versions can handle this using an a combination of implicit Hash/kwargs. e.g.
CSV.parse(string, col_sep: '|', write_headers: false, quote_empty: true) { ... }
If this is not an option then you going to need to patch the CSV class to make it backwards compatible e.g.
csv_shim.rb
# Shim CSV::parse to mimic old method signature
# while supporting Ruby 3 kwargs argument passing
module CSVShim
def parse(string, options={}, &block)
super(string, **options, &block)
end
end
CSV.singleton_class.prepend(CSVShim)
Then you can modify as:
require 'csv'
require 'csv_shim.rb' if RUBY_VERSION > '2.6.0'
#...
CSV.parse(string, csv_options) { .... }

Related

How can I mimic Node.js's require function in Ruby?

In node.js you can write:
var lib = require('lib');
but in Ruby the require function simply runs the code in the file and true is returned.
Currently, I'm using a very dirty solution:
main.rb:
$stuff = []
require './file1.rb'
require './file2.rb'
# and so on
file1.rb:
$stuff << something
and so on.
How can I eliminate the use of a global variable?
eg:
main.rb:
$stuff = []
$stuff << cool_require './file1.rb'
# etc
file1.rb:
exports.what = something
One of the biggest errors when working with a language, is trying to make the language working like a different one.
Ruby is not NodeJs, there are features built-in into each language that are unique to the language and cannot be reproduced easily.
In other words, there is no way to implement the NodeJS require behavior in Ruby because in Ruby there is no notion of export. When you require a file, every method/class included in the required file are made available to the scope.
In Ruby there are objects and method visibility. The way you have to make a method visible or not is to declare it as public or private/protected.
Well, first consider that Ruby is not Node.js. As Simone Carletti said, there are some features that are unique to each language. Sometimes it's good to take from other language but sometimes it's bad.
There are few things that you must keep in mind:
meth is method invocation, to pass method you use method method: method(:meth) or package it into module/class
you can use class/module by assigning it to some 2nd variable:
class A;
def self.aa; puts 'aa'; end;
end;
New_a = A;
New_a.aa # aa;
eval is dangerous method(you can evaluate unknown code)
Method:
Here is one way you can do. It is not idiot-proof tough. It is not 100% safe(eval). :
file1.rb:
Module.new do
def self.meth1
42
end
def self.meth2
'meth2'
end
end
This file contain module with 2 methods.
I am using Module.new because it returns object that you want. You can assign it later into variable/constant.
I am using self.meth* so you don't have to include but run instantly it like this: module_name.meth1()
req.rb:
def cool_require name
eval(File.read name)
end
Some_variable = cool_require('req.rb')
puts Some_variable.meth1 # 42
puts Some_variable.meth2 # meth2
cool_require reads filename(argument name) and evaluate it(it is just like you would type it in irb/pry)
Some_variable is constant. It won't disappear that easily.
2 last line is how it works. As fair I remember, that's how node.js' require works.

Ruby 1.8.7 method parameter defaults

In ruby 1.8.7 I notice that
def some_method(arg1,arg2='default',arg3,arg4)
will return
syntax error, unexpected ',', expecting '='
It works fine in Ruby 1.9
However, this works in Ruby 1.8.7:
def some_method(arg1,arg2='default',arg3='default',arg4='default')
Is this normal, or did I do something wrong here?
Ruby 1.8.7 only supports optional arguments at the end of the argument list.
# works in all versions of ruby
def foo(a, b=2)
puts "a:#{a} b:#{b}"
end
foo(1) # a:1 b:2
foo(2, 3) # a:2 b:3
However ruby 1.9+ supports optional arguments in any spot.
# works only in ruby 1.9+
def foo(a=1, b)
puts "a:#{a} b:#{b}"
end
foo(5) # a:1 b:5
foo(5, 6) # a:5 b:6
You're doing it right. Optional arguments that come before required arguments was a language feature introduced in ruby 1.9, and is unavailable in ruby 1.8.x versions.

Ruby 1.9.3 JSON Parsing

I'm encountering a weird problem when parsing JSON with Ruby 1.9.3-p392 under RVM on CentOS 6.4. Instead of decoding embedded objects into their appropriate Ruby classes, it's just loading the object as a hash. In Ruby 1.9.3-p194 it works correctly.
Take the following sample:
require 'json'
class TestMe
attr_accessor :me
def initialize(option_hash = nil)
if option_hash
#me = option_hash['me']
end
#me ||= "Hello"
end
def to_json(*a)
{
JSON.create_id => self.class.name,
'data' => {
"me" => #me
}
}.to_json(*a)
end
def self.json_create(o)
new(o['data'])
end
end
t = TestMe.new
t.me = "foo"
t2 = JSON.parse(t.to_json)
puts t2
If I run this on Ruby 1.9.3-p194, it outputs the following:
#<TestMe:0x00000001c877f0>
If I run the same snippet on Ruby 1.9.3-p392, it outputs the following:
{"json_class"=>"TestMe", "data"=>{"me"=>"foo"}}
The behavior in p194 is what I expect and what the documentation implies. Why isn't p392 parsing the JSON data correctly?
Still not sure why/what changed, but I found a work-around. Basically, you need to construct a Parser object and pass in the :create_additions option, instead of just calling JSON.parse.
Example:
p = JSON::Parser.new(json_string, {:create_additions => true})
result = p.parse
As others have stated, it sounds like the recent change to the way JSON objects are unmarshelled. I ran into a very similar issue here and got a great answer.
JSON by itself will return a hash. There is an extension to it that gives it added capability. Try using:
require 'json'
require 'json/add/core'
I think at one point in past Rubies JSON automatically loaded the extensions but that was dropped for comparability with the JSON spec.
"add/core" includes some to_json methods to base objects and might add the capability to recover custom objects. I ran into a similar situation passing regular expressions via JSON and that was the fix.
I'm not near my computer so that isn't confirmed, but it might help.

How can I read Bytes from a File in Ruby 1.9?

In Ruby 1.9 the File and IO libraries were changed -- they now seem to always interpret the data as encoded strings (e.g. UTF-8), and the returned values seem to be always strings.
I need to read a file in Ruby 1.9 byte by byte, without any modification or interpretation of the data.
I want to read byte sequences, not encoded strings.
Any tips on how to best do this?
I had a similar problem in a gem I wrote. Here's the relevant code:
(you don't need the require statements)
# ==============================================================================
# Loading Libraries and Stuff needed for Ruby 1.9 vs 1.8 Compatibility
# ==============================================================================
# the idea here is to define a couple of go-between methods for different classes
# which are differently defined depending on which Ruby version it is -- thereby
# abstracting from the particular Ruby version's API of those classes
if RUBY_VERSION >= "1.9.0"
require "digest/md5"
require "digest/sha1"
include Digest
require 'fileutils' # replaces ftools
include FileUtils::Verbose
class File
def read_bytes(n) # returns a string containing bytes
self.bytes.take(n)
end
def write_bytes(bytes)
self.syswrite(bytes)
end
def get_byte
self.getbyte # returns a number 0..255
end
end
ZEROBYTE = "\x00".force_encoding(Encoding::BINARY) unless defined? ZEROBYTE
else # older Ruby versions:
require 'rubygems'
require "md5"
require "sha1"
require 'ftools'
def move(a,b)
File.move(a,b)
end
class String
def getbyte(x) # when accessing a string and selecting x-th byte to do calculations , as defined in Ruby 1.9
self[x] # returns an integer
end
end
class File
def read_bytes(n)
self.read(n) # should use sysread here as well?
end
def write_bytes(bytes)
self.write(bytes) # should use syswrite here as well?
end
def get_byte # in older Ruby versions <1.9 getc returned a byte, e.g. a number 0..255
self.getc # returns a number 0..255
end
end
ZEROBYTE = "\0" unless defined? ZEROBYTE
end
IO has the binmode method (calling it is setting it), which disables newline and encoding conversion. The File class inherits this method.

Printing the source code of a Ruby block

I have a method that takes a block.
Obviously I don't know what is going to be passed in and for bizarre reasons that I won't go into here I want to print the contents of the block.
Is there a way to do this?
You can do this with Ruby2Ruby which implements a to_ruby method.
require 'rubygems'
require 'parse_tree'
require 'parse_tree_extensions'
require 'ruby2ruby'
def meth &block
puts block.to_ruby
end
meth { some code }
will output:
"proc { some(code) }"
I would also check out this awesome talk by Chris Wanstrath of Github http://goruco2008.confreaks.com/03_wanstrath.html He shows some interesting ruby2ruby and parsetree usage examples.
In Ruby 1.9+ (tested with 2.1.2), you can use https://github.com/banister/method_source
Print out the source via block#source:
#! /usr/bin/ruby
require 'rubygems'
require 'method_source'
def wait &block
puts "Running the following code: #{block.source}"
puts "Result: #{yield}"
puts "Done"
end
def run!
x = 6
wait { x == 5 }
wait { x == 6 }
end
run!
Note that in order for the source to be read you need to use a file and execute the file (testing it out from irb will result in the following error: MethodSource::SourceNotFoundError: Could not load source for : No such file or directory # rb_sysopen - (irb)
Building on Evangenieur's answer, here's Corban's answer if you had Ruby 1.9:
# Works with Ruby 1.9
require 'sourcify'
def meth &block
# Note it's to_source, not to_ruby
puts block.to_source
end
meth { some code }
My company uses this to display the Ruby code used to make carbon calculations... we used ParseTree with Ruby 1.8 and now sourcify with Ruby 1.9.
In Ruby 1.9, you can try this gem which extract the code from source file.
https://github.com/ngty/sourcify
In Ruby 2.5 the following works
puts block.source
In ruby 2.7, using the method_source gem (pry depends on it)
Set.instance_method(:merge).source.display
# =>
def merge(enum)
if enum.instance_of?(self.class)
#hash.update(enum.instance_variable_get(:#hash))
else
do_with_enum(enum) { |o| add(o) }
end
self
end
The repo says it works for procs, but I haven't tested it.

Resources