I think the point about expressiveness is exactly what makes ruby so divisive: It is great when you are deep within the ecosystem or do exactly what the ecosystem expects you to want to do, and an absolute pain if you want to do anything non-standard.
The supports_feature-method is probably defined somewhere 5 abstractions deep. If you are lucky that is, it might also be part of some library's weird meta-programming of supports-* that no LSP can point you towards. I've never worked in an ecosystem that celebrates implicitness as much as ruby does, and it is driving me nuts.
The fact that finished code looks great and reads well doesn't balance the scales in my book.
Yes, but pro tip: you can do object.method(:supports_feature).source_location to inspect where it comes from. It may be a module included into the class, the class itself or one of its super classes, or a concept built on modules like ActiveSupport "concerns". But source_location works in almost all cases.
Yes, if this comes from method_missing, you can't check it's source_location. You need to use your knowledge that the method name works and that method_missing is how such things are done, and then you do object.method(:method_missing).source_location and read the logic there.
The lack of safe and reliable IDE introspection and design-time type support are my number one issue with Ruby, and why I tend to use typed languages instead.
However, there are workflows for inspecting a codebase that work, they just don't fit in well to the usual LSP+IDE pattern. You are expected to use irb/rails console to do your inspection of the structure and behavior of your programs.
To me this is why I don't like IDEs.
My Ruby projects often have a wrapper that reloads code and puts me in a pry prompt. I can edit code and introspect objects live as the code changes.
It's a natural extension of Ruby's Smalltalk legacy.
I recently replaced my X window manager with one I've written in Ruby, and I can just attach to it and modify state or change code live to test or fix things.
Being confined to JS and Java in VS Code for work is like having one arm tied behind my back.
Indeed, the live interactivity of Ruby is it's strongest asset, and the best part of it's Smalltalk legacy. I think it's the thing non-Ruby devs understand the least. For instance in good Rails apps, your models don't just serve as the business layer of your web forms, background jobs and rake tasks-- they serve as the interface for developers and operations folks too. The ability to get directly into the machinery to inspect and command the state of your systems is super powerful, and as a result, very little work is put into higher level ops tooling like it might be in more opaque systems.
This is true in the dev space too. In dev, you can just set breakpoints by inserting a pry/byebug call and the debugger is just part of the application at runtime. Can you do it in a sufficient Ruby IDE too? Of course. In my experience this isn't super common though, and isn't going to give you the best debugging experience, because it's separating you from that hands on direct inspection context.
And IDEs like Rubymine like to think that the excellent deterministic automatic refactorings of languages like Java are actually feasible in a Ruby codebase, but I have seen that first hand go horribly wrong with a developer who trusted them.
Yes, but this is something that IMO didn't age too well when we transitioned from pet servers to cattle servers (and that's even more true in a serverless scenario or even just Kubernetes). I'm not saying that you can't do it but 1) it will discouraged by security best-practices in most places 2) the Ruby in which you are in might disappear under your feet in a second
A significant part of the utility is in development, and that doesn't go away.
I've yet to see an ops team (I've been involved in ops work for 28 years at this point) that doesn't have plenty of escape hatches, irrespective of the language. One can pretend that it's locked down all one wants, but the moment the ability to open a root prompt in a container exists that is just an illusion.
The most brutally locked down systems I've seen still wouldn't usually prevent you from getting access one way or another (sometimes the means of getting access have been painful, like going via an IPMI console or other "fun" detours, on a very few rare instances it's required physical presence in a specific location; most places the most locked down setups you'll tend to find require a VPN or going via a bastion host), but would ensure the containers were destroyed after you exit (to prevent cattle from turning into pets... so you can debug but not leave changed state).
I'm not saying there aren't people who actually lock their systems down to a point where their ops team can't gain access to use a console; but I am saying that even a lot of places where people think that is the case, it often isn't actually the case. It may well be locked down too much for developers to be able to use it to debug in normal circumstances, though. I'd rather people are honest about it and secure and restrict the access properly than pretend there aren't workarounds that are in regular use, as I've seen way too many times.
If you don't have a way of mostly preventing processes from being killed when a connection is ongoing, that is a choice you made. If your technology is fighting you because of choices you made and you keep it that way, that is also a choice. Maybe it's right for you, but no system I've built would randomly pull the rug on me other than due to actual system failures.
but I am saying that even a lot of places where people think that is the case, it often isn't actually the case.
Yup, one day someone was poking around our mess of over-engineered k8s web app ephemera, clicked a button, and realized we finally had a working Rails production console, albeit one in a web browser, without our tooling, and that would drop connectivity every 15 minutes. When this was discovered this was told to be all part of the plan… welcome to the spin zone.
they serve as the interface for developers and operations folks too. The ability to get directly into the machinery to inspect and command the state of your systems is super powerful
Which was awesome and super efficient and kept developers close to the production environment and the necessary knowledge of that context to properly build applications.
Then DevOps came along with a bunch of great new ideas…………
I think you're right many don't understand it, but also many who do understand it a bit seem to be scared of trying to embrace it, and so never really experience the power of it.
Even within the Ruby community a whole lot of people just stop at fairly basic exploration in the Rails console and don't explore the full level of flexibility they get you.
And I think a good Ruby IDE would be one that tried to be more of a Smalltalk like environment - if you want to refactor Ruby code, you're far better off starting by introspecting an actually loaded app environment rather than starting from a static parse.
Do you mean you do all coding, including file saves in a REPL? Just trying to understand.
They explain elsewhere that they use an external editor, though they do sometimes use pry's builtin shortcuts and EDITOR convention integration to perform quick edits.
I'll note an additional take on this, represented by my editor to illustrate just how far down the rabbit hole this goes with Ruby:
My editor is in Ruby, and uses DrB to talk to the backend. It has a key combination to throw me into a pry prompt, and also throws me into a pry prompt if any exception is thrown. Since DrB will forward any exceptions during the execution of any messages (and here we really see the message passing bit - they are literally messages passed over a socket and via proxies that don't know what they mean) back to the client, this means that any exceptions in the server will throw me into a pry prompt where I can dynamically edit the code of the server process and continue execution.
The server process holds the open buffers, and I can reattach to it, and thanks to that use of DrB I was able to switch to using my own editor day to day at a point where it was still wildly unstable without losing any data. Sometimes I'd edit the editor with itself while it was in a broken state, and reload the offending code to fix things and just keep working.
With most other languages - with a few exceptions - I'd have to wait until I had something more finished and more stable to start using things. With Ruby I often feel comfortable with starting to use projects while they're still crash-prone and half-finished and lacking because it's so easy to metaphorically replace the engine in mid-flight.
Fancy, custom editor or something that's out there?
Very custom, to the point that the current iteration is dependent enough on a bunch of details of my environment (part of this is an ongoing drive towards minimalism - e.g. the editor doesn't know how to open files or select buffers or select themes; all of those things are delegated to scripts that currently use rofi) to the point I'm not convinced it'll even start on someone. I'm slowly cleaning it up to at least pretend it might work for someone else, but that means also deciding on a cleaner interface to the helper scripts so I don't have to package up my entire environment in one go.
The beauty of the DrB part of it, though, is that the shell of that is very small: Just spawn a DrB server, spawn a client, and wrap the client in a begin/rescue block with binding.pry, and put any critical data on the server side. Suddenly your server-side is near crash-proof and your data much less likely to disappear. If you want an extra level of protection, run a threat on the server side to checkpoint the data regularly (I used to checkpoint it every 5 seconds at the start; it's now at around 5 minutes, which also means every buffer I've opened and not bothered to kill from the last 5-6 years are still accessible... I never bothered to add code to clean them up as they just don't take up much space)
You can do that. E.g. pry has an "edit [methodname]" command that will spawn $EDITOR. But I usually keep my editor in a different window and just call a "reload" method at the pry prompt where I've wrapped up the logic to reload the running code.
Occasionally I may restart, if e.g. object state has changed enough, but this means that if I've set up a bunch of test data for example, and run into a bug, I can fix the bug, "reload", and the state of the running objects remain the same but the method will have updated and I can retry the same method call with the exact same object state.
Shh, you're giving away our secrets. :)
Just let the bliss wash over you and ignore the haters.
Hah. It always seems to me in these threads that a lot of the hate starts from a point of not understanding the language very well. If you're used to seeing working on a software project as a static, batch-oriented process, Ruby - like Smalltalk - is alien. When people then try to force it into a batch-oriented process they're throwing out so many of the nice things I'm not surprised they dislike what they're left with.
But I don't let it bother me. I instead enjoy rewriting an ever-increasing part of the software I use day to day in Ruby (my editor, shell, file-manager, contextual menus, menu bar, font renderer, window manager so far - I keep telling myself I need to stop myself before I start writing an X or Wayland server too, or even worse, before insanity takes me and I start writing a browser)
You should make a "my tooling" YouTube video or GitHub org- I would love to see your unique little tool ecosystem
I'll do something once I've cleaned it up a bit more. It's a bit all over the place at the moment as I went on a bit of a spree this autumn and rewrote whatever annoyed me, and one of the luxuries of having done this pretty much just for my own use is I haven't had any strong incentives to make it pretty... But I do intend to tidy it up and when I do I'll at a minimum do a writeup.
That sounds very interesting! Can you please share the wrapper that does all that magic? Thanks!
Pry provides most of the value, so my script is usually basically just something like this:
It does require that you're somewhat careful not to make the loading of your files too stateful, which is a good practice anyway, and you do need to be mindful that things will occasionally fall apart if you reload the running code as it will modify the classes of objects that already exists but not e.g. update their instance variables, so if you add a method that expects @foo to have been initialized, but existing objects do not have it initialized, things will go obviously go badly.Since pry supports "edit some_method_name" and will spawn $EDITOR you can even do edits in the same terminal that way, but I tend to prefer to have my editor open in another window. (And since so many here seems to struggle to find methods, in addition to "edit", "show-method some_method" in the right context in pry is also highly useful)
Sometimes I'll keep ways to trigger pry in applications during regular runtime because it's so useful for debugging issues, and sometimes even fixing issues in a running process.
EDIT: While I prefer pry, it's worth noting that Irb has gotten a lot better (lots of features from pry) in recent versions, and also "rdbg" (debug gem) is awesome if you don't want to use a separate script like this - you can equally well run your code under rdbg and just have a script handy to load tools you want for a debug session; the downside of rdbg is if e.g. attaching remotely you'll be running in a trap context, which means you don't have quite the same freedom with respect to what code you can actually run - that may or may not matter.
At its heart, Ruby just doesn’t do static analysis. Program behaviour is defined at runtime and at runtime only. This ethos is why method calls in the language are implemented with message passing and also why everything is a mutable instance of Object.
The behaviour of a Ruby program, right down to the types and methods available, is only defined by running the program itself. It allows for all the glorious freedom of meta programming for which the language became famous as well as the often times inscrutability of the source code.
With a language like Python, the source code at the start bears a good resemblance to the structure of the program when any particular function executes.
With Ruby, the source code at the start of a program is merely a hint as to what the ObjectSpace will contain when your particular function ends up being called!
This is the classic mistake of confusing Ruby with Rails. That might be true of Rails. It absolutely isn't true of vanilla Ruby. The article was not "I love Rails".
Does Rails not use Ruby?
It does, but at this point it’s like saying that JavaScript is DOM even though you can use it outside a web browser. Or it’s like blaming Python for TensorFlow. Like, yes, Rails is a use case for Ruby, and a popular one, but not the only one.
Agreed. Ruby is not Rails in so many ways, even on philosophical grounds. Rails is full of magic, implicitness, and conventions, which enables a great deal of very speedy development but also becomes† a minefield if you steer away from The Way; while plain Ruby is very explicit and in plain sight, there's no magic. It's a blank page, an open field, it's raw material for creative ground.
Rack or Minitest for example are very Ruby. RSpec on the other hand is full of Railsy magic, just yesterday testing some threaded stuff I was bit by `let` because it has concurrency consequences, and the magic becomes darkness as you have to look behind the curtain and beat it into submission in roundabout ways that obscure the thing you're actually doing.
† or sometimes is a minefield, all by itself, like this nonsense: https://github.com/rails/rails/blob/1512cf2ba578282c898b8eb6...
Reminds me of Angular vs React-- In my opinion it's much easier to get immediately productive in React, but it's very easy to produce a rats nest over time if you aren't experienced with the framework. Versus Angular which has a lot of upfront learning curve, but it's prescribed structure makes it fairly easy to scale out the app over time.
Rails is definitely more like React in this respect. It turns out you can absolutely color outside the lines and still enjoy the speediness, but it's not something that I experienced devs should try to do without making a huge mess.
The point is valid though. Yes Rails is particularly bad, but I've been working with Ruby for ~15 years now, and plenty of non-rails codebases in that time. The metaprogramming and implicitness runs deep in most of the community, it's common to find highly magic and hidden implementations in Ruby generally.
I often find the sort of over-the-top implementations you describe in gems that were written by Rails users. Since other frameworks and vanilla are not super uncommon, it's fairly normal to make your gem that you envisioned being used within a Rails app not dependent on Rails. If those developers read too much Rails framework code (or toxic blog posts from DHH) and got the wrong idea, then it's not surprising they imitated what they thought was the way to do things.
If the DOM APIs were full of really bad patterns, or used a bad language feature, then the blame would partially lie on Javascript for promoting those patterns or not having better alternatives.
Ruby is not Rails, but Rails is Ruby.
In my experience this is almost never the case. #supports_feature? would be defined right there where you'd expect - in that Subscription model.
Even tough you can reach for metaprogramming (like define_method or method_missing), that's really not how the entire ecosystem of guides and tutorials will point you.
Anyways, when in doubt, just plug a debugger and call "@subscription.method(:supports_feature?).source_location`, and generally that's all it takes.
Rails is the biggest Ruby project and it’s heavily based on metaprogramming. Even Rubymine has trouble finding the canonical definition of a named method sometimes – it has to guess. I love Ruby but I totally understand this criticism.
Rails is a library, though, the advice applies to application/business code. Metaprogramming is extremely useful for making really simple-to-use libraries. If your business logic is full of metaprogramming, that's more of a problem, especially in this day of age where it's common for developers are only sticking around at any given job for 2 to 3 years.
The most common framework in ruby disagrees. It wiuld be 5 layers deep
Well, it would probably be 2-3 layers deep in a concern, really. Concerns are just fancy modules, and modules are just reusable parts of a class. In Rails basically everything is a concern. To a concerning degree actually, as a professional Rails developer of many years, I think they went way overboard with it. My team and I also curse it's single minded design when we have to drill down there. Thankfully that's a pretty rare thing to have to do.
As for the abstraction, Rails does take it pretty far, but you also have to take into account that it's a framework that supports numerous pluggable strategies and disparate backends, and extensibility. I think most mature frameworks are pretty complex-- the goal of the framework is that it's all encapsulated away from you for the vast majority of tasks, and in Rails, that is extremely true.
It's a pretty simple heuristic to track down the source of metaprogrammed functions... just check for method_missing() or define_function() or a handful of other keywords. I believe the regular syntax is just shorthand for these features anyway.
edit- as another comment points out, you can just do `@subscription.method(:supports_feature?).source_location` too
Another strategy is just to step into the method call in pry/byebug. I've done that for particularly tricky cases. At that point it's pretty easy to walk it up to the mechanism that does the metaprogramming.
Weird that nobody is mentioning how poorly ruby is documented on its main site.
There is no proper tutorial, only links to external resources of varying accuracy and levels of update-ness.
It really doesn’t help with grasping such a customisable runtime.
IMO this is because software engineers both get bored and feel a need to compete. The more abstract they can make code the better (more safe?) they feel. That lets them play chess on their terms and eliminate the competition. But that's just my take, I may be wrong. Fortunately the most commonly used gems wrap this abstraction into usable APIs, and most have good documentation.
While this can be correctly attributed to ruby or the devs in its ecosystem, I've been on teams with staff using other languages and stacks that let their completion/anxiety fly making it hard to read and understand their code. I guess my point is it's a good critique, but it falls beyond the meta programming in ruby.