Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do Rails classes call methods directly on classes

I'm trying to understand a little more of the mysterious ways of rails and I have the following question.

When I am in a class e.g. a model I can call methods directly without putting the method calls inside a method on the model.

For example:

Post < ApplicationRecord
   
   has_many :comments

end

In this case has_many is a method call but its just sitting in my Post class without being inside a method. If I try to do the same thing in a blank ruby project it doesn't work.

for example

class ParentClass

    def say_hello
        p "Hello from Parent Class"
    end

end


class ChildClass < ParentClass

    say_hello

end

child = ChildClass.new
# => undefined local variable or method `say_hello' for ChildClass:Class (NameError)

So how does Rails do it? I see it all the time I even see some gems that add these method calls, e.g. acts_as_votable

Can anyone explain whats going on?

like image 268
Brad Avatar asked Oct 22 '25 22:10

Brad


1 Answers

There are two important concepts going on here - the first is that in Ruby unlike in more classical languages like Java the class body is simply a block of executable code:

class Foo
  puts "I can do whatever I want here!!!"
end

class simply declares the constant (or reopens the class if it already exists) and executes the block in the context of the class being created (or reopened). This class is an instance of the Class class which we call the singleton class or eigenclass which can be a bewildering concept, but it helps if you think about that in Ruby everything is an object and classes are just objects that are blueprints for creating instances and thus can have methods and attributes of their own.

The second concept is implicit self. In Ruby method calls always have a recipient - and if you don't specify the recipient its assumed to be self. self in the class declation block is the class itself:

class Bar
  puts name # Bar
  # is equivalent to
  puts self.name
end

Thus you can fix your example by simply defining the method as a class method instead of an instance method:

class ParentClass
  def self.say_hello
    p "Hello from Parent Class"
  end

  say_hello # Hello from Parent Class
end 

has_manyis merely a class method inherited down from ActiveRecord::Base that modifies the class itself by adding methods.

class Thing < ApplicationRecord
  puts method(:has_many).source_location # .../activerecord-6.0.2.1/lib/active_record/associations.rb 1370
end

Ruby has many examples of this type of meta-programming like for example the built in #attr_accessor method and they are commonly referred to as macro methods. These methods are quite simple once you get your head wrapped around the meta-programming concept:

class Foo
  def self.define_magic_method(name)
    define_method(name) do
      "We made this method with magic!" 
    end
  end

  def self.define_magic_class_method(name)
    define_singleton_method(name) do
      "We made this method with magic!" 
    end
  end

  define_magic_method(:bar)
  define_magic_class_method(:bar)
end
irb(main):048:0> Foo.new.bar
=> "We made this method with magic!"
irb(main):048:0> Foo.baz
=> "We made this method with magic!"

Its not really magic - its simply a class method that modifies the class itself.

like image 85
max Avatar answered Oct 25 '25 13:10

max



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!