-
Notifications
You must be signed in to change notification settings - Fork 1
Classes and Modules
#About classes Ruby classes are broadly similar to classes in object-oriented languages such as Java, however there are important differences. Key characteristics of Ruby classes include:
- They are Objects themselves (and therefore they have a class, which is Class (and Class has a class, which is itself))
- They are not a static specification processed at compile time as in e.g. Java - they are defined at runtime
- They are almost completely mutable - you can add, modify and delete methods, constants and instance/class variables at runtime, however you can't change what its superclass is (at least not directly)
- They don't really have a name or namespace of themselves - class objects are typically assigned to a Ruby constant and accessed/instantiated by accessing this constant
- Ruby constants are always attached to a module or class, and a namespace hierarchy emerges - top-level classes are objects in constants typically attached to the Object class, other classes can be stored in constants attached to top-level classes, etc.
- Methods are always attached to a class (or a module) and can be called on (and only on) instances of that class or its subclasses - there is no analog for the "static method" in other languages, i.e. methods which can be called on the class itself (but see singleton classes below)
- There is almost no Ruby syntax for defining classes - classes are defined by creating a class object with a specified superclass (typically using the
classstatement) and then executing Ruby code which adds components to the class object such as methods, constants and class variables. This code typically includes (but is not limited to): - the
defstatement, which adds a method to the in-scope class object - calls to methods defined on the Class or Module classes (and therefore callable on class objects), such as
attr_accessor, which sets up a publically read-only property - nested
classstatements which create classes within the outer class's namespace - Even classes in the Ruby core library are mutable - changing methods in core objects as part of a Ruby program is called "monkey patching"
#About modules Ruby modules are a bit like abstract classes - they are typically used in these scenarios:
- As a container for methods which can be "mixed in" to another module or class - mixing in a module is similar to inheriting all the methods in the module
- As an element in the namespace hierarchy for constants, containing only other constants (such as classes and modules)
Modules are very similar to Ruby classes with the following key differences:
- They can't be instantiated
- They don't have a superclass
- They can be "mixed in" to another module/class, whereas classes can't
- The Module class can be subclassed - Ruby disallows subclassing of the Class class
#About singleton classes and metaclasses If you have some knowledge of Ruby, you may already know that at face value Ruby appears to support some methods that don't fit in with the characteristics described above. In Ruby, you can:
- Define a method on a regular object, which can only be called on that object
- Define a method on a class or module which can be called on that class or module, as opposed to on instances of it
It's important to understand that behind the scenes, there is nothing special about these methods. What is special is that Ruby is defining these methods on the "singleton class" of the object, class or module.
All objects in Ruby have a singleton class (at least in concept, although in implementation they may not be created until they are needed). A singleton class is a class whose only instance is the one object it's attached to - methods defined on an object's singleton class are specifically callable on that object, and not on other objects which are also instances of the the object's "real" class. Therefore, Ruby can:
- Define a method specific to a regular object, by defining it on the object's singleton class
- Define a "static method" on a class/module which can be called directly on the class/module, by defining it on that class/module's singleton class
The singleton class of a class is called a "metaclass" - this distinction is made only because metaclasses have more complicated inheritance relationships than singleton classes, and the reason for this is that metaclasses are attached to classes which themselves have superclasses and can have mixed-in modules, whereas singleton classes are attached to objects which don't have any superclass or mixins.
#How all this works It is confusing to try to understand the relationships and interactions between objects, classes, modules, mixins, superclasses, singleton classes and metaclasses - objects can be related to classes and modules through all of these mechanisms, so how exactly does Ruby decide what set of methods can be called on an object, and what class or module's method implementation will be used?
The key to understanding this is to look at Ruby's internal model for these relationships, which is quite elegant and simple because it distills all of this into a single class for each object and a single-inheritance tree for each class, and then resolving method calls is straightforward - essentially a method call on an object is resolved to either the object's class or the first possible ancestor class of the object's class.
A review of the fundamental classes in Ruby and the internal state maintained for them
BasicObject:
- Is the fundamental root class of the whole Ruby class inheritance tree - all classes ultimately inherit from it
- All Ruby objects are instances of this class
- This class is not usually used directly - it exists because if classes don't want to have their method and constant namespaces populated by everything available in the Object class, they can avoid it by explicitly inheriting from this class
- State maintained for instances of this class and its subclasses:
- The instance's class (i.e. BasicObject or one of its subclasses)
- Instance variables
Object:
- Superclass is BasicObject
- Is in general the fundamental root class for all Ruby classes, unless classes explicitly decide to inherit from BasicObject instead
- Is an ancestor superclass of every class in the Ruby core library
- Is effectively the root constant namespace in Ruby
- By default, newly created classes inherit from Object
Module
- Superclass is Object
- State maintained for instances of this class and its subclasses:
- Methods attached
- Constants
- Class variables
- Superclass (this is nil to begin with in Modules, and you can't directly set this on Modules as you can with Classes)
Class
- Superclass is Module
- Ruby disallows subclassing Class and singleton classes
- State maintained for instances of this class:
- A flag indicating whether it is a singleton class or not
- Superclass is mandatory (except for BasicObject)
Every object has, in concept, a singleton class - however in practice they are only created when they are needed, otherwise there would be an infinite proliferation of singleton classes to create because singleton classes themselves also have singleton classes.
Lets say, for example, we have:
- a class
Personwhose superclass is Object - a class
Developerwhose superclass is Person - an object
devof classDeveloper
Singleton classes on non-class objects are simply inserted as a hidden element in the object's inheritance tree. At face value, the inheritance tree appears to be this:
dev <instanceof> Developer <subclassof> Person <subclassof> Object <subclassof> BasicObject
Internally, dev's singleton class is inserted into the mix - it is internally the class of dev, and its superclass is the "real" class of dev
dev <instanceof> <singletonclass:dev> <subclassof> Developer <subclassof> Person <subclassof> Object <subclassof> BasicObject
Note that the singleton classes of dev's class and it's superclasses (e.g. Developer, Person) are not included in dev's inheritance tree, and therefore dev can't access methods defined thereon. This is why an instance of a class can't access the class's "static methods", i.e. it's class's singleton class's methods.
Singleton classes defined on classes (including other singleton classes) are called metaclasses and are implemented slightly differently. Lets start with looking at what would happen if they are implemented the same way, with the Developer class as an example:
Developer <instanceof> <metaclass:Developer> <subclassof> Class <subclassof> Module <subclassof> Object <subclassof> BasicObject # if the world was simple...
There's nothing wrong with this, however Ruby has decided to add a bit extra. In the "static method" paradigm of other languages, a subclass inherits the static methods of its superclass - in Ruby, can subclasses be made to inherit singleton methods from their superclasses, so that e.g. the Developer class inherits "static methods" from the Person class?
Yes, they can, and do - a subclass's singleton class inherits from its parent's singleton class:
Developer <instanceof> <metaclass:Developer> <subclassof> <metaclass:Person> <subclassof> <metaclass:Object> <subclassof> <metaclass:BasicObject> <subclassof> ??? # Almost...
OK, that almost worked, but there's 2 unresolved problems with the scenario above:
- What is the superclass of the metaclass of BasicObject?
- Less obviously - developer's metaclass is no longer inheriting from Class, as it was in the basic scenario above. This means that you can't call Class methods on the Developer class, which would be wrong because Developer is obviously an instance of Class.
How could Ruby resolve this? Well, it sets the superclass of BasicObject's metaclass to Class.
Developer <instanceof> <metaclass:Developer> <subclassof> <metaclass:Person> <subclassof> <metaclass:Object> <subclassof> <metaclass:BasicObject> <subclassof> Class <subclassof> Module <subclassof> Object <subclassof> BasicObject # Ruby's internal reality
It turns out that these rules are sufficient to describe all of Ruby's singleton classes and metaclasses, and the result is a consistent and correct model. Even for metaclasses of metaclasses (meta-metaclasses), etc. - a diagram showing the network of singleton and superclasses looks quite complicated and bewildering, but it's really just an extrapolation of the rules and principles described here. Here's a diagram from the Ruby Class API documentation:
+---------+ +-...
| | |
BasicObject-----|-->(BasicObject)-------|-...
^ | ^ |
| | | |
Object---------|----->(Object)---------|-...
^ | ^ |
| | | |
+-------+ | +--------+ |
| | | | | |
| Module-|---------|--->(Module)-|-...
| ^ | | ^ |
| | | | | |
| Class-|---------|---->(Class)-|-...
| ^ | | ^ |
| +---+ | +----+
| |
obj--->OtherClass---------->(OtherClass)-----------...
Mixing in a module (mixin module) to a module/class (base class) works by inserting a hidden internal object called an ICLASS, which functions as a reference to the mixin module, into the superclass hierarchy of the base class. Instances of that base class then have access to the methods defined on the mixin module, and any changes to methods in the mixin module will already be seen in the base class.
If a module is mixed into another module, this causes the base module to have a hidden ICLASS object as its superclass - this is the only circumstance in which a module can have a superclass.
Specifically, the rules for mixing in a module are as follows:
- The mixin module, and each of its ancestor modules will be inserted into the base class's superclass hierarchy
- Each of the modules to be inserted is processed as follows:
- If it's already inserted somewhere in the base class's superclass hierarchy, it won't be inserted again (including if it's mixed in to one of the base class's ancestor classes)
- The mixin module will be inserted "directly above" the base class - its ICLASS will become the base class's superclass, and the ICLASS's superclass will become the base class's original superclass
- Ancestors of the mixin module will be inserted:
- If their prior mixin module has been (by this process just now, or otherwise) mixed in to the base class itself, directly above the prior mixin module
- Otherwise (i.e. their prior mixin module has been mixed in to a superclass of the base class), directly above the last module that was mixed in by this process, if any
- Otherwise (i.e. this is the first ancestor module of the mixin module which was not already in the base class's superclass hierarchy), directly above the base class
Note that because each ancestor module of the mixin module is explicitly mixed in, changes to the superclass hierarchy (via mixin) of a module are not reflected as additional mixins to modules/classes which have mixed in that module.
#Module/class names
Modules and classes do not intrinsically have names - although they are typically assigned to constants, they may not be. However, there are many cases where Ruby requires the name of a module/class, e.g. for writing error/warning messages and the default implementation of Object.to_s.
When the name of a module/class is required, Ruby will search through the constant namespace (starting with Object) for the class, and construct the name by appending ::<name> to the name of the first located enclosing module/class.
When a class/module successfully finds its name, it will remember the result forevermore, and subsequent name requests will not result in another search.
Generally, a module/class's name will be resolved when:
- Something requests its name, or
- It is the target of a
classormoduleexpression
The name described above can be called the "true" name - if a true name is not found, then a "temporary" name will be assigned to the module/class, which describes its own class (Class, Module or a subclass of Module) and a unique object ID similar to Object.to_s. This temporary name will be remembered and reused until a search for the true name is successful.
#Instantiation and allocator methods
There is no Ruby syntax for instantiating classes - they are typically created with API methods defined on the class, most commonly Class#new or an overriding definition in a class object's singleton class.
There is a special type of internal method called allocator methods, represented in this document with the name <alloc>. These are methods like any other, except they have a special method name which doesn't conform to Ruby name requirements, meaning they can't be accessed directly in Ruby code - they can only be defined and used internally through the Ruby C API.
The instance creation API described below makes use of allocator methods, basically requiring them to be the most fundamental methods for instantiating classes. There is a default implementation of the allocator method in the singleton class of BasicObject, and other classes defined in the Ruby C API sometimes override the allocator method in their singleton class to implement custom instance creation code which can be invoked by the instance creation API.
#Instance creation API
Class#new [ <args> ]
This is the method you almost always use to instantiate a class - it returns the object created.
First, it performs some checks and creates the object:
- If
selfis a singleton class, raise error - If
selfis a class which has not been properly "initialized" (i.e. the default implementation ofClass#initializehas not been called), raise error - Allocate the object with the
<alloc>method onself- by default this method creates a blank object
Then, it invokes what amounts to the object's constructor:
- Call the
initializemethod on the allocated object, passing along the formal arguments, and if present, the block
Class#allocate
This method is the same as Class#new except it doesn't invoke the object's initialize method.
BasicObject::<alloc>
Module::<alloc>
Class::<alloc>
The default BasicObject allocator function, and some custom allocators for Module and Class. They create and return objects of class BasicObject/Module/Class or subclasses thereof, which are blank (except that the object's class has been set)
private BasicObject#initialize
initialize is effectively the constructor method in Ruby - it is invoked by Class#new on its newly created object, passing along all arguments and block. This default implementation does nothing and returns nil.
private Module#initialize [ <block> ]
The default constructor for Module invokes, if present, the block that was passed to it, by passing that block to the default implementation of module_exec(self).
Returns nil.
Note that the module expression does not invoke initialize if it creates a module.
private Class#initialize [ <superclass> ] [ <block> ]
The default constructor for Class performs a number of functions:
- Assign the superclass of the class - from the first argument, if present, otherwise Object
- Invoke
inherited(self)on the superclass - If a block was provided, execute it the same way as
Module#initializedoes - Returns the class
It follows from this that you can override the Class#initialize method with Ruby code, select a superclass however you like, and assign it to the class by invoking this default implementation.
Note that this default implementation must be invoked at some point for each class created via API - until it's invoked the class won't have a superclass, and the Class#new method won't permit you to instantiate the class. Also, this implementation will raise an error if it's called twice on a single class.
Note that the class expression does not invoke initialize if it creates a class, rather it performs the same initialization actions as this default implementation.
private Class#inherited <subclass>
This method is invoked on a class (excluding singleton classes) whenever it is subclassed, with the subclass as a parameter. The default implementation does nothing.
#Mixin API
private Module#include <mod>+
Method used to include one or more modules in a class/module. Modules are included in reverse order so the first listed modules have the highest precedence in the inheritance chain.
Raises error if any of the arguments is not a module (including if they are a class).
For each module argument, in reverse order:
- Invoke
arg.append_features(self) - Invoke
arg.included(self)
Returns self
TOPLEVEL_BINDING::eval('self')::include
The singleton class of self in the top context (see execution context section) defines this method. In other words, if you invoke include from top-level Ruby code which is not nested in a class, module or method definition expression, you get this method.
This method will invoke the default functionality of Module#include on Object, unless the context is a source file that has been loaded with wrap=true, in which case it will honour the encapsulation and invoke on the Module instance that was pushed onto the class reference stack due to the wrap.
private Module#append_features <target_mod>
undef Class#append_features
Implements the actual mixing in of a module into a target module/class. Invoked by Module#include.
- Raises error if
<target_mod>is not a class or module - Mixes
selfinto<target_mod>
Returns self.
This method is undefined for Class objects because they can't be mixed in.
private Module#included
Invoked after append_features when a module is included.
Default implementation does nothing and returns nil.
Kernel#extend <mod>+
Method used to "extend" an object's callable methods with one or more modules (in other words, include those modules in the object's singleton class). Modules are extended in reverse order so the first listed modules have the highest precedence in the inheritance chain.
Raises error if any of the arguments is not a module (including if they are a class).
For each module argument, in reverse order:
- Invoke
arg.extend_object(self) - Invoke
arg.extended(self)
Returns self
private Module#extend_object <target_obj>
undef Class#extend_object
Implements the actual mixing in of a module into the singleton class of a target object. Invoked by Kernel#extend.
- Mixes
selfinto singleton class of<target_obj>
Returns <target_obj>.
This method is undefined for Class objects because they can't be mixed in.
private Module#extended
Invoked after extend_object when an object is extended.
Default implementation does nothing and returns nil.
#Cloning API
Kernel#dup
Creates a duplicate of an object:
- Raise error if an immediate type object
- Allocate a new object with
<alloc>on the "real" class of the object - Copy to new object:
- Tainted flag
- Untrusted flag
- Instance variables
- Finalizers
- Invoke
initialize_dup(<original_object>)on the new object
Kernel#clone
Creates a clone of an object. This is similar to Kernel#dup, however dup is intended to be a slightly less rigorous copy, acting almost as though creating a regular new object with sufficient parameters to the constructor to end up with the same internal state. In addition to the cloning done by Kernel#dup, it will:
- Clone the singleton class of the object, including copying over its:
- Tainted flag
- Untrusted flag
- Frozen flag
- Superclass
- Instance variables
- Constants
- Methods
- Clone the singleton class's metaclass too, unless the singleton class we are cloning is itself a metaclass (this restriction may be a bug)
- There is a bug in the 1.9.3 implementation, and this singleton cloning doesn't work correctly at present
- Invoke
initialize_clone(<original_object>)instead ofinitialize_dup(<original_object>) - Copy over the frozen flag
Kernel#initialize_dup <original_object>
Kernel#initialize_clone <original_object>
initialize_dup will be called on a newly dup'd object, and initialize_clone will be called on a newly cloned object. Their parameter is the object that this has been cloned from. The default implementation of both methods is to invoke initialize_copy(<original_object>).
Kernel#initialize_copy <original_object>
Default initialization for a newly cloned or duped object, via default implementations of initialize_dup and initialize_clone.
This default implementation does nothing except implement some odd checks that appear to be designed either to catch bugs in Ruby C API usage, or to catch scenarios where initialize_copy is invoked directly in unreasonable circumstances:
- Raises an error if object is frozen
- Raises an error if the "real" class of the object and
<original_object>are different - Returns the object
Module#initialize_copy <original_object>
This default initializer for dup and clone on Module (and Class).
This will raise an error if:
- The original object is BasicObject
- The original object is a singleton class
- The target object is class that has already been initialized (either through
.initializeconstructor or.initialize_copycloning). Note that this does not apply to modules. - The target object is frozen
- The target object has a different "real" class to the original object
If the target and original objects are the same object, this will do nothing but return the object.
This will then copy over from <original_object>:
- A clone of the singleton class, under certain odd circumstances. This cloning uses the same logic as
Kernel#clone. This procedure is undertaken if: - This method was invoked through
Kernel.dup, or - This method was invoked directly on an uninitialized class, or
- This method was invoked directly on a module whose singleton class has never been referenced
- The superclass (including if it's an included module)
- The instance variables (removing any already on the target object)
- The constants (removing any already on the target object)
- The methods (removing any already on the target object)
Returns the target object.
It would appear as though this method is intended to be usable in at least some of the following interesting scenarios:
- The common case, invoked via
duporclone - Overridden to provide custom behaviour, whose function would be expected to invoke the original
initialize_copyorinitializeif applied to aduporcloneof a class - Invoked directly on an uninitialized class e.g. one which has been
allocate'd, or which has beennew'd,clone'd ordup'dand due to overrides the relevant initializer callback was not invoked - Invoked directly on any module to reset all of its properties (including its inheritance chain)
#Reflection API
Module#name
Returns the true name of the module/class, or if it doesn't have one, nil.
Kernel#class
Returns the "real" (non-singleton, non-included-module) class of the object
Kernel#singleton_class
Returns the singleton class of the object
Kernel#instance_of? <class>
Returns true if <class> is the "real" (non-singleton, non-included-module) class of the object, otherwise returns false
Module#include? <mod>
Returns true if <mod> has been mixed in to the module/class, otherwise returns false
Kernel#kind_of? <mod>
Kernel#is_a? <mod>
Returns true if <mod> is in the inheritance hierarchy of the object (including regular classes, superclasses and mixed-in modules), otherwise returns false
Class#superclass
Returns the "real" (non-singleton, non-included-module) superclass of the class. Raises error if class has not yet been initialized with a superclass
Module#included_modules
Returns an array of modules which have been mixed in to the module/class, in order from closest to farthest up the inheritance chain.
Module#ancestors
Returns an array of the non-singleton classes and mixed-in modules in the module/class's inheritance chain, in order from closest to farthest up the inheritance chain.
A Ruby Language Reference
Copyright © by Michael Hore, 2016.
Introduction to This Document
Ruby Elements
- Classes and Modules
- Methods
- Blocks, Procs and Lambdas
- Execution Context and Closures
- Variables, Constants and Namespaces
- Types and Literals
- Ruby Expressions
- Operators
Syntax Grammar
Exceptions and Throw
Ruby Sourcefiles and Libraries
Multi Threading
Execution and Lifecycle