Sorry if this sounds like a stupid question but - is there no "Go to definition" command in an IDE that can help with something like this? I mean, I understand that there is, but it doesn't work well with Ruby. Why?
Other people have mentioned "dynamic typing" as being the reason for this, but that's not actually true. The real reason is two Ruby features: `define_method` and `method_missing`.
If you have a class `Customer` with a field `roles` that is an array of strings, you can write code like this
class Customer
ROLES = ["superadmin", "admin", "user"]
ROLES.each do |role|
define_method("is_#{role}?") do
roles.include?(role)
end
end
end
In this case, I am dynamically defining 3 methods `is_superadmin?` `is_admin?` and `is_user?`. This code runs when the class is loaded by the Ruby interpreter. If you were just freshly introduced into this codebase, and you saw code using the `is_superadmin?` method, you would have no way of knowing where it's defined by simply grepping. You'd have to really dig into the code - which could be more complicated by the fact that this might not even be happening in the Customer class. It could happen in a module that the Customer class includes/extends.
The other feature is `method_missing`. Here's the same result achieved by using that instead of define_method:
class Customer
ROLES = ["superadmin", "admin", "user"]
def method_missing(method_name, *args)
if method_name.to_s =~ /^is_(\w+)\?$/ && ROLES.include?($1)
roles.include?($1)
else
super
end
end
end
Now what's happening is that if you try to call a method that isn't explicitly defined using `def` or the other `define_method` approach, then as a last resort before raising an error, Ruby checks "method_missing" - you can write code there to handle the situation.
These 2 features combined with modules are the reason why "Go to Definition" can be so tricky.
Personally, I avoid both define_method and method_missing in my actual code since they're almost never worth the tech debt. I have been developing in Rails happily for 15+ years and only had one or two occasions where I felt they were justified and the best approach, and that code was heavily sprinkled with comments and documentation.
That code is *literally* calling class_eval with a multi-line string parameter, where it inlines the helper name (like admin, user, whatever), to grow the class at runtime.
It's been widely understood in the Ruby community for some time now that metaprogramming—like in the example above—should generally be limited to framework or library code, and avoided in regular application code.
Dynamically generated methods can provide amazing DX when used appropriately. A classic example from Rails is belongs_to, which dynamically defines methods based on the arguments provided:
class Post < ApplicationRecord
belongs_to :user
end
This generates methods like:
post.user - retrieves the associated user
post.user=(user) - sets the associated user
post.user_changed? - returns true if the user foreign key has changed.
Aren’t all these enhancement methods that are added dynamically to every ActiveRecord a major reason why regular AR calls are painfully slow and it’s better to use .pluck() instead? One builds a whole object from pieces, the other vomits put an array?
It's simply not true that "regular AR calls are painfully slow." In the context of a web request, the time spent instantiating Active Record objects is negligible. For example, on my laptop:
Active Record methods are defined at application boot time as part of the class, they're not rebuilt each time an instance is created. So in a typical web app, there's virtually no performance penalty for working with Active Record objects.
And when you do need raw data without object overhead, .pluck and similar methods are available. It’s just a matter of understanding your use case and choosing the right tool for the job.
Mainly because of the dynamically typed nature of the language. Not limited to Ruby/Rails. My colleagues used RubyMine because of this. I'm using Neovim with LSP, it's ok but nowhere near Go for example.