`rescue` also uses the match operator, which I found useful in the past when wrapping libraries that didn't expose a useful exception type hierarchy. In particular I seem to recall that the "pg" gem would throw `PG::Error`, with the actual error details from the Postgres server only accessible in the `message`. I wrote a little library that allowed me to create "virtual" exception types that could match based on the message or the result of an arbitrary proc (if the underlying exception class offered a `code` or `reason` attribute, for instance).
For all its (sometimes deserved) reputation as a big and complex language, Ruby is actually pretty good at making sure that language features are made with the same underlying building blocks, and thus two things that look similar probably work similarly as well.
Wow I've been programming Ruby for 15 years and didn't know `rescue` uses a match operator.
It's annoying when a library raises only one exception type for every error and the only difference is in the message. Matching on the message right in the rescue expression would be tremendously useful instead of parsing the message and deciding how to deal with it inside the rescue body.
It's a decade old and less than 100 lines so it might be more useful as an example than a gem. The last time I needed to do something like this I just rewrote the parts I needed. The ecosystem is just generally better about exceptions than it used to be, too.
I feel like people got hung up on computational performance for a while. 5 to 10 years ago, a bunch of big startups hit the scale where they needed to intentionally work around Ruby's limitations. People seemed fixate on those performance issues while overlooking the business context - that hitting that scale means you are wildly successful and solving problems most startups never have the chance to solve.
It seems there's a swing back towards people considering raw productivity. Especially, at early stage or pre-product market fit.
Given that Rails is highly correlated with Ruby usage, it's simply hard to beat how much productivity and breadth that Rails gives you. Especially, with how much "grunt" work is easily handled by it.
I love trying new frameworks on side projects, but I always find myself coming back to Rails. There is inevitably some nuanced situation that the framework doesn't handle that would be ridiculously easy in Rails.
Ruby has also improved single-threaded performance and introduced a parallelism construct, so, while it is still far from fast, the basic performance and scaling situation has improved somewhat.
I love Ruby so much. Its syntax and flexibility are such a joy to work with. I hope it does have a resurgence in popularity. I've been using it my whole career, and fearing the day I have to switch to something new.
IMO Learning new languages is one of the best ways to be become a better programmer. You can carry the things you like about Ruby with you, and learn to think about things in new ways.
Productivity is a very sticky feature. IMO there's no language that I know of which effectively competes with Ruby in this category.
I don't even know if people realize that Ruby is the closest thing to an Aspect Oriented Programming experience in the wild, at least that I've seen. It's the secret sauce IMO.
Sounds like it was whether Python or Ruby is more popular back then, which was won by Python but then people still preferred Ruby because of its elegant language design and those voices seem finally heard around enough.
I even see Crystal (grammatically similar compiled language) gaining attention.
I do love Ruby syntax and used it on several occasions but it's typing system seems far from elegant compared to TypeScript in my opinion which doesn't want me to switch to Ruby at this point.
Pattern matching gets even more fun with the rightward-assignment syntax and additional conditionals. Here's a single example case from the UUID/GUID library I'm working on where the constructor method takes arbitrary positional arguments, many combinations of which may yield a valid UUID. This particular case matches the components of a Microsoft-style GUID so it can handle the endianness appropriately:
in [::Integer => data1, ::Integer => data2, ::Integer => data3, ::Array => data4] if (
data1.bit_length.<=(32) and data2.bit_length.<=(16) and data3.bit_length.<=(16) and (
data4.size.eql?(8) and data4.all?(&::Integer::method(:===)) and data4.max.bit_length.<=(8)
)
) then
Tangental, but this article deserves huge credit for having code containing the main point right at the top allowing the reader to derive the main point in < 20 seconds. I wish more written content was like this. Brilliant.
Ruby is a wonderful language, I just wish I wasn't stuck with 2.6 at work because all of the improvements especially in Ruby 3.0 are just wonderful. I keep wanting to branch out to other languages but keep coming back to Ruby as my bread and butter.
May I recommend to anyone facing similar issues and who may have at least some agency in dealing with the problem (can't assume you do, so forgive me in that case) the incredible work of Victor Shepelev with Ruby References: https://rubyreferences.github.io/rubychanges/evolution.html
Note that these are not copies of the NEWS.md typically released when minor and major versions of Ruby come out. Victor specifically spent time to write more descriptive notes of what each notable change occurred over time. It's an incredible resource and we're extremely lucky to have him in our community.
There's even a changelog for this meta-changelog, which makes my little Keep a Changelog heart sing, so you can see evolutions of this site over time as well: https://rubyreferences.github.io/rubychanges/
I moved us up from 2.4 to 2.7 last year trivially. Basically just changing the version base in our dockerfile. A few gems had to go up too, to support the kwargs changes. But like... 1 day of work.
I'm looking to go to 3.x soon too, after I get our EOL'd rails & lots of gems updated.
What features in particular are you looking at in Ruby 3.0? The type system stuff seems really cool, but I haven't been hearing much about it in the wild.
Unfortunately, the switch from 2.7 to 3.0 is likely to be more troublesome, due mostly to the change in handling keyword arguments (they're no longer treated as equivalent to a last positional argument that happens to be a hash, and you need to toss in '*' to convert from one convention to the other). This isn't hard to clean up if you have good test coverage... but one reason for not having done upgrades is not having that.
This is a convention we've already adopted but we haven't had the chance to do a clean sweep of the rest of the codebase unfortunately. You're right though, it's one of our biggest hurdles to upgrading.
1. Ask management to write in full letters and sign a paper basically saying "I acknowledge that not maintaining software up-to-date is a security risk and opens the company to substantial financial damage"
2. Hire a Pwn-As-A-Service to extract data from your company from the outside
3. Tell management "I told you so"
You didn't read this here
More seriously, I think the best you can do is not expect to find happiness in a setting that you don't fully control (ie your job), accept that, and look for joy elsewhere. It might be another job, it might be another hobby. Work fewer hours.
I appreciate your advice. I regularly voice concerns about security and let management make decisions. From my perspective I've done my due diligence and my hands are clean.
I've stopped looking for "joy" from work. Mainly focused on finding joy with my children and other hobbies, while working as little as possible. The job is the means to that end now, rather than the end itself. They continue to pay me and it supports the things I want to do outside of work so I am "happy" enough. The work itself is also moderately interesting and a decent challenge from a domain perspective, so it's not all bad.
> I appreciate your advice. I regularly voice concerns about security and let management make decisions. From my perspective I've done my due diligence and my hands are clean.
I feel like the best you can do unironically is the first step I outlined: ask management to formally recognize that despite your warnings, they choose cost reduction. Have a trace of that, not just in case of problems, but for yourself: it's a great reminder that as long as you aren't in charge, it can't be your problem.
> More seriously, I think the best you can do is not expect to find happiness in a setting that you don't fully control (ie your job), accept that, and look for joy elsewhere. It might be another job, it might be another hobby. Work fewer hours.
That's how I upgraded from Ruby 2.4 / Rails 5.0 to Ruby 3.2 / Rails 7.0
I do some...let's say non-trivial reporting and there's so many new features in modern postgres that would make my life so much easier, whenever I accidentally end up on a documentation page for a newer version with something I want but can't use I die a little inside.
For us, it's just incredibly hard for us to justify the effort _and_ risk. Particularly, the risk.
Most of us have been burned by a major upgrade (either here or elsewhere) where things have broken in non-obvious, hard to test ways. This is particularly problematic in the dependency chain where support is limited. Breaking issues may turn us into unexpected maintainers of key libraries.
I'm not saying that it's the case for you, but a lot of time I've seen that this actually means "engineers aren't able/willing to talk with management to justify the value of maintainance and make a concrete proposal of how to go about it".
But then again, some other times management just sucks.
Naah, most of the times its that management doesn't want to pay the migration cost. They will keep using a system till it stops producing value, or till adding new things is profitable. They can't give a damn about day to day or what's cool/elegant/maintainable because they don't interact with the innards, ever. These decisions are made with a single equation, if migration cost > new feature cost + maintenance, they don't want to migrate.
Edit: The maintenance isn't a long term view btw. They are not looking at a 5-6 year window where the migration cost slowly pays off. They are worried about optimizing the next quarter.
We upgraded from Rails 4 to 5 last year (lol - it was EOL before our migration was even done), but that was before our CTO left to be a CEO somewhere else and there's been no attempt to find a replacement. Now the executive is obsessed with piling on features with little regard to technical debt or maintenance. It's going about as well as you'd think.
I’ve been lucky to have been working in smaller companies where I’ve been able to communicate the issues with that.
I can’t imagine working in a place that doesn’t want to listen to its engineers or experts. (The same works the other way around too: engineers not willing to understand the business context)
It also likely means no one in management understands building software.
It's tiring when every line of code needs a value proposition and a clear use case. Some things don't take much work to improve/maintain but are next to impossible to slap a $ value on.
even = ->(x) { x % 2 == 0 }
even === 4 # true
even === 5 # false
That's cool. No need for a is_even or is_odd gem.
I really do like that ability to do a case statement though. I cannot think of a situation where I need it now but I could definitely see it being handy instead of having to do if..else if
Alternatively you can call Integer#even? or Integer#odd? directly:
4.even? # true
5.even? # false
Or if you really want to use ===, it is possible with the slightly sillier (imho) use of Symbol#to_proc:
case some_num
when :zero?.to_proc then "zero"
when :even?.to_proc then "even"
when :odd?.to_proc then "odd"
end
I say sillier, because even though it is very explicit, the use of #to_proc, by name, feels just a tiny bit less awesome than its usual use via the & operator, e.g. array_of_nums.map(&:odd?). It would be really cool to be able to write:
case some_num
when &:zero? then "zero"
when &:even? then "even"
when &:odd? then "odd"
end
However I say this with no grasp of just how complex that could be to implement, or other implications for the language's grammar.
The only official Ruby documentation exists at https://docs.ruby-lang.org/en/ and is generated via the Ruby source code itself.
The excellent Colby Swandale has been working on a new open source alternative for Ruby documentation considering the design shortcomings of the default generated API documentation with https://rubyapi.org which I highly recommend.
Sadly there are a lot of third-party operated documentation sites that exist likely because of how hard-to-discover the official API documentation site is. It's something the community would like to address, but given the amount of link equity a lot of these older and often unmaintained doc sites have acquired it's fairly tricky.
That's the Core reference guide. You might want the book "Programming Ruby"[1], which is linked on the documentation landing page[2]. Alternatively, for a quick overview, I recommend Learn X in Y Minutes[3].
The Ruby docs are, generally, the weak spot of the language (moreso than even being slow, on which there is also more progress).
Fortunately (while there was a long time where this was not the case), there is a reasonably current version of the “Pickaxe” book, which is the closest existing thing to the documentation Ruby should have: https://pragprog.com/titles/ruby5/programming-ruby-3-2-5th-e...
Did you expect something else? The way I get to that link is to go to ruby-doc.org and click on "Complete API docs for Ruby 3.2.2". API docs is what I expected.
Yeah, ruby is a weird one. They have excellent documentation of the API, but all the language documentation is wildly out of date. The main web site links to an old version of "Programming Ruby" which covers 1.6. Has nothing changed about the language in 20 years?
The "C" family has one of the worst case/switch statements out there. When Java et. al. cloned it, they should have provided an alternative. Perhaps keep the old style for backward compatibility, but provide a new one with different key-words to avoid confusion. Example:
select(a) {
when 1,2,3 {...}
when 4 {...}
when 5,6 {...}
otherwise {...}
}
Agreed that the C-style switch is crammed with misfeatures. But Java does now let you write this (haven't tried compiling this, may contain small errors):
static String describe(int change) {
return switch (change) {
case 0 -> "unchanged";
case -1, 1 -> "mostly unchanged";
case Integer i && i > 0 -> "increased";
case Integer i -> "decreased";
}
}
No fallthrough, can mix constants and conditions (with syntax some will find clunky), and is an expression.
It's cool that Ruby has a === operator, but the fact that it is not a more strict version of == has caused me trouble in the past.
In TypeScript === is more strict, and when I go back and forth between Ruby and TS I have made a mistake in the past by putting a === when I intended a strict equality check.
I wonder if there is a linting setting where you can prevent your code base from ever using it.
Very similar to Ada'a switch statement, which utilities "case .. when". I don't think Ada can utilize regex in their switch statements but they can do neat things like convert a string into its corresponding enumeration.
The switch statement in Ruby uses the `===` method under the hood. `===` does not mean "equals" like it does in Javascript. Instead, `a === b` means, does `b` belong in the set of `a`.
This might confuse a reader. The Object === cases are successful, because the Class-objects Integer and String are Objects. In ruby everything is an Object.
`Integer ===` (and more generally if `===` is implemented as a class method) checks if the right hand side is of the class Integer. The object Integer is not, it's an object of class Class.
Sorta? Like you're right, there's not compiled machine code that does a jump. But Ruby's case statement has nice semantics to make clear code out of what could be a complex if/elsif/else chain. The use of the === function is powerful, and lets the reader of the code focus on the condition, not the function call to check it.
if (patternA.match(input))
elsif (patternB.match(input))
else
end
vs
case input
when patternA
when patternB
else
end
And since it uses ===, you can define that on your own classes too. Hypothetically for instance, you can make fairly complex policy type classes to make reusable boolean statements.
case current_user
when NotValidatedEmail
when NotCompletedOnboarding
when SomethingElse
else
end
and then you can use those elsewhere in the code by leveraging the more global use of ===
> there's not compiled machine code that does a jump
It's more of an implementation detail, but the Ruby VM actually does a jump if all the cases are "keyable"(typically integers, strings, symbols, etc). It does a hash lookup and jump to the returning address.
That's cool, thanks for the code pointer. I knew I was probably wrong in the strictest sense, especially with all the work that's gone into the various approaches to JIT and performance work in Ruby.
I should go read some of the interpreter code at some point - fun work going on there.
For all its (sometimes deserved) reputation as a big and complex language, Ruby is actually pretty good at making sure that language features are made with the same underlying building blocks, and thus two things that look similar probably work similarly as well.