5 ms·
> The supports_feature-method is probably defined somewhere 5 abstractions deep. Yes, but pro tip: you can do object.method(:supports_feature).source_location
by rezonant 3y ago
> 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.
- vidarh 3y agoTo 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.
- geospeck 3y ago> My Ruby projects often have a wrapper that reloads code and puts me in pry prompt. I can edit code and introspect objects live as the code changes. That sounds very interesting! Can you please share the wrapper that does all that magic? Thanks!
- vidarh 3y agoPry 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.
- busterarm 3y agoShh, you're giving away our secrets. :) Just let the bliss wash over you and ignore the haters.
- vidarh 3y agoHah. 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)
- bbkane 3y agoYou should make a "my tooling" YouTube video or GitHub org- I would love to see your unique little tool ecosystem
- vidarh 3y agoI'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.
- mjdiloreto 3y agoIt 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.
- deleted 3y ago[deleted]
- quickthrower2 3y agoDo you mean you do all coding, including file saves in a REPL? Just trying to understand.
- rezonant 3y agoThey 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.
- vidarh 3y agoI'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.
- rezonant 3y agoFancy, custom editor or something that's out there?
- 3y ago
- rezonant 3y agoIndeed, 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.
- vidarh 3y agoI 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.
- darkwater 3y ago> 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
- gorgoiler 3y agoAt 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!
- kazinator 3y agoThere is virtually no difference in this regard between Ruby and Python. I can swap those two words in your comment without affecting its quality.
- gorgoiler 3y agoIn 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?
- kazinator 3y agoCreating a new class in Python at run time is pretty easy with the three-argument form of the type function: >>> NewClass = type('NewClass', (), {'foo':42}) >>> x = NewClass() >>> x.foo 42