For followup post with some solutions, check this.
A while ago I wrote
a list of big ideas for Ruby. But there's also a lot of small things it could do.
Kill 0 octal prefix
I'm limiting this list to backwards compatible changes, but this is one exception. It
technically breaks backwards 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 behaviour - 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 standard library as second class, and nowhere it's 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 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
query parameters access
Ruby is an old language, and it added a bunch of networking related APIs back then internet was young. I don't blame anyone for these APIs not being very good, but by now they really ought to be fixed or replaced.
One mindbogglingly missing feature is access to query parameters in
URI
objects to extract or modify them. The library treats the whole query as opaque string with no structure, and I guess expects people to use regular expressions and manual
URI.encode
/
URI.decode
.
There are gems like
Addressable::URI
that provide necessary functionality, and
URI
needs to either adapt or get replaced.
Replace net/http
It's similar story of API added back when 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
urllib
,
urllib2
,
urllib3
, 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 afterwards.
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
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 can use:
glob("#{path}/*/*.txt").map{|subpath| Pathname(subpath)}.select{|file| file.size > 1000}
which 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_Spinner")
, then 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
Pathname
s 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 best solution now, but not human readable, no comments, dumb ban on line final commas, and data loss on conversion
JSON5
- fixes some of 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 eval
them directly.
- Optionally allow apps to explicitly support own classes, and handle missing ones with excepions.
This is unproved concept and it should be gem somewhere, not part of standard library, but I'm surprised it's not done yet.