How Ruby Objects Speak to Each Other. Like Really, What's Happening?


Sometimes your brain can't mentally wrap itself around certain simple concepts. This is about one of those sometimes.

It all began one day while studying Object Relations in Ruby...

Here's a class called Song. And the methods available to all instances of the Song class will have access to the #title & #artist methods. Only Song's instances who should know about it. Nobody elses business.

Now here comes along another Class object:

Everything's hunky-dory so far. And then this happens:

Why the Confusion?

  • Genetics
  • Another thing is the scope of methods- you have your instance, class and global ones. This had nothing to do with them.
  • If there was a more direct, linear relationship through class inheritance or throwing in a module I would've been fine. But there was no prior relationship between the classes
  • I had read programming order was maintained through strict encapsulation. If encapsulation didn't work then code you wrote for one class somewhere would inadvertently affect methods in some other class and the world would just burn in the ensuing chaos.
  • But mostly all this confusion was just my limited exposure to the concept of object-oriented programming.

The solution is really straight-forward and quite silly in retrospect. In line 6, my brain was taking drake as a variable, but drake is a predefined object. And that's where the magic happened. It's object relations 101. It's just a fundamental nature of how Ruby objects are designed. A truth that you accept and move along towards greater struggles. Nothing to see here, problem solved, case closed, adios. But thing is when you're really new to shit, you really don't know what the "truths" are and can end up questioning the universe.

The good thing about being confused about the small stuff is that it makes you wrestle with larger fundamental issues. And that can throw you down an interesting rabbit hole, where you end up learning things that might surprise you. And while literature is plentiful on the little stuff like code problems on StackOverflow & Google, larger concepts are more of a personal journey. Or I just need to find a good book.

What this Confusion Inadvertently Taught Me

In Ruby, everything is an object... That's possibly the first time someone has said that about Ruby.

But what does it mean??? You'd think maybe the programming community has a singular definition of objects which would make life easier for newbies. You would be a naive fool. Here's a list of some of the prominent object-oriented programming languages:
Javascript, Ruby, Python, Objective-C, Scala, Rust, C++, Simula, Rust, C#, Smalltalk.

There is no standard agreed definition. Lot of languages claim to be Object Oriented, but they often mean it different ways.
Object Oriented term coined by Alan Kay, inspired by Smalltalk, but insisted it was not meant for C++.

Jeff Foxworthy, prolific Rubyist & Avant-Garde idiom master.

Actually this definition came through John Cinnamond in "Ruby Beyond the Basics". In Ruby an object can be defined by two things: (1) Local State, (2) Responds to Messages.

Understanding Ruby Objects
What an object means is that it has a local state, and that it's capable of receiving messages. In Ruby, instance variables set the local state which are typically set during the initialize method. The local state of an object, is a collection of objects. So an object is a collection of references to other objects (local state) that respond to certain messages. Even those methods are objects.

Messages == Method calling. You can send a message to an object, object decides how it wants to respond. Can accept the message, raise an exception or give back a No Method Error.
Important point is all that an object can do is receive messages. It can't control what happens when those messages are received. Objects are autonomous, one object can't control the behavior of another object.

Special Objects: Exception to local state and messages. Primitive data types (can't be represented by another object/referenced to). Fixnum/Float/Rational, String, True/False, Hash, Array, Symbol
These special guys work like constructors instead of instantiating with a #new(). As opposed to can't put the value of the number in cause you'll go back into an infinite regress.
Forget about it, treat them like normal objects.

What is not a Ruby object?
-- Blocks on their own are not Ruby objects.

-- Operators

-- Debate on methods, with the larger consensus being not an object.

--Control structures


Duck Typing
A really interesting notion of Object Oriented languages is that the definition of a class is never finalized, because Ruby is a dynamic language. We can reopen classes to add more methods in. We can also define methods or remove them completely.

No way of knowing all the methods just by looking at Ruby source code, happens a lot in Rails. In ActiveRecord the methods in the model depend on the underlying database schema. No way of being certain what those messages are going to be. Other factors like which class they're being defined in, order of the files being loaded- the methods may be too late or too early. Apparently this is not really an issue for compiled languages. In Ruby developers think differently about type safety. They like to think about Duck Typing.

If it walks like a duck and quacks like a duck it probably is a duck.
So instead of asking about the class of an object, we should think about its behavior. The subtlety is Ruby doesn't care about classes rather than the messages being sent between objects. We should be thinking about the messages flowing between objects.

If you've written a lot of attr_accessors in your program time to revist for a redesign. Remember you should mostly be concerned with the flow of messages between objects, not with letting objects mess with the internal states of other objects.

Code Reuse
Code reuse is about sharing/communicating the code you've written. Two types of Reuse: Inheritance & Modules.
Ruby supports single inheritance. Every class in Ruby has exactly one superclass. If you don't specify a superclass to your class, it assumes it's an "Object" class.
If we want something more flexible, Ruby allows us to use modules. Module is just a collection of methods. Looks a lot like a class. You can include them in classes using the 'include' keyword. You get all the methods. The superclass of class is module, so classes are just special types of modules.
'Extend' is another interesting way to use modules. So we know that every object has a class, but (uncommon fun fact) every object also has a Singleton class which just pertain to that instance, & it's own methods.

Method Dispatch
Fancy way of saying, if I call a method how does Ruby know where that method is defined. The first place Ruby looks for a method definition is the singleton-class, then the obj's class, then it's superclass till it reaches basic object which has no superclass and it hits nil- so no matching method. And that's where the story would end and you see a "NoMethod Error". But it doesn't. Ruby goes back to the singleton class and now looks for a method called #method-missing. #method_missing takes 3 arguments: (name, *args, &block)
Mad respect to Ruby for being so helpful and really wanting to find this method that doesn't even exist. Ruby really knows how to make a programmer feel special. So method-missing is a method called when you call a method and it can't be found. And repeats the whole process (following up the inheritance chain). Then it gives us the NoMethodError.


The whole concept of the fundamental nature of Ruby objects & how they communicate using messages obviously goes a lot deeper than this, but just sticking to a beginner-friendly level. I'm happy I didn't have the Google vocabulary to find an easy answer to my initial query. The small simple details are easy to look up, but understanding the larger complex concepts is pretty important. It gives us more control as a programmer. The unintended byproducts of ignorance are illuminating.