logoalt Hacker News

Rhainur05/04/20251 replyview on HN

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.


Replies

t-writescode05/04/2025

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...

        def self.define_helpers(mapping) #:nodoc:
        mapping = mapping.name

        class_eval <<-METHODS, __FILE__, __LINE__ + 1
          def authenticate_#{mapping}!(opts = {})
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.

show 2 replies