> The supports_feature-method is probably defined somewhere 5 abstractions deep.
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.
> might also be part of some library's weird meta-programming of supports-* that no LSP can point you towards
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.
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.
> 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.
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
> Yes, but this is something that IMO didn't age too well when we transitioned from pet servers to cattle servers
A significant part of the utility is in development, and that doesn't go away.
> I'm not saying that you can't do it but 1) it will discouraged by security best-practices in most places
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.
> 2) the Ruby in which you are in might disappear under your feet in a second
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.
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.
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…………
Pry provides most of the value, so my script is usually basically just something like this:
def reload
load "app.rb"
# If you want to load multiple files, remember that if app.rb
# uses require/require relative, the files will only be loaded
# once; you can change that but really it's not usually what
# you want for production; in that case either look into a proper
# reloader, or you'll want to load the rest of the files here.
# Sometimes it's fine to e.g. do a Dir.glob("*.rb").each {load _1}.
load "dev.rb"
# Put "dev niceties" in this file. E.g. maybe load table_print or
# awesome_print, Hirb, or whichever nice formatting tools you
# want, or define any custom introspection methods that are
# helpful. Eg. for an analytics project I worked on had a few tools to
# wrangle CSV test data there. (I really ought to extract the best
# from my various projects into a gist or something, but these
# files often accumulate really project-specific stuff.
end
require 'pry'
reload
binding.pry # Throws you in the Pry prompt at the top level scope.
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.
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.
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.
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)
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)
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.
It will never be tidy enough to show other people, or at least that's the way it will feel. People would love to see what is done anyway, as this sounds like a fascinating project. Even just a basic walkthrough of how you currently use the system will be valuable.
I've put plenty of half-baked stuff out there over the years, so that doesn't really worry me. More that at the moment if you try to copy any of it the github repos are all at different stages of not quite up to date, and APIs are in flux, and you're just really likely to have a bad time trying to get anything to work.
I think the real starting point for me is going to be to clean up PureX11 a bit more so the API is at least somewhat cohesive, and then push the WM as it's working enough that it's been my only wm for a few weeks (it does have significant quirks still, but with somewhat minor cleanups it's a decent starting point to play with), and then the terminal as it's fairly freestanding, then some of the file management tools, toolbar, popup menu etc., then lastly my editor. The editor has by far changed most from the version on Github and is also most likely to cause problems for others, so that might take a bit of time, not least because I'm in the middle of a fairly significant overhaul of the way the views and models works.
Here's some of what is out there, though:
* Skrift: This is a Ruby port of libschrift, a TTF font renderer. It's heavily cut down, and currently stands at about 680 lines of code. I intended to tidy up the API as it's still a bit messy after my rewrite: https://github.com/vidarh/skrift
* X11 bindings for Skrift: https://github.com/vidarh/skrift-x11 - these are messy, and I have significant updates to them (including basic fontset support and a mechanism for pixel-perfect boxdrawing characters at any reasonable scale) that have not yet been pushed: https://github.com/vidarh/skrift-x11
* Pure-X11: This is a form and significant overhaul of pure X11 client bindings for Ruby (as in not Xlib or XCB needed): https://github.com/vidarh/ruby-x11 - it's not terribly out of date, but it's a bit in flux as I don't like the initial mechanism, used for the protocol and so I'm thinking about how to trim it down and make it easier to use.
* This is the starting point for my terminal. My terminal is nothing like that any more, but this is the repo that will get all the updates, eventually: https://github.com/vidarh/rubyterm - this initial prototype used a C extension and server-side fonts, while the current version uses Pure-X11 and Skrift
* This was the very first version of my WM I used, a few hours into the switch (from bspwm). It's a straight port from TinyWM. My current one has tiling and some EWMH support and multiple desktops and adds about 700 lines of code - it'll start appearing on Github soon: https://gist.github.com/vidarh/1cdbfcdf3cfd8d25a247243963e55...
* This is a script I used to feed into a 9menu style popup menu script from my file manager to generate folder-contextual actions based on the folder contents: https://gist.github.com/vidarh/323204137de5293bfe216ec751646... -- the current version is quite a bit slicker and will eventually show up
* This is a very dated and broken version of my editor, and odds are you'll struggle to get it to work at all, as it depends on various helper scripts that are not yet packaged up, as have been massive updated since that version; I'm hoping to maybe bring the repo a bit more up to date over the holidays: https://github.com/vidarh/re
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!
In my experience most Python I see is amenable to static analysis and type checking. It’s possible to do the same with some Ruby, but the language is designed to make it very easy to programmatically create new types. Do you not find that this drastically reduces the effectiveness and coverage of Ruby type checkers?
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.
> might also be part of some library's weird meta-programming of supports-* that no LSP can point you towards
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.