Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

11 Small Improvements for Ruby

DZone's Guide to

11 Small Improvements for Ruby

Are you a Ruby developer? Do you think the language could use some polishing? Then read on to see what one dev suggests.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

Kill 0 Octal Prefix

I'm limiting this list to backward compatible changes, but this is one exception. It technically breaks backward compatibility, but in reality, it's far more likely to quietly fix bugs than to introduce them.

Here's a quick quiz. What does this code print:

p 0123
p "0123".to_i
p Integer("0123")

Now check in actual Ruby and you'll see my point.

If your answer wasn't what you expected, then it proves my point. The whole damn thing is far more likely to be an accident than intentional behavior - especially in user input.

If you actually want octal - which nobody ever uses other than Unix file permissions - use 0o123.

Add Missing Hash Methods

Ruby has a bad habit of treating parts of a standard library as second class, and nowhere is it more mind boggling than with Hash, which might be the most commonly used object after String.

It only just got transform_values in 2.4, which was probably the most necessary one.

Some other methods which I remember needing a lot are:

  • Hash#compact
  • Hash#compact!
  • Hash#select_values
  • Hash#select_values!
  • Hash#reject_values
  • Hash#reject_values!

You can probably guess what they ought to do.

Hash#zip

Technically Hash#zip will call Enumerable#zip so it returns something, but that something is completely meaningless.

I needed it crazy often. With a = {x: 1, y: 2} and b = {y: 3, z: 4} to run a.zip(b) and get {x: [1, nil], y: [2,3], z: [nil, 4]}, which I can then map or transform_values to merge them in a meaningful way.

Current workaround of (a.keys|b.keys).map{|k| [k, [a[k], b[k]]]}.to_h works but good luck understanding this code if you run into it, so most people would probably just loop.

Enumerable#count_by

Here's a simple SQL:

SELECT author, COUNT(*) count FROM posts GROUP BY author;

Now let's try doing this in Ruby:

posts.count_by(&:author)

Well, there's nothing like it, so let's try to do it with existing API:

posts.group_by(&:author).map{|author, posts| [author, posts.size]}.to_h

For such a common operation having to do group_by / map / to_h feels real bad - and most people would just loop and +=  like we're coding in some JavaScript and not in a civilized language.

I'm not insisting on count_by  - there could be a different solution (maybe some kind of posts.map(&:author).to_counts_hash ).

Uri

It's a similar story of an API added back when the internet was young and we didn't know any better. By today's needs the API feels so bad, quite a few people literally use `curl...` , and a lot more use one of hundred replacement gems.

Just pick one of those gems, and make it the new official net/http. I doubt you can do worse than what's there now.

Again, I'm not blaming anyone, but it's time to move on. Python had urlliburllib2urllib3, and by now it's probably up to urllib42 or so.

Make bundler Chill Out About binding.pry

For better or worse bundler became the standard dependencies manager for Ruby and pry its standard debugger. But if you try to use require "pry"; binding.pry  somewhere in your bundle exec enabled app, it will LoadError: cannot load such file -- pry, so you either need to add pry  to every single Gemfile, or edit that, bundle install every time you need to debug anything, then undo that afterward.

I don't really care how that's done - by moving pry to standard library, by some unbundled_require "pry", or special casing pry, the current situation is just too silly.

Actually, Just Make binding.pry Work Without Any require

I have this ~/.rubyrc.rb:

begin
  require "pry"
rescue LoadError
end

Which I load with the RUBYOPT=-r/home/taw/.rubyrc.rb shell option.

It's such a nice quality of life improvement to type binding.pry instead of require "pry"; binding.pry, it really ought to be the default, whichever way that's implemented.

Pathname#glob

Pathname

suffers from being treated as second-class part of the stdlib. Check out this code for finding all big text files in 

path = Pathname("some/directory")

:

path.glob("*/*.txt").select{|file| file.size > 1000}

Sadly this API is missing.

In this case, I can use:

glob("#{path}/*/*.txt").map{|subpath| Pathname(subpath)}.select{|file| file.size > 1000}

This not only looks ugly, it would also fail if path contains any funny characters.

system Should to_s its Argument

If wait_time = 5 and uri = URI.parse("https://en.wikipedia.org/wiki/Fidget_Spinnerthen this code really ought to work:

system "wget", "-w", wait_time, uri

Instead, we need to do this:

system "wget", "-w", wait_time.to_s, uri.to_s

There's seriously no need for this silliness.

This is especially annoying with Pathname objects, which naturally are used as command line arguments all the time. Oh and at least for Pathnames it used to work in Ruby 1.8 before they removed Pathname#to_str, so it's not like I'm asking for anything crazy.

Ruby Object Notation

Serializing some data structures to send over to another program or same in a text file is a really useful feature, and it's surprising Ruby doesn't have such functionality yet.

So people use crazy things like:

  • Marshal - binary code, no guarantees of compatibility, no security, can't use outside Ruby.
  • YAML - there's no compatibility between every library's idea of what counts as "YAML," really horrible idea.
  • JSON - probably the best solution for now, but not human readable, no comments, dumb ban online final commas, and data loss on conversion.
  • JSON5 - fixes some of the problems with JSON, but still data loss on conversion.

What we really need is Ruby Object Notation. It would basically:

  • Have strict standard.
  • Have implementations in different languages.
  • With comments allowed, mandatory trailing commas before newline when generated, and other such sanity features.
  • Would use same to_rbon / RBON.parse interface.
  • And have some pretty printer.
  • Support all standard Ruby objects which can be supported safely - so it could include Set.new(...), Time.new(...), URI.parse(...) etc., even though it'd actually treat them as grammar and not evalthem directly.
  • Optionally allow apps to explicitly support own classes, and handle missing ones with exceptions.

This is an unproven concept and it should be a gem somewhere, not part of standard library, but I'm surprised it's not done yet.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
ruby ,web dev ,ruby classes ,methods

Published at DZone with permission of Tomasz Wegrzanowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}