How to make a gem support multiple Ruby versions elegantly - ruby

I'm writing a gem that I want to work across multiple Ruby versions, what's the best way to do this? The naive solution is to do stuff like this
if RUBY_VERSION <= 1.8.7
my_hash = {:a => 1}
elsif RUBY_VERSION >= 1.9.3
my_hash = {a: 1}
...
end
What's the best way to make your gem support multiple Ruby versions?

Ruby > 1.9.3 still supports the old hash syntax. If you need to support 1.8.7 and your only problem are hash literals, the elegant solution is to use the old syntax exclusively. This way you can drop any conditionals.

You can write two versions of gems on different files within the lib directory, and on the main file, load either of them depending on the Ruby version.
Main file (foo_gem/lib/foo.rb)
if RUBY_VERSION <= 1.8.7
require_relative "./foo-ruby1.8.7"
elsif RUBY_VERSION >= 1.9.3
require_relative "./foo-ruby1.9.3"
end

Related

Which libraries are loaded by default in pry?

When I use Tempfile class in pry, I don't use require it.
% pry -f
pry(main)> Tempfile
Tempfile < #<Class:0x00007fb5121149b8>
But when in irb I must to require tempfile first.
% irb
irb(main):001:0> Tempfile
NameError: uninitialized constant Tempfile
from (irb):1
from /Users/ironsand/.rbenv/versions/2.4.3/bin/irb:11:in `<main>'
irb(main):002:0> require 'tempfile'
=> true
So it seems pry load some libraries by default.
Which libraries are actually loaded?
This is my environment
% ruby -v
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]
% irb --version
pirb 0.9.6(09/06/30)
% pry --version
Pry version 0.11.3 on Ruby 2.4.3
You can compare $LOADED_FEATURES to see everything that's loaded in a default pry session vs an irb session (or a plain ruby script).
The short answer is that Pry loads whichever libraries it needs to provide its own behaviour. The long answer is too long (and too likely to get out of date, or differ between versions) to list here -- better to ask your current environment.
It's good practice not to rely on other libraries to load their dependencies for you, because those dependencies can change. (Though in cases like this, it can be hard to notice you're missing a require, because everything still works.)
As an alternative to $LOADED_FEATURES, which can be a bit excessive, consider:
Gem.loaded_specs.values.each {|s| puts s.name}
which only lists gems with specs
[20] pry(main)> Gem.loaded_specs.values.each {|s| puts s.name};nil
did_you_mean
coderay
method_source
pry
io-console
vls
=> nil
BTW, in this example, I had loaded the vls gem manually.

ruby 1.9.3 can't use open3 (uninitialized constant ConfigureController::Open3)

The project works fine in Ruby 1.9.2, but I want to use 1.9.3.
This line worked fine in 1.9.2:
o, e, s = Open3.capture3("echo a; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
However in 1.9.3, I get
uninitialized constant ConfigureController::Open3
Do I have to install the module, or something? thanks!
Did you require 'open3' at the top of your source file? It's possible that before, it was required in turn by something else you were requiring, but now in 1.9.3 it is not. You may need to explicitly require it.

bundler and ruby 1.9.2

Why am I not able to use these lines in a Gemfile:
gem 'date'
gem 'pp'
Must these be required in file instead like this:
require 'date'
require 'pp'
Or is there a way to mix them into your Gemfile so they are available project wide?
I think that date and pp are part of ruby 1.9.2 core and as a result are different from regular gems but I don't exactly understand why...
Because those are not Gems but part of the Ruby standard library. But the standard library isnt loaded by default, hence the require statements

What zip library works well with Ruby 1.9.2?

I used the rubyzip gem in Ruby 1.8.7 before, but I heard rubyzip doesn't work well with ruby 1.9.2.
What zip libraries work well with Ruby 1.9.2?
Have you actually tried using rubyzip with 1.9.2? Seems to work fine for me:
>> RUBY_VERSION
#=> "1.9.2"
>> require 'zip/zip'
#=> true
>> Zip::ZipFile.foreach(File.expand_path("~/Downloads/Archive.zip")) { |f| p f }
#=> [bartxt, footxt]
bar.txt
foo.txt
I used rubyzip gem in Ruby 1.8.7 also. For Ruby 1.9.x you need to use version 0.9.5 or higher. Works without any problems.
I found zip it says it's compatible with 1.9.1 I don't think it would have any issues in 1.9.2

Why does 6.times.map work in ruby 1.8.7 but not 1.8.6

The following code snippet works fine in 1.8.7 on Mac OS X, but not in 1.8.6 on Ubuntu. Why? Is there a workaround?
Works in 1.8.7:
$ ruby --version
ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0]
ltredgate15:eegl leem$ irb
>> 6.times.map {'foo'}
=> ["foo", "foo", "foo", "foo", "foo", "foo"]
>>
But not in 1.8.6:
# ruby --version
ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-linux]
Ruby Enterprise Edition 20090610
# irb
irb(main):001:0> 6.times.map {'foo'}
LocalJumpError: no block given
from (irb):1:in `times'
from (irb):1
irb(main):002:0>
Why is there a difference? What's the workaround for 1.8.6?
In 1.8.7+ iterator methods like times return an enumerator if they are called without a block. In 1.8.6 you have to do
require 'enumerator'
6.enum_for(:times).map {...}
Or for this specific use case you could simply do (0...6).map {...}
In Ruby 1.9, the library was changed so functions that did iteration would return an Enumerator object if they were called without a block. A whole host of other language features were also changed, and it was widely known that compatibility would be broken between Ruby 1.8.x and Ruby 1.9 in the interests of improving the language as a whole. Most people didn't find this too distressing.
The Ruby development team decided that Ruby 1.8.7 should be a transition release adding some of the library features that Ruby 1.9 introduced. They took a lot of criticism for the decision, and many enterprise Ruby users remained (and many still remain) running Rails on Ruby 1.8.6, because they feel the changes introduced 1.8.7 are just too large, and too risky. But nevertheless, 1.8.7 remains, and having iteration functions return Enumerators is one of the features that was incorporated.
It is this migration feature that you're seeing in 1.8.7, which is not present in 1.8.6.
sepp2k's answer gives a good workaround. There's not much for me to add on that count.
Because 1.8.6 #times yields on the given block, while 1.8.7 returns an Enumerator object you can keep around and implements Enumerable.
Ruby 1.8.7 introduces many changes. If you want to use them in Ruby 1.8.6, simply
require 'backports'
That's it. This gives you many methods of 1.9.1 and the upcoming 1.9.2 as well, although it's possible to require 'backports/1.8.7' for just the changes of 1.8.7, or even just the backports you need, e.g. require 'backports/1.8.7/integer/times'

Resources