There are ways! Ruby documentation is sparse, so you'll need to read the C documentation (manpages).
In this case, the best bet is "nonblocking sockets". You can create one using `socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)`. When you try to connect, you'll get the error `EINPROGRESS`. Then you can `IO::select()` the socket for write and call `getsockopt()` to read the result.
Afterwards, before every read, call `IO::select()`.
(Disclaimer: I haven't tested this in Ruby specifically.)
Other places to look for docs:
recv_nonblock -- https://ruby-doc.org/stdlib-2.5.1/libdoc/socket/rdoc/BasicSocket.html#recv_nonblock-method -- gives an error right away when you call it on a socket that has no data available. Use it with `IO.select()` -- there's an example at https://www.rubydoc.info/stdlib/core/IO.select
To set timeouts during close, look into `SO_LINGER` (see setsockopt -- https://ruby-doc.org/stdlib-2.5.1/libdoc/socket/rdoc/BasicSocket.html#method-i-setsockopt).
And since Ruby's own docs are so sparse, you'll want to refer to C manpages -- `man 2 socket`, `man 2 connect`, etc.
Maybe it'll feel like a lot of effort for no gain. Ruby, like Python, was designed to feel simplistic. But if you ever want your socket code to run as a part of something bigger -- say, a web server -- you won't regret learning how to `select()`.