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 theprivatemethods 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
selfis, 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; ...,:nameis defined on instances", rather than "nameis 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
classs:
class User
def name
end
end
User.new.name:name will be defined on all instances of User.
- On
classs:
class User
def self.name
end
end
User.name:name will be defined on User.
- On instances of
classs through inheritance:
class Person
def name
end
end
class User < Person
end
User.new.nameUser inherits from Person. :name will be defined on all instances of Person and User.
- On
classs through inheritance:
class Person
def self.name
end
end
class User < Person
end
User.nameUser inherits from Person. :name will be defined on Person and User.
- On instances of
classs throughmodulemixins:
module Details
def name
end
end
class User
include Details
end
User.new.nameUser mixes-in Details. :name will be defined on all instances of User.
- On
classs throughmodulemixins:
module Details
def name
end
end
class User
extend Details
end
User.nameUser 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_methodhas 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.ageThis 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_methods12. 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.nameclass Admin < User
end
Admin.nameSimilar 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.nameThis 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.ageThis 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.versionThis 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.versionThis 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.versionConfusing, 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.versionThis 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.ageThis 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.typeThis defines a singleton method on the class.
23. On modules using the <object . > prefix:
module User
end
def User.type
end
User.typeThis 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
endA 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
endInstance 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
end26. Private module methods using instance_eval
The second uses instance_eval.
module Details
instance_eval do
private
def sex
end
end
end27. 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
end28. Private module methods using instance_eval:
module Details
instance_eval do
private
def sex
end
end
end29. (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