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.
To add, the above code is a pretty near approximation of the literal code inside the devise codebase, which is a very standard Ruby auth system.
See here:
https://github.com/heartcombo/devise/blob/main/lib/devise/co...
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 hurts my soul.