Skip to content

Instantly share code, notes, and snippets.

@rahulrajaram
Last active September 30, 2018 22:01
Show Gist options
  • Save rahulrajaram/cf1896bbf0bf644086800b8d8f87e7e7 to your computer and use it in GitHub Desktop.
Save rahulrajaram/cf1896bbf0bf644086800b8d8f87e7e7 to your computer and use it in GitHub Desktop.
Ruby: Where is the method defined?

Ruby: Different ways to define methods in

The Ruby Object Model

One of the central aspects of  the Ruby programming language is the Ruby object model, as per which everything (but for constructs such as methods, and keywords) in Ruby -- ​​classes, instances, lambdas, procs, strings, numbers, decimals, hashmaps -- is an object. This renders a kind of uniformity to Ruby that few other languages offer.

At the very same time, the object model can also become confusing. In particular, the following two questions can sometimes become difficult to answer, but being able to readily answer which can set you apart as a Ruby programmer.

  1. What is self in a given context?
  2. If I define a method here, where will it go? That is, which object is it going to be defined on?

After about a year and a half of programming in Ruby, I stumbled upon the article, "Metaprogramming in Ruby: It's all about the Self. It opened my mind up to Ruby's elegance. Over the course of one month of reading the article, I became a significantly better Ruby programmer; it all, finally, made sense. Before you proceed any further, I recommend you read the article.

Another year and a half have passed since, but I sometimes still find myself confused at times. So, I mean for the rest of this article to be a cookbook of the various kinds of method definitions in Ruby which I am aware of, along with lite formal discussions of what happens in the background. Wherever possible, I offer advice on best-practices.

Useful tips

  1. Explore pry. Particularly, make sure you debugging with pry (using binding.pry)
  2. Using binding.pry, you perform object introspection and reflection.
  3. Particularly, you have at your disposal the following important methods that would help you understand the more difficult parts of this article better.
    • methods: when called on an object, returns all methods
    • singleton_methods: when called on an object, returns all singleton methods defined on that object.
    • singleton_class: when called on an object, returns the singleton class/eigenclass/metaclass of that object.
    • instance_methods: when called on an object, returns all the instance methods defined on the object.
    • private_methods: when called on an object, returns all the private methods defined on the object.
  4. When trying to list methods in any of the above fashions, sort-ing them can make the output more readable.
  5. When having difficulty with understanding what self is, fallback to "Metaprogramming in Ruby: It's all about the Self
  6. There are no "class methods" in Ruby. Class methods are essentially instance methods defined on classes, which are all instances of the class, Class.
  7. It is good to know, and become accustomed to the idea, that methods are defined in the metaclasses of objects. There are separate discussions dealing with this matter in depth.
  8. However, the goal of the designers of Ruby is to actually hide the complex concepts of metaclasses. Unless absolutely necessary, one does not need to discuss where methods are actually defined. It is okay to sound pedestrian. It is okay to say "def name; ..., :name is defined on instances", rather than "name is actually an instance method defined on the metaclass of instances, and because the instances inherit from their respective metaclasses, they all have access to :name".

Common method definitions

  1. On instances of classs:
class User
      def name
      end
end

User.new.name

:name will be defined on all instances of User.

  1. On classs:
class User
    def self.name
    end
end

User.name

:name will be defined on User.

  1. On instances of classs through inheritance:
class Person
    def name
    end
end

class User < Person
end

User.new.name

User inherits from Person. :name will be defined on all instances of Person and User.

  1. On classs through inheritance:
class Person
    def self.name
    end
end

class User < Person
end

User.name

User inherits from Person. :name will be defined on Person and User.

  1. On instances of classs through module mixins:
module Details
    def name
    end
end

class User
    include Details
end

User.new.name

User mixes-in Details. :name will be defined on all instances of User.

  1. On classs through module mixins:
module Details
    def name
    end
end

class User
    extend Details
end

User.name

User mixes-in Details. :name will be defined on User.

  1. On the metaclass/eigenclass/singleton class:
class User
   class << self
       def name
       end
   end
end

User.name

:name will be defined on User.

8. On modules:

module Rails
    def self.setup
    end
end

Rails.setup

:name will be defined on Rails, the module.

Something interesting to note here is that one might feel compelled to extend the above defined Rails module in a class, expecting that the method would get defined on the class, but that is not true.

Such methods are defined exclusively on module objects.

class User
    extend Rails
end

User.setup # will fail!

You should have a really goo reason to use methods on modules over methods on classes or instances of classes. We shall put the idea of module methods to good use later.

Less common definitions

9. On select class instances using define_singleton_method:

class User
  def setup(first_name, last_name)
    first_name = first_name
    last_name = last_name
    age = 18

    define_singleton_method :"#{first_name}_#{last_name}" do |*args|
      puts "#{age}"
    end
  end
end

user = User.new
user.setup("rahul", "rajaram")
user.rahul_rajaram # :rahul_rajaram is defined on this instance,
                   # and this instance alone

User.new.rahul_rajaram # hence, this will fail!

define_singleton_method defines a singleton method on precisely that object which self is. In the above example, the only instance of User to have the method :rahul_rajaram is the one that calls define_singleton_method to define :rahul_rajaram on itself.

The advantages of this approach are the following:

  • the method defined by define_singleton_method has access to the scope where it is called at the time of method definition.
  • the method is defined on-demand, and only when necessary, and on objects that really need it.
  • this allows for certain decoration/specialization patterns in Ruby

10. On classs using `define_singleton_method:``

class User
  attributes = [:name, :age, :sex, :address, :dob, :phone, :email]

  attributes.each do |attribute|
      define_singleton_method :"find_by_#{attribute}" do |*args|
          User.where("#{attribute} == #{args[0]}")[0]
      end
  end
end

User.find_by_name('rahul')
...
User.find_by_email('[email protected]')

Similar to the previous example. Now you know how Rails defines a whole gamut of convenience methods!

The advantages of the previous example hold here, too.

11. On all instances of a class using define_method:

class User
  attributes = [:name, :age, :sex, :address, :dob, :phone, :email]

  attributes.each do |attribute|
      define_method :"#{attribute}" do |*args|
      end
  end
end

User.new.age

This example employs define_method, which defines the specified method on all instances of the class. Contrast this with the previous example, where define_singleton_method takes arguments similarly, but defines them on the class.

Naturally, one might wonder what the behaviour of define_method is when self is an instance of the class.

This is not a case that Ruby permits, hence the following is an error.

class User
  def define_methods
    attributes = [:name, :age, :sex, :address, :dob, :phone, :email]

    attributes.each do |attribute|
        # `define_method` is not defined on instances of classes
        define_method :"#{attribute}" do |*args|
        end
    end
  end
end

User.new.define_methods

12. On instances using instance_eval:

class User
end

def define_age
    instance_eval do
      def age
         # this method is defined on `user` and `user` only.
         # No other instance of `User` will have this method.
      end
    end
end

user = User.new
user.define_age

User.new.name # hence, this is an error.

Notice that this manner of defining a singleton method is similar to defining a singleton method through define_singleton_method. If, to define a singleton method is all you want to do, you could use either form.

However, the general usage of instance_eval is to create a new lexical scope with self set as the object on which instance_eval is called. This scope has the advantage of being able to access the scope outside itself.

13. On classs using instance_eval:

class User
end

User.instance_eval do
  def age
  end
end

User.name
class Admin < User
end

Admin.name

Similar to the previous example, but with classs for instances. Although a bit esoteric, note that it is possible to inherit methods through inheritance this way,

14. On modules using instance_eval:

module User
end

User.instance_eval do
  def name
  end
end

User.name

This method provides another way to define methods that can be called on modules. It does not look immediately useful when compared to the method we discussed previously, but combined with instance_eval's ability to refer to the outer scope, it can be petty useful.

15. On instance of classs using class_eval:

Such a thing does not exist.

16. On classs using class_eval:

class User
  def name
  end
end

User.class_eval do
  def age
  end
end

User.new.age

This is very similar to defining the method using instance_eval, however there does lie a difference -- inside the body of the instance_eval, method definition happens on the metaclass, whereas inside the body of the class_eval, method definition happens on the class itself.

17. On instances of classs using class_eval in an extend-ed module:

module Details
end

Details.class_eval do
  def version
  end
end

class User
    include Details
end

User.new.version

This one is a bit confusing, but is similar to defining defining methods meant to be extend-ed by classs. Of course, the only good reason to use this approach is when you want to reference variables outside the class_eval.

18. On classs using module_eval in an include-ed module:

module Details
end

Details.module_eval do
  def version
  end
end

class User
    include Details
end

User.new.version

This is exactly the same as when you use class_eval.

19. On classs using class_eval in an extend-ed module:

module Details
end

Details.class_eval do
  def version
  end
end

class User
    extend Details
end

User.version

Confusing, but is similar to defining defining methods meant to be extend-ed by classs. Of course, the only good reason to use this approach is when you want to reference variables outside the class_eval.

20. On classs using module_eval in an extend-ed module:

module Details
end

Details.module_eval do
  def version
  end
end

class User
    extend Details
end

User.version

This is exactly the same as when you use class_eval.

21. On select instances of classs using the <object . > prefix:

class User
end

user = User.new
def user.age
    # this method is defined on the object, `user`, this object alone.
end
user.age

This defines singleton methods on instances. No other instance of User will have the method, :age defined on it by default.

22. On classs using the <object . > prefix:

class User
end

def User.type
end

User.type

This defines a singleton method on the class.

23. On modules using the <object . > prefix:

module User
end

def User.type
end

User.type

This defines a singleton method on the module, User. extend-ing/include-ing classs will not have access to this method.

24. Defining methods by passing strings to class_eval:: Consider the mattr_accessor access specifier, which is defined by Rails. It allows the programmer to set up getters and setters for module-level variables, which is not a provision Ruby comes with.

module Rails
    mattr_accessor :author_name, :sprockets_module

    def self.setup
        yield
    end
end

module Atrium::Sprockets
end

Rails.setup do |config|
    config.author_name = "Rahul Rajaram"
    config.sprockets_module = Atrium::Sprockets
end

A perusal of the above linked GitHub page clearly would show how Rails achieves this -- by passing method definitions in string forms. The following is a simplified version of the code.

class Module
    def mattr_accessor(sym)
        class_eval <<-METHODS
            def self.#{sym}=(obj)
              @@#{sym} = obj
            end

            def self.#{sym}
              @@#{sym}
            end
        METHODS
        end
    end
end

Instance methods, :author_name=, :author_name, :sprockets_module=, and :sprockets_module= are being defined when the method call mattr_accessor :author_name, :sprockets_module is made. The setters assign to class variables, and the getters read those class variables.

25. Private class methods using (class << self): In a lot of cases, you might need to make class methods private. There are only two ways to do this. The first uses (class << self; ... ;end).

class Details
    class << self
        private

        def age
        end
    end

    private

    def self.sex
        # not quite private
    end
end

26. Private module methods using instance_eval The second uses instance_eval.

module Details
    instance_eval do
        private

        def sex
        end
    end
end

27. Private module methods using (class << self):

In rare cases, you might need to make module methods private. There are only two ways to do this. The first uses (class << self; ... ;end).

module Details
    class << self
        private

        def age
        end
    end

    private

    def sex
        # not quite private
    end
end

28. Private module methods using instance_eval:

module Details
    instance_eval do
        private

        def sex
        end
    end
end

29. (Accidentally) on the metaclass using (class << self):

Beware of defining on self inside (class << self) because it opens up the scope of the metaclass.

class User
    class << self
        def self.age
            #
        end
    end
end

User.singleton_class.age # OK

User.age # Error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment