In order to make use of OpenStruct, `require 'ostruct'` first needs to be declared. Our code neglected to make that declaration, and we saw failures when it was deployed. This code, however, passed all of our tests. We discovered it was because our testing framework included rspec-expectations, which has a dependency on diff-lcs[1], and diff-lcs itself declares `require 'ostruct'`[2]. Because of this, ostruct was loaded globally before our code was tested, which silently masked the underlying issue.
This being said, I do understand the sentiment that this feature seems superfluous and may introduce unnecessary complication, especially from a Rubyist's point of view. The underlying mental model of Ruby dependency management is different from many other languages, and it's something to keep in mind when coming from other languages that do have scope for declared dependencies.
[1] https://github.com/rspec/rspec-expectations/blob/v3.13.3/rsp... [2] https://github.com/halostatue/diff-lcs/blob/v1.5.1/lib/diff/...
The first execution of new code is in CI?
Some new features feel almost prescribed from other languages? Like, RBS and Namespaces specifically... they don't really fit the model of Ruby exactly, but they need to be there so Ruby can be presented as "having" type-safety and a-solution-for-monkey-patching. I'm all for taking inspiration from other places, but Ruby wasn't quite built around the same axioms that other programming languages started from.
It was only after working with Go that I realized how much compile-time safety and performance I was missing out on with Ruby.There's no doubt I would use Ruby today again, but I can't imagine going further beyond simple scripts and workflows. I have recently tried building a very small website with Rails, and I was somewhat surprised the framework did not mature all that much, and type safety and IDE support still seem to be iffy. There is so much unwanted magic that I still see with Ruby on Rails in general. I was looking up to Crystal to address some of the issues I described above, but unfortunately it just remained as a less mature language. Elixir was great, but with the experience I have had, I will actually drop heading into the functional programming world. That leaves me with only one choice, which is Golang. I am not a fan of everything it has, but it is fairly easy for my fellow engineers to pick up, and the IDE support has been nothing short of fantastic.
This is really really a big stretch, but I have always been hopeful towards looking for a TypeScript sort of compile-time layer for Ruby. It may probably never happen though, sadly.
I'd be interested to hear what you find lacking in Crystal, too. For me it would be editor support, last time I tried the LSP I couldn't get it to work.
Could you elaborate on this? It's a valid resolution, but I consider Elixir to be on the pragmatic side of the functional paradigm, so I'm wondering what pushed you away.
I've been working with Ruby for 20 years, and I've not needed something like this. This feels like adding a lot of complexity for little practical benefit. The trade-off feels off. I don't think this is worth the additional complexity.
Follow gem naming conventions and this is a non-issue -- both FooBar::Record and BazQux::Record can coexist for foo_bar and baz_qux gems, respectively. If a gem is defining other top-level constants outside of their gem module, then that's considered against convention, i.e. bad practice, and the language should not be modified to allow such a thing.
I'd like to hear of a real use case for namespaces that existing conventions don't already solve.
Similarly, if you want to group modules that don't conflict together under a short module name, nothing stops you from doing:
module MyGroup
include Module1
include Module2
...
end
In other words, wrapping them doesn't remove your ability to use short non-namespaced names.Using `include` of specific functionality into a class that will use it is furthermore an idiomatic way of avoiding that extra typing without polluting the global namespace
For that matter, you can often achieve close to what you're arguing for as well without actually making any changes to Ruby:
def wrap_load(path) = class_eval(File.read(path), path)
module Test
# some_file.rb will act as-if defined within Test
wrap_load("./some_file.rb")
end
You can do better than that, to get closer to emulate `require` rather than `load`, and handle dependencies.Overall, I think the fact you can do these things suggests you could probably write a good enough plug-in `require` monkeypatch suitable for the rare cases where `include` from within a class or module without needing a language extension.
This is not the Ruby ethos.
And why would you bother doing that when it would be a breaking change?
>First, I'm not convinced by the motivations:
>
>Avoiding name conflicts between libraries: Applications can require two different libraries safely which use the same module name.
>
>Is this a problem that happens on a regular basis? I believe Ruby has a pretty well established convention for libraries to expose a single module with a name that correspond to their gem name.
I really don't think we want to make it easier for newbies to alter gem naming conventions and run multiple versions of a gem within the same project, this sounds like a genuine nightmare to me. I've found from jumping in to fix broken and crippled rails projects for startups that the fuckup surface area is already high enough.
But I don't personally think Shopify would benefit from this specific implementation of namespaces (a couple colleagues do). I'm personally not even sure Namespace is a proper term to describe that feature. It's more some sort of lightweight sandboxing to me.
Also:
> They have so many expert Ruby devs
If anything, the average Ruby expertise at Shopify is likely noticeably lower than in most Ruby/Rails shop.
I personally would love to have this feature!
I'm updating my opinion from "mixed feelings" to "against" on this.
This namespaces feature allows you to manipulate existing globals, but keep it isolated in your own namespace. That seems pretty good to me :) Because it remains isolated, you can also use these features more aggressively.
Over the past two years, I have come to understand that this contributes to the nightmare that is the Nodejs ecosystem (and the browser JS exosystem in general), at least when it comes to writing reliable software.
Maybe after a decade or two I wouldn't care, but as someone who has only used Ruby casually I would steer clear of it for anything serious, largely due to the lack of namespaces.
Even though this is from the YJIT folks, they include the non-jit improvements as a comparison.
Solves this problem and "magic" that so many complain about while retaining all the other great things to love about Ruby.
Many times LSP's can't figure out where the code is coming from if it's a few layers deep. Then you're stuck with the time consuming method of running the code and doing something like what you're describing above just to read it.
require 'foo'
Bar = Foo::Bar
That being said, I have no particular stance on whether this feature is a good change to the language; in a decade of Ruby this is the only situation I can recall that really merited it, and the concerns articulated by byroot and others do resonate with me.
[0] https://github.com/panorama-ed/memo_wise/blob/main/benchmark... [1] https://github.com/panorama-ed/memo_wise/blob/main/memo_wise...
One of the tradeoffs that (imo) has been net positive in the Ruby ecosystem, is how a project has to ultimately load and run a single set of versions of all dependencies. It creates some extra maintenance work on one hand, but the result is that it encourages the ecosystem to not have to face the hell of having 3 or 5 or 10 versions of each common dependency within a project.
I recognize that this is an occasional cost to library maintainers, but in the long-term has contributed to benefits. My perception is that Ruby libraries have smaller list of dependencies than similar libraries in some other languages. There are several reasons for this, but this ecosystem pressure to stay compatible with a range of dependency versions is one of them.
It feels to me like this leads to a sweet spot for small- and medium-sized projects, and I can see it might have an upper limit? I have not been in the situation, but heard of situations where the largest projects inevitably run into needing specific versions of two separate libraries that don't agree on a version of a common dependency.
But I think the more interesting story is the widespread opposition to the way this was forced through in spite of major conceptual problems, bugs and performance regressions.
https://naildrivin5.com/blog/2019/07/10/the-frightening-stat...
require 'foo'
module MyPreferredNamespace
include Foo
end
If you use a third party library, when you require something, you have no idea what "modules" or other values it creates polluting the global namespace. This at least assigns those values to a local variable where they can be accessed and doesn't make them global.
That said, I'm not a fan of ruby and all the workarounds to try to make it like a more sane language that it isn't.
Wasn't that the purpose of refinements? Perhaps I am misremembering, because I never had a need to reach for refinements myself.
Because, if the external dependency smells that bad, what the root cause? If that is crap code produced thus for some reason (unskilled/over-time-bound-pressured person for example), then probably this is better to avoid using this code altogether. And if this is instead because the language encourage easy path with bad outcomes, then it might make more sense to make a proposal with a facility to instead make the path of least resistance be something of more enjoyable outcome.
It will be identified as such by e.g. the default `inspect`, but nothing stops you from `include`-ing MyModule into another module or class, or even the global scope so that you can reference MyClass without MyModule.
The example given in the ticket, defining a global constant PORT in the root namespace and then loading it separately into two apps--nobody actually does in Ruby this today, because it is well-known that it will cause conflicts. Any gem (Ruby library) that did this sort of thing has long-since been barked at and corrected--the solution is simply to nest the constant inside your main module e.g. MyGem::PORT.
Now, gems will be free to define whatever constants/modules/etc. they want. When someone files an issue, the response can be: "Oh you don't like that I redefined Array? Load my gem in a namespace!"
(In the example, it usefully provides a way to maintain sanity when `app1.rb` and `app2.rb` both define a global constant named `PORT`)
However, I'm not sure how much existing code is defining stuff in such a poorly considered way. (I don't mean that rhetorically. Maybe it's more pervasive than I think)
Furthermore, would adding this feature to the language actually encourage such bad behavior? (Would it even be "bad" behavior at that point?)
So I'm kind of leaning toward "I can see how this would be useful, but I don't want the language to condone such a bad practice."
That said, I do think there's use to this. First of all it would allow fancy platforms like RoR to make more effective use of namespaces. Right now you always need to specify the fully qualified name of a constant whenever you're defining it, which is just not aesthetically pleasing.
Another potentially useful place for this is in migrations. If you could just move the old implementation of a thing into a subdirectory and then load it into a namespace you make references to it a lot more explicit, and you give the replacement architecture full freedom within its root namespace.
Just to say, it's not only bad behavior that would be enabled by this feature. I definitely agree that having gems not sticking to their own modules would be a very bad thing indeed.
Ruby on Rails specifically has a batteries included approach that lets you get up and running very quickly. But my perception is that as JavaScript has leaked into the server (benefits of writing the same language on front and back end) it's eaten into Ruby's mindshare. Python also finally moved on from its 2 vs 3 nightmare and shares many of the benefits of Ruby.
These days you'd build a blog in 20 minutes by vibe coding it in Cursor and deploying it on to some serverless edge compute platform or something.
Almost 20 years ago you'd build a blog in 20 minutes by installing rails and running a few commands on the terminal to generate the UI, backend, DB schema/migrations and all that, and then `git push heroku master` to see it deployed on the web. Quickly enough you'd git gud and wouldn't need to lean on the scaffolding tools.
At least in London there's still a pretty strong market for it, and the overlap in syntax between Ruby and Elixir is enough that you could take your pick (the Elixir shops I know of will look for experienced Ruby engineers by default because the pool of Elixir engineers is much smaller).
The original DHH demo video[1], from 19 years ago, was a blog in 15 minutes.
AI is clearly rubbish! :P
Shopify, Github, AiBnB, Square, Instacart, etc...
There's more big apps/websites using Rails than Elixir or Phoenix...
Sure, but thats a weak argument considering Ruby on Rails is considerably older than Phoenix and adoption is in decline while Phoenix grows.
By the way, I've seen many more Rails projects than Phoenix ones but they share the same general architecture: (server or containers) + db. The only difference is that we usually run Ruby jobs inside sidekiq and we use Tasks or GenServers for Elixir jobs. We wrote our own code to restart and try again jobs that eventually failed (we can't lose a job because of a deploy or a reboot.)
With Elixir we stored the job data in a PostgreSQL table and started a process with the id of that record as argument.
When we had to restart the BEAM (deploy, reboot, anything) a process would look at that table and start a process for every record not marked as complete. If a process failed too many times and its supervisor gave up on it, we could restart it manually by calling a function in the console with the record id as argument. Or we could start all the processes of a given type at once, or all of them.
So we basically wrote a small subset of Redis and of Sidekiq admin.
https://news.ycombinator.com/item?id=43130546
https://news.ycombinator.com/item?id=43881035
https://news.ycombinator.com/item?id=42253735
and more: https://hn.algolia.com/?dateRange=pastYear&page=0&prefix=fal...
For being employable... I'd say Ruby is going to be near the middle of the pack. Elixir is going to be near the harder-side, and JS is going to be near the easier-side... on average IMO.
If you think ruby intrigues you, give it a try! You've got precious time + motivation for this sort of thing, but messing around with a programming language is not much effort if you're intrigued. Getting a feel for it is a sub-weekend project away. It's my favourite language for bodging, the scripts I make in ruby for little data-mashing or system things are surprisingly stable and readable, even a year or two later. (Compared to a nodejs, perl or shell script... which all tend to have understandability-half-lives of a month for me haha) Automate something you find annoying.
Open any “Who is hiring” and compare number of Elixir positions vs Ruby.
That said I feel like Elixir/Phoenix is doing a lot of things right today and if I were invested in one or the other I'd stick with that one. But I'm more of a bootstrapper than someone who looks for a job. Ruby is #8 in Pull Requests and Elixir #24 on Github. Source: https://madnight.github.io/githut/#/pull_requests/2024/1
If I were just looking to be marketable I'd consider Python, Java or Go... but I don't love working in those languages as much as I do Ruby (or Elixir for that matter).
Edit: apparently, people who do love Ruby are also against this feature, for roughly the same reasons. Kinda funny, but entirely reasonable in hindsight.