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.
- What is self in a given context?
- 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.
- Explore pry. Particularly, make sure you debugging with pry (using
binding.pry
) - Using
binding.pry
, you perform object introspection and reflection. - 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 methodssingleton_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 theprivate
methods defined on the object.
- When trying to list methods in any of the above fashions,
sort
-ing them can make the output more readable. - When having difficulty with understanding what
self
is, fallback to "Metaprogramming in Ruby: It's all about the Self - There are no "class methods" in Ruby. Class methods are essentially instance methods defined on classes, which are all instances of the class,
Class
. - 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.
- 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
".
- On instances of
class
s:
class User
def name
end
end
User.new.name
:name
will be defined on all instances of User
.
- On
class
s:
class User
def self.name
end
end
User.name
:name
will be defined on User
.
- On instances of
class
s 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
.
- On
class
s 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
.
- On instances of
class
s throughmodule
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
.
- On
class
s throughmodule
mixins:
module Details
def name
end
end
class User
extend Details
end
User.name
User
mixes-in Details
. :name
will be defined on User
.
- 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.
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 class
s 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 class
s 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 class
s for instances. Although a bit esoteric, note that it is possible to inherit methods through inheritance this way,
14. On module
s 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 class
s using class_eval
:
Such a thing does not exist.
16. On class
s 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 class
s 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 class
s. Of course, the only good reason to use this approach is when you want to reference variables outside the class_eval
.
18. On class
s 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 class
s 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 class
s. Of course, the only good reason to use this approach is when you want to reference variables outside the class_eval
.
20. On class
s 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 class
s 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 class
s using the <object . > prefix:
class User
end
def User.type
end
User.type
This defines a singleton method on the class
.
23. On module
s 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 class
s 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