Very clear APIs and syntax(with the possible exception of blocks which can be weird because they aren't quite functions), and tons of raw metaprogramming powers.
You can argue it sacrifices too much of the other things to deliver on these things, but it's hard to argue against it doing well at what it optimizes for!
Pieter Levels writes his startups in PHP and hasn't had a performance bottleneck so far. For most applications, the performance of the language won't be an issue. I personally wouldn't pick PHP for any of my own projects, but Ruby I'd pick any day.
Nitpick: technically `Gem::Version` is part of `rubygems`, and while `rubygems` is (typically) packaged with Ruby, it's actually entirely optional, so much so that `rubygems` actually monkeypatches† Ruby core's `Kernel` (notably `require`) to inject gem functionality.
MRuby has none of it, and CRuby has a `--disable-rubygems` configure flag.
Back in 1.8 days, you even had to manually require `rubygems`!
† https://github.com/ruby/rubygems/tree/4e4d2b32353c8ded870c14...
$ txr -i version.tl
1> (equal (new (app-ver "1.2.003")) (new (app-ver "1.2.3")))
t
2> (equal (new (app-ver "1.2.003")) (new (app-ver "1.2.4")))
nil
3> (less (new (app-ver "1.2")) (new (app-ver "1.2.3")))
t
4> (greater (new (app-ver "1.2")) (new (app-ver "1.2.3")))
nil
5> (tostringp (new (app-ver "1.2.3.4")))
"1.2.3.4"
6> (tostring (new (app-ver "1.2.3.4")))
"#S(app-ver str \"1.2.3.4\" vec (1 2 3 4))"
Code: (defstruct (app-ver str) ()
str
vec
(:postinit (me)
(set me.vec (flow me.str (spl ".") (mapcar int-str))))
(:method equal (me) me.vec)
(:method print (me stream pretty-p)
(if pretty-p (put-string `@{me.vec "."}` stream) :)))I wouldn't be as much in love with programming, if it wasn't for Ruby. And although I use many other programming languages these days, Ruby will forever have a special place in my heart.
Having done mostly TypeScript and Elixir lately, I had forgotten things could be so succinct yet so clear. The combo of modern (to me) Ruby's lambda syntax (in the .map call), parentheses-less function calls, the fact that arrays implement <=> by comparing each item in order, that there's an overloadable compare operator at all, having multiple value assignments in one go... It all really adds up!
In any other language I can think of real quick (TS, Elixir, C#, Python, PHP, Go) a fair number of these parts would be substantially more wordy or syntaxy at little immediately obvious benefit. Like, this class is super concise but it doesn't trade away any readability at all.
Having learned Ruby before Rails became commonplace, with its love for things that automagically work (until they don't), I had kinda grown to dislike it. But had forgotten how core Ruby is just an excellent programming language, regardless of what I think of the Rails ecosystem.
I'd love to see a lot more writing and advocacy around Ruby, and not Ruby/Rails. I don't use Ruby/Rails! I use Ruby. And I suspect a lot of folks who have left Ruby behind over the years might not realize some (many?) of their gripes are not with Ruby in general, but Rails in particular.
rubygems.org also has decided to, rather than fix on existing problems, eliminate all former maintainers and instead put in Hiroshi Shibata as the solo lead - the same guy who keeps on writing on different github issue trackers how he does not have time to handle any issue requests for low-used projects. Wowsers.
Other than that, how was the play, Mrs. Lincoln?
Also, add readability and maintainability to that list, and scaling to a large codebase. And good debugger support.
difflib is probably my favorite one to cite.
Go see for yourself: https://docs.python.org/3/library/index.html
The benefit there is that their quality, security, completeness and documentation are all great!
man, people are still parroting decade old, incorrect talking points I see.
Is ruby as performant as C, probably not, although, actually, in some cases, it outperforms C -> https://railsatscale.com/2023-08-29-ruby-outperforms-c/
One of the largest ecommerce apps in the world runs ruby, shopify. Ruby now has a JIT, there has been insane effort put into making ruby faster.
Metaprogramming is Lisp's canonical super power. Ruby is going to win out on tasks where it has built in syntax, like matching regular expressions.
But once you get to metaprogramming Lisp macros are going to give Ruby a run for its money.
I will say one of the under appreciated aspects of Ruby is the consistency of its semantics, where everything is message passing very much like Smalltalk.
That's the reason why we can't have nice things.
* default libraries (these are maintained by the Ruby core team, delivered with Ruby, and upgraded only as part of Ruby version upgrades.)
* default gems (these are maintained by the Ruby core team, delivered with Ruby, not removable, can be required directly just like default libraries, but can be updated separately from Ruby version upgrades.)
* bundled gems (these are gems that are delivered and installed with Ruby, but which can be upgraded separately or removed.)
Rubygems is a default gem. [0] It used to not be part of the standard library, but it has been since Ruby 1.9, released in 2007.
[0] see, https://stdgems.org/
it's so typical of ruby culture "haha, what if I do this silly thing" and then that gets shipped to production
Bit of a clarification after reading the article - that article demonstrates a pure-Ruby implementation [0] outperforming a C extension [1], which is not what I had originally expected when first clicking on the link.
[0]: https://github.com/tenderlove/tinygql
[1]: https://github.com/rmosolgo/graphql-ruby/tree/master/graphql...
I love me some Clojure, and its macros aren’t bad or anything, but I feel Scheme (and Racket) has the most elegant metaprogramming.
1> (hash)
#H(())
2> *1
#H(())
3> (set [*1 (new (app-ver "1.2.3.0004"))] 'mapped)
mapped
4> *1
#H(() (#S(app-ver str "1.2.3.0004" vec (1 2 3 4)) mapped))
5> [*1 (new (app-ver "1.2.3.4"))]
mapped
6> [*1 (new (app-ver "1.2.03.4"))]
mapped
7> [*1 (new (app-ver "1.2.02.4"))]
nilAdmittedly, I have never done a lot with Ruby, but I have done some Rails and I tried for a few months to use it for simple “shell” scripts, and the language never felt beautiful or elegant to me.
Admittedly, I come from a strong functional programming background, so it is entirely possible that my brain sees “it’s not really a ‘functional’ and therefore I don’t like it”, but I do like Rust (even when I write it very imperatively) and I even kind of like modern Java.
Dunno, I will admit that I am weird, my favorite language is Clojure and I know that that one is an acquired taste :).
Why thank you! :D
from dataclasses import dataclass
@dataclass(order=True)
class AppVersion:
major: int = 0
minor: int = 0
patch: int = 0
@classmethod
def from_string(cls, version_string):
parts = map(int, str(version_string).split('.'))
return cls(*parts)
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
All languages steal from each other, so on a long enough time scale, it makes sense they kinda converge.I work at an enterprise running Rails (you've heard of us, if you're in North America). Discussions about rails abound, and my opinion has distilled into "I love writing Ruby, I loathe reading it"
Ruby, and Ruby on Rails is a treasure trove of little handy bits you can use if you just know where to look. I really miss some aspects of ruby (I just don't have a chance to use it these days).
Glad to see it's getting love on here recently.
Some of those languages would have you deal with the problem of allocating multiple arrays in the heap just to compare three numbers. Or give you tools to outlaw passing invalid strings to AppVersion.new (quick: what is the comparison between AppVersions "foo" and "bar"?).
Plus you have very few tools to ensure code remains beautiful. I've worked with Ruby for close to two decades, almost nothing in the real world looks that clean. Take a look at the Gem::Version#<=> implementation that the article talks about: https://github.com/ruby/ruby/blob/master/lib/rubygems/versio...
from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class AppVersion:
major: int = 0
minor: int = 0
patch: int = 0
@classmethod
def from_string(cls, version_string: str):
return cls(*[int(x) for x in version_string.split(".")])
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
Before dataclasses you could've used namedtuples, at a loss of attribute typing and default initializer: from collections import namedtuple
class AppVersion(namedtuple("AppVersion", "major minor patch")):
@classmethod
def from_string(cls, version_string: str):
parts = [int(x) for x in version_string.split(".")] + [0, 0]
return cls(*parts[:3])
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}" record AppVersion(int major, int minor, int patch) implements Comparable<AppVersion> {
public static AppVersion of(String version) {
var array = Arrays.copyOf(Arrays.stream(version.split("\\.")).mapToInt(Integer::parseInt).toArray(), 3);
return new AppVersion(array[0], array[1], array[2]);
}
public int compareTo(AppVersion other) {
return Comparator.comparingInt(AppVersion::major)
.thenComparingInt(AppVersion::minor)
.thenComparingInt(AppVersion::patch)
.compare(this, other);
}
public String toString() {
return "%d.%d.%d".formatted(major, minor, patch);
}
}I like when parens/brackets are reliable wrappers for chunks of code. Like being able to ‘vi{‘ in vim to select a function body. Or ‘%’ to jump to the matching paren.
Do you find the language more readable without it? Less visual noise?
Scala would like to have a terse say about this... :-)
That's not a nitpick, that's paraphrasing ;)
> It used to not be part of the standard library, but it has been since Ruby 1.9, released in 2007.
That's the mention of 1.8 I made, but it's a bit more complex: it still can be excluded, in two ways:
- at build time via `configure` (and then it's not even there)
- at run time via `--disable-gems`
The interaction between rubygems and ruby core is surprisingly small.
> Rubygems is a default gem
It is not! See `lib/ruby/gems/3.3.0/specifications/default` in any Ruby install, or in source the absence of _any_ gemspec for `rubygems` while there is for bundler[0].
Instead it's, as you mentioned, a default library.
The very principle of gems is that you can have multiple versions of a gem separately installed (to the point of allowing only one to be visible at any one time, activated by `rubygems`-provided `Kernel.gem`). The implementation of that is done by `rubygems` itself so if it were a gem, one would not be able to activate `rubygems` without `rubygems` itself...
This is also why it can be special-case upgraded only via the very special `gem update --system`, which downloads a "gem" named `rubygems-update` (not `rubygems`); scare quotes because it's using the gem format and infrastructure mostly as a delivery mechanism, not by being an _actual_ gem[1] in the traditional sense (well it is a gem, but a gem of an updater, not of `rubygems` itself).
When updated, the new copy of rubygems is installed in `site_ruby`, because the load path is the only mechanism available to define location priority (`ruby --disable-gems -e 'p $LOAD_PATH'`).
Fun fact: the only thing that "prevents" a file present it `${PREFIX}/lib/ruby/3.4.0/rubygems` to not be `load`ed or `require`d is merely that new code in `${PREFIX}/lib/ruby/site_ruby/3.4.0` shall not make reference to it, but it's all perfectly visible otherwise.
docker run --rm -it ruby:3.4 /bin/bash
gem update --system
ls -ld /usr/local/lib/ruby/3.4.0/rubygems /usr/local/lib/ruby/site_ruby/3.4.0/rubygems
echo 'p __dir__' > /usr/local/lib/ruby/3.4.0/rubygems/foo.rb
ruby -e 'p $LOAD_PATH; require "rubygems/foo"'
[0]: https://github.com/ruby/ruby/blob/v3_4_7/lib/bundler/bundler...[1]: https://github.com/ruby/rubygems/blob/v3.7.2/hide_lib_for_up...
https://github.com/ruby/rubygems/blob/v3.1.6/lib/ubygems.rb
https://github.com/ruby/rubygems/commit/8933115bff09402f6baa...
https://github.com/ruby/rubygems/commit/7ac54d5bb3411233b405...
Versions numbers can go to 10!?!
Right. I think I found it on stackoverflow.
The question is: why does the official documentation not mention this, along with guides?
Answer: because documentation is something the ruby core team does not want to think about. It is using scary language after all: English. The bane of most japanese developers. Plus, it is well-documented in Japanese already ... :>
raise "check if monkeypatch in #{__FILE__} is still needed" if Gem::Version.new(Rails.version) >= Gem::Version.new("8.0.0")
This will blow up immediately when the gem gets upgraded, so we can see if we still need it, instead of it laying around in wait to cause a subtle bug in the future....There are presumably many other ways to solve that problem, but still.
parts = version_string.to_s.split(”.”).map(&:to_i)
Is it to_string? Isn't version_string already a string?Here's a screenshot from inside FreeCAD:
https://f.toi.sh/rubygem-screenshot.png
A nice manifold solid:
https://f.toi.sh/rubygem.3mf / https://f.toi.sh/rubygem.stl
A terrifying non-manifold FreeCAD mess: (requires surface WB)
Maybe I am just stupid when it comes to reading Ruby/Rails or maybe that codebase was uniquely awful, but it was ~impossible to figure out where things were defined or how data moved through the system. A huge ball of mutable state that was defined at runtime.
When people say "I love writing Ruby" what I hear is "I love writing greenfield Ruby". Everybody loves writing greenfield code! The difference between greenfield and brownfield Ruby is stark, in my experience.
And to be clear I do not hate Ruby. It got me into the industry, it taught me a lot, it just optimizes for a set of values that I don't happen to share anymore, which is fine.
You find the same thing with JS to an even higher degree, but there's always 10 options in NPM and they all need to be updated every year otherwise the other 20+ packages you depend on can't be upgraded. There's a stark contrast in maintenance overhead and DX between frontend and server side.
Even the rails JS libraries are very stable. Hotwire's Stimulus hasn't had a release since 2023 and it always works just fine. https://github.com/hotwired/stimulus/releases
@functools.total_ordering
class AppVersion:
def __init__(self, version_string):
parts = [int(x) for x in str(version_string).split('.')]
self.major, self.minor, self.patch = parts[0] or 0, parts[1] or 0, parts[2] or 0
def __lt__(self, other):
return [self.major, self.minor, self.patch] < [other.major, other.minor, other.patch]
def __eq__(self, other):
return [self.major, self.minor, self.patch] == [other.major, other.minor, other.patch]
def __str__(self):
return f'{self.major}.{self.minor}.{self.patch}' >>> from packaging.version import Version
>>> Version("1.2.3") > Version("1.2.2")
True
>>> Version("2.0") > Version("1.2.2")
TrueI think it's a safety measure in case the argument passed in is not a string, but can be turned into a string. Safe to assume that calling "to_s" on a string just returns the string.
So, doing the same thing with Python would be less like bundling requests with python and more like moving http into a wheel that was installed with python but could be upgraded separately.
It's also a quite bad practice to my eye.
I don’t feel strongly about it, but you gotta admit that this is remarkably easy on the eyes yet also easy to follow:
parts = version_string.to_s.split(”.”).map(&:to_i)
The Elixir equivalent, likely a series of pipes, would be just as easy to follow but substantially more to read, more symbols to parse etc. I don’t feel like this here line of Ruby makes any sacrifices in understandability compared to this Elixir port we’re both imagining.Good point about grepping though.
And yes.. without ecosystem/libraries, everybody’s just creating their own thing over and over again and there’s not a coherent way of thinking
The Joy of Ruby
I strongly appreciate how much decision fatigue Rails avoids by just offering you so much batteries-included stuff. I tried getting into Django and immediately spun out fretting over what ORM or migration manager or caching system to use. (One of my coworkers who is a huge Django says I'm nuts and that Django offers those things too, so I may be misremembering.) Rails being as opinionated as it is saves so much thinking effort along those lines.
I think both of those facets make it extremely appealing to, as you say, anyone greenfielding code, and are the exact things that make it an absolute trash fire to maintain anything of appreciable size.
We're constantly fielding incidents due to something being undefined or unexpected nils or other basic typecheck failures.
> it just optimizes for a set of values that I don't happen to share anymore
That is a lovely way to put it, I'm gonna steal that
By the way, the generated identifiers are more a rails thing than a ruby thing.
RBS is a thing; we use it extensively.
> Good luck finding where the `process` function is called from
I don't use it like that but I seem to recall RBS itself has a query mechanism and can answer these kind of questions.
I think you're probably right. But fwiw, as a non-Rubyist who values good style and is recently working in a one-off Ruby codebase, I've found it easy to use LLMs to write Ruby code that is idiomatic and leverages built-ins well. I use ChatGPT 5 Thinking for this, and I don't let it generate any code that I use directly. I ask it about ways to do things, including stuff built-in to the stdlib, and sometimes have it generate 3 or 4 implementations. Then I compare them, consult the language docs or stdlib API docs or the pickaxe book, and choose what seems the most stylish or composable. I then write it out by hand, bearing in mind what I just learned, and see what Rubocop has to say about its style.
I wouldn't say LLMs have been essential in this, but they've been pretty convenient. Because Ruby has relatively a lot of special syntax, it's also nice to be able to inquire directly about the meaning of some syntax in a bit of generated code-- especially when it involves characters that are (for some reason) ungoogleable, like an ampersand.
I think people who use LLMs to generate code and people that embrace agentic coding AIs and "vibe coding" will absolutely fall into the pattern you describe. But RTFMers and developers who care about craftsmanship will probably use LLMs as another discovery mechanisms for the stdlib, popular Gems, and popular style conventions.
Not _exactly_ the same cut, but might be good enough for you?
The Ruby example should be compared to the implementation of data classes. The Ruby code shows how cleanly the code for parsing, comparing and printing a version string can be. We would need to see the code underlying the data classes implementation to make a meaningful comparison.
@major, @minor, @patch = parts[0] || 0, parts[1] || 0, parts[2] || 0
self.major, self.minor, self.patch = parts[0] or 0, parts[1] or 0, parts[2] or 0
The Ruby case is intended to handle strings like "1", "1.2", and "1.2.3".The Python code will throw an IndexError as written. Which is why I did this in the namedtuple example:
parts = [int(x) for x in version_string.split(".")] + [0, 0]
That ensures you'll have at least three parts so you can then: self.major, self.minor, self.patch = parts[:3]I'd use `semver` from PyPI and whatever the equivalent Gem is on the Ruby side in most cases.
But once you get used to it: - you don’t really notice the parens anymore (definitely not the trailing ones anyway) - you get the advantages of structural editing. So many nice shortcuts for moving stuff around. After working in clojure, typing typescript feels so clunky. How do you focus the next argument? Jump to the enclosing function scope? Yank the body of the closure? You just have to actual navigate to those things and select them, so uncouth!!
def bump_minor
self.class.new(major, minor + 1, patch)
end
(although I'm not sure why it would be useful in that particular case, it's just an example of how you can build new objects out of existing ones without having to mutate them)See the commit that made it complex: https://github.com/ruby/ruby/commit/9b49ba5a68486e42afd83db4...
It claims 20-50% speedups in some cases.
There's churn that comes with that. Ruby will have code that is ever changing to gain 5%, 10% performance every now and then. You gotta put that on balance: in a language like Go this method would've been ugly from the start but no one would've needed to touch it in 100 years.
Huh?
[major, minor, patch] =
version_string
|> String.split(".")
|> Enum.map(&String.to_integer/1)
I don't know that I'd call this "substantially more" of anything. if x.is_a? Array
x = x.first
Or something like that. Could be one line too: x = x.first if x.is_a? Array> You can search for "def process"
That tells you where it is defined, not where it is called from.
Who is "we"? The Ruby projects I use are Asciidoctor and Gitlab and neither of them use it.
Also putting types in a totally separate file is an abysmal UX.
You can use Crystal which is faster that Go
def <=>(other)
[major, minor, patch] <=> [other.major, other.minor, other.patch]
end
vs: def __lt__(self, other):
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
Then use the `total_ordering` decorator to provide the remaining rich comparison methods.That said, it's a little annoying Python didn't keep __cmp__ around since there's no direct replacement that's just as succinct and what I did above is a slight fib: you still may need to add __eq__() as well.
Regardless of how slow the language is, the 90/10 rule applies: 90% of the time is spent in 10% of the code. Optimize that 10%! Making the rest of the code faster isn't worth the code quality cost.
vs
.map(&:to_i)
To me the latter is no less clear so yeah, substantially more.
For context I write Elixir once a week and Ruby once a decade. In particular in this case elixir’s (and erlang’s) arity disambiguation (the slash) seems like unhelpful noise. Enum.map only accepts single-arity functions so it seems weird to me that the language couldn't figure that out for me.
eq: If true (the default), an __eq__() method will be generated. This method compares the class as if it were a tuple of its fields, in order. Both instances in the comparison must be of the identical type.
Too many hashmaps being used where you should just use a class?
Even if you implement an "interface" (duck typing) with `respond_to?(:to_app_version)` you still can't be sure that the return type of this `:to_app_version` is actually a string you can call `split()` on.
$dayjob; does it really matter who as long as it's an example of it being useful to someone?
> putting types in a totally separate file is an abysmal UX.
Is it though? I much prefer
def frobz(fizbazbang)
# ... the code
to def frobz(Hash<Symbol | String | Integer, Array<String | Integer>> fizbazbang) -> Array<Hash<String | Integer, String>> fizbazbang
# ... the code
It's the job of the code editor to get that information surfaced to me at the proper place (e.g virtual content like vim inlay hints, hover tooltips, those vscode embeds or whatever they are called). If I need to jump quickly I have projections† set up.Having the types inline isn't that useful anyway as it gives yo only the signature but gives you no info of whatever type intermediate things are without much thinking.
And interesting things happen when you have separate `.rbs` files: I started to develop some things type first then progressively fill the implementation in.
† Well known because of https://github.com/tpope/vim-projectionist but projections are not limited to vim.
While we're here, worth highlighting `cmp_to_key` as well for `sorted` etc. calls.
> it's a little annoying Python didn't keep __cmp__ around since there's no direct replacement that's just as succinct
The rationale offered at the time (https://docs.python.org/3/whatsnew/3.0.html) was admittedly weak, but at least this way there isn't confusion over what happens if you try to use both ways (because one of them just isn't a way any more).
Caching is also vastly underutilized, most apps are read-heavy and could serve a significant portion of their requests from some form of caching.
> When there are performance issues, 95% of the times they come from the database, not the language.
Eh, statements like these are always too hand wavy. Resource usage has to do with performance, the DB has no fault in it but the runtime does.
Having worked with Rails a ton there’s a very large overhead. Most apps would see a significant speed up if rewritten in a faster language and framework, with no changes to the DB whatsoever. The amount of memory and CPU expended to the app servers is always significant, often outweighing the DB.
DHH used to say that it didn't matter if Rails was slow because the database was I/O bound anyway. But that was a long time ago. Times have changed considerably. Most especially because DHH now promotes using SQLite, which completely takes the worst I/O offender right out of the picture. Nowadays the language (when it is as slow as Ruby) is most likely to be the problem.
Unless you really need to scale for a ton of users, you don't have to go crazy to get decent performances out of rails/ruby. How many requests/sec are we even talking about here?
These days the main issue why web apps are slow or fragile is because they are huge React apps that do way too much in the browser than they need too. The response time from the server is rarely the issue unless you're doing heavy analytics. High end React shops do some crazy optimization to try to compensate for this fact. Linear is the only one I've seen do it well with JS.
In theory the language is slow, in practice it doesn't really matter because the db is much slower unless you're github or twitter and you really need to scale.
When you choose ruby, you trade faster dev time for slower runtime. I am OK with this trade-off 99% of the time. My dev time costs way more than a couple ms lost on my production server.
> DHH used to say that it didn't matter if Rails was slow because the database was I/O bound anyway. But that was a long time ago. ... Nowadays the language (when it is as slow as Ruby) is most likely to be the problem.
Nowadays CPU speeds, available RAM, and network speeds dwarf what was top-of-the-line "a long time ago," making the trope of "Ruby is too slow" much less believable "nowadays."
Ruby is a beautiful language, but that does not translate to efficient use of dev time. Ruby is not a language that you can quickly write usable code in. Some other languages are clearly more productive. You can create works of art, though, which is still pretty appealing. Ruby does have a place in this world.
It was, again, DHH/Rails that used to make the claim about developer time — premised on Rails eliminating all the so-called "situps" that other frameworks imposed on developers. It is very true that when you eliminate a bunch of steps you can move a lot faster. But:
1. Everyone and their bother have created Rails clones in other languages that also eliminate the same "situps", negating any uniqueness Rails once offered on that front.
2. It turns out those "situps", while a dog in early development, actually speed development up down the road. If you are churning out a prototype to demonstrate an idea that will be thrown away after, its a pretty good tradeoff to ignore what you can, but things become far less clear cut when a program finds longevity.
Nowadays the language (when it is as slow as Ruby)
is most likely to be the problem.
And now state: "Too slow" is a mischaracterization. Ruby was never
too slow, only comparatively slow.
In response to my identifying the "Ruby is too slow" trope. Furthermore, when you assert: Now, if you could eliminate the time spent in Ruby,
you'd see significant percentage increases in performance.
This implies a performance issue within Ruby when, in fact, the narrative has now been shifted into execution percentage allocations. For example, if an overall execution flow takes 1 millisecond and time within the Ruby runtime accounts for 600 microseconds, then the above would be true.One way to describe your position is disingenuous.
Yes, that's right. If you have performance problems then Ruby taking up the large majority percentage of the execution allocation means that Ruby is the problem.
But your incorrect assumption is that you will have performance problems in the first place. Neither Ruby nor databases are that slow.
You must have done that stupid HN thing where you pick random strings of words and try to derive meaning from them without taking in the entire context that surround those words into account?
Yesterday I hosted November’s Hotwire Native Office Hours.
[

](https://newsletter.masilotti.com/p/office-hours)
Every month I host an hour long Zoom session for developers to directly ask me questions. The topics range greatly: some folks are just getting started and others are asking very specific, advanced questions.
This month we covered everything from registering bridge components to native vs. web-based tabs to authenticating Apple Watch apps! It’s really fun to see what folks are working on in the Hotwire Native space.
During the session I shared some code I wrote to figure out what version a Hotwire Native app is running. The app sends the version in the user agent (e.g. v1.2.3) and then I parse it with the following Ruby class on the server:
class AppVersion
include Comparable
attr_reader :major, :minor, :patch
def initialize(version_string)
parts = version_string.to_s.split(”.”).map(&:to_i)
@major, @minor, @patch = parts[0] || 0, parts[1] || 0, parts[2] || 0
end
def <=>(other)
[major, minor, patch] <=> [other.major, other.minor, other.patch]
end
def to_s
“#{major}.#{minor}.#{patch}”
end
end
And it works great! I use this throughout my apps to feature flag code based on which version the app is running.
But someone brought up something even better: Gem::Version. This class accomplishes the same goal: “process string versions into comparable values”.
irb(main)> Gem::Version.new("1.2.3") > Gem::Version.new("1.2.2")
=> true
irb(main)> Gem::Version.new("2.0") > Gem::Version.new("1.2.2")
=> true
It can even compare prerelease versions, like alphas or betas.
irb(main)> Gem::Version.new("2.0.b1") > Gem::Version.new("1.9")
=> true
irb(main)> Gem::Version.new("2.0") > Gem::Version.new("2.0.b1")
=> true
The big advantage this class has over my implementation is that it’s built into Ruby!
It’s not even a Rails dependency but part of the standard Ruby library. Internally, this class is used to compare version numbers when parsing your Gemfile. Check out the documentation, I picked up a few things on semantic versioning when reading it.
I’ve already replaced my AppVersion class with Gem::Version. But it makes me wonder what other Ruby/Rails features I don’t know about and am implementing on my own!
I never would have learned about Gem::Version if it wasn’t for someone bringing it up during office hours. I’d still be using my custom (and most likely buggy!) AppVersion implementation… like a chump. 😅
But seriously, this is why I love connecting with folks in the Ruby/Rails community. Every time I go to an event, host a workshop, give a talk… I learn something new. Sometimes it’s small things like Gem::Version. Other times it completely changes my career, like the first time I heard about Hotwire Native.
I’ve taken this to heart and recently started organizing monthly Coffee and Code coworking sessions in my city, Portland, OR. Every month I post up at a local coffee shop with my laptop and invite all of the Portland Ruby Brigade to join.
This month we had five people! It’s no corporate-sponsored-meetup but it sure is something. And the connections that folks are making during these casual events are real. I’ve already seen folks exchange emails for potential future contract gigs.
What’s the first step you can take today to build some community in your local area? Even if it is just finally inviting that connection-to-be for a beverage… I say go for it.
No posts