Search code examples
ruby-on-railsrubyoopclass-method

ELI5: Why can you call class methods within class methods without explicitly stating the class itself? (Ruby/Rails)


Sorry, the title is kind of confusing but I just discovered something that's quite mindboggling. When you call class methods inside class methods, you don't have to put a self before it in order for it to execute. I don't understand why this works.

For instance, suppose you have a model Food class with the following schema:

  create_table "foods", force: :cascade do |t|    
    t.string  "name", null: false
    t.string  "food_type", null: false
    t.datetime "expiration_date", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

class Food < ApplicationRecord
  def self.output_new
    new(name: 'marshmallow', food_type: 'snack')
  end
end

Instead of writing Food.new or self.new, simply writing new returns a newly created instance. Writing the methods using the Eigenclass syntax also yields the same results. This isn't limited to new but it works with practically all applicable class methods like create, delete, instance_methods, etc.

I've searched why this is the case, but I don't really understand. Would someone please explain why this is happening to ensure I'm not hallucinating?

Thank you!


Solution

  • Whenever you call a method, Ruby first checks to see if it's defined on self. There is only one exception to this that I know of - if you have a local variable with the same name as the method, it will reference the local variable instead:

    def num; 1; end
    num = 2
    num # => 2
    

    Class methods are the same as instance methods in this regard.

    It's easier to think of the cases where you can't omit the self:

    • When using a setter method e.g. self.foo = "bar" it's required because Ruby needs to know you're not trying to just set a local variable (as foo = "bar" would):

      class Test
        attr_writer :foo
        def initialize
          self.foo = 1 # calls the setter method
          foo = 2 # creates a local variable
        end
      end
      
    • When there's ambiguity between a local variable and a method (both have the same name) and you want to call the method:

      class Foo
        def self.a; 1; end
        def self.b
          a = 2
          a # references local variable
          self.a # calls method
          a() # can also call the method by writing out parens (removes ambiguity)
        end
      end
      
    • When self is not what you want it to be in that scope.

      class Foo
        def self; a; 1
      end
      
      a # obviously doesn't work, it isn't defined here
      Foo.a # works
      
    • When the method name is the same as a reserved word (e.g. class)

      def class_name
        class.name # syntax error, it thinks you want to define a class
        self.class.name # works 
      end