Exploring objects in Ruby and Ruby on Rails

object

This article is targeted at people who want to understand what objects are in the programming world and also for people who work with the Ruby programming language and/or Ruby on Rails. It will explore the concept of objects in Ruby and briefly touch upon what object oriented programming is.

One of the first things people read about the Ruby programming is that everything is an object. Some people don’t fully process what that means and just dive straight into Ruby on Rails to make web applications. I was somewhat guilty of this myself when I first started out but learned to appreciate the world of objects. Let’s take a step back and understand what exactly is an object in the programming world.

An object can be anything. This means an object can be a car, house, dream, color, number, action, controller, view, ect. We define(describe) these objects in Ruby using classes. So a Dream object might encapsulate(contain) a house and a car object within itself. And the car and house objects might encapsulate one color object each, respectively. Each of these objects can also hold built in data-types, which are also objects in Ruby, which will look at later. Some basic examples of already built in data-types in Ruby are Fixnum/Float (Numbers), Strings (“Hello World!”), and more complex examples are hashes, arrays, and symbols(ruby specific data-type, more on this later). Objects in programming world can get quite complex to the point of video game developers can make video games look near life-like to the untrained eye, but that topic is far from in scope for what we will cover here.

The code below can be executed, it defines(with classes) and Instantiates(creates class in memory) a dream object with a house and a car object and two color objects.

class Dream
  # skipping getters / setters

  def initialize(house, car)
    @house = house
    @car = car
  end

end

class House
  # skipping getters / setters

  def initialize(color, age)
    @color = color
    @age = age
  end

end

class Car
  # skipping getters / setters

  def initialize(color)
    @color = color
  end

end

class Color
  # skipping getters / setters

  def initialize(color_name)
    @color_name = color_name
  end

end

# object 1, a color
red_color = Color.new("Red")
# object 2, another color
blue_color = Color.new("Blue")
# object 3, a house
a_house = House.new(blue_color, 1945)
# object 4, a car
a_car = Car.new(red_color)

# object 5, creating an dream object. A class instance of Dream assigned to dream_object variable.
a_dream = Dream.new(a_house, a_car)

# checking the contents of the dream_object variable and printing to screen
puts dream_object.inspect

Running this simple ruby program and outputting the contents of the dream_object yields this.

commodore_64:~/dream_folder$ ruby dream.rb
#<Dream:0x0000000204ab08 @house=#<House:0x0000000204abd0 @color=#<Color:0x0000000204ac20 @color_name="Blue">, @age=1945>, @car=#<Car:0x0000000204ab30 @color=#<Color:0x0000000204ab58 @color_name="Red">>>

Indentation that makes it easier to read:

<Dream:0x0000000204ab08 
  @house=#<House:0x0000000204abd0 
    @color=#<Color:0x0000000204ac20 @color_name="Blue">, 
    @age=1945
  >, 
  @car=#<Car:0x0000000204ab30 
    @color=#<Color:0x0000000204ab58 @color_name="Red">
  >
>

The weird numbers are just addresses in memory(RAM) where these objects were stored when this program was running.

This program simulates a basic dream that contains a blue house from 1945 and a red car. In total we created 5 objects directly, A Dream(1) containing a House(1) and a Car(1) and Color(2) objects, for the house and car respectively.

This code is very simple and lacks the use of complex abstractions and methodologies directly such as Polymorphism and Inheritance but some of this is going on behind the scenes automatically. It also didn’t cover methods for setting and getting the values for the dreams, houses, cars, and color objects and methods for interactions between the objects.

For something like the Ruby on Rails source code that defines and relates the different nuances of an MVC web application framework you are bound see a lot more complexity than this simple example.

Remember, at the end of it all, everything is an object in Ruby. These objects talk and interact with each other using methods/functions. Methods/functions can and are usually wrapped within objects too, but we won’t get into that now to not over complicate things. These objects are defined using classes and can be instantiated. An instantiated class is an Object.

There is also the notion of a Module which is a collection of methods but modules don’t generally get instantiated more than once and their purpose is just to hold a grouping of methods that generally will all be related to each other and a similar subset of objects. For instance a “Trig” module might hold the methods for calculating sin and cosine and other trigonometric equations and the methods can be executed on numbers, which are Objects also in ruby, which we will see in a second.

Any line of code that is written with Ruby is doing something with an object or a set of objects. Even the simplest line of code can be creating, reading from, writing to, or even deleting an object(s). Classes are used to define objects and the BasicObject class is defined as a starting point in the Ruby programing language from which all other classes are defined from.

“BasicObject is the parent class of all classes in Ruby. It’s an explicit blank class.”
http://ruby-doc.org/core-2.4.0/BasicObject.html

“Object is the default root of all Ruby objects. Object inherits from BasicObject which allows creating alternate object hierarchies. Methods on Object are available to all classes unless explicitly overridden.”
http://ruby-doc.org/core-2.4.0/Object.html

Here is some very basic Ruby code.

var_1 = 1
var_2 = “one”

What we actually did by creating those 2 variables and setting them is we created two objects. ‘var_1’ is a variable set to an Object of class “Fixnum”. ‘var_2’ is a variable set to an Object of class “String”.

2.0.0-p648 :1 > var_1 = 1
=> 1
2.0.0-p648 :2 > var_1.class
=> Fixnum
2.0.0-p648 :3 > var_1.class.superclass
=> Integer
2.0.0-p648 :4 > var_1.class.superclass.superclass
=> Numeric
2.0.0-p648 :5 > var_1.class.superclass.superclass.superclass
=> Object
2.0.0-p648 :6 > var_1.class.superclass.superclass.superclass.superclass
=> BasicObject
2.0.0-p648 :7 > var_1.class.superclass.superclass.superclass.superclass.superclass
=> nil

And

2.0.0-p648 :1 > var_2.class
=> String
2.0.0-p648 :2 > var_2.class.superclass
=> Object
2.0.0-p648 :3 > var_2.class.superclass.superclass
=> BasicObject
2.0.0-p648 :4 > var_2.class.superclass.superclass.superclass
=> nil

BasicObject.superclass returns nil because BasicObject is the root class of all objects as mentioned before and is at the top of the hierarchy.

nil does have a class otherwise it would not adhere to ruby’s methodology of everything being an object, see below.

2.0.0-p648 :1 > nil.class
=> NilClass
2.0.0-p648 :2 > nil.class.superclass
=> Object
2.0.0-p648 :3 > nil.class.superclass.superclass
=> BasicObject
2.0.0-p648 :4 > nil.class.superclass.superclass.superclass
=> nil
2.0.0-p648 :5 > nil.class.superclass.superclass.superclass.superclass
NoMethodError: undefined method `superclass' for nil:NilClass

Even the Rails Ruby gem itself is made from objects, see below.

2.0.0-p648 :1 > Rails.class
=> Module
2.0.0-p648 :2 > Rails.class.superclass
=> Object
2.0.0-p648 :3 > Rails.class.superclass.superclass
=> BasicObject
2.0.0-p648 :4 > Rails.class.superclass.superclass.superclass
=> nil

We make all these objects interact with one another via methods. Every object has a standard set of methods.

2.0.0-p648 :002 > Object.methods
=> [:allocate, :new, :superclass, :freeze, :===, :==, :<=>;, :<, :<=, :>, :>=, :to_s, :inspect, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :nil?, :=~, :!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

We won’t dive into what each standard method does to the object we will show how using the standard object methods you can find which methods are available to you from within specific types of objects. Let’s for instance see what kind of methods we have available to us for our var_1 Fixnum object.

# ‘-’ takes the array difference, [1, 2, 3] - [1, 2, 3, 4, 5] = [4, 5]
2.0.0-p648 :007 > var_1.methods - Object.methods
=> [:-@, :+, :-, :*, :/, :div, :%, :modulo, :divmod, :fdiv, :**, :abs, :magnitude, :~, :&, :|, :^, :[], :<<, :>, :to_f, :size, :zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, :chr, :ord, :to_i, :to_int, :floor, :ceil, :truncate, :round, :gcd, :lcm, :gcdlcm, :numerator, :denominator, :to_r, :rationalize, :singleton_method_added, :coerce, :i, :+@, :quo, :remainder, :real?, :nonzero?, :step, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect, :polar, :conjugate, :conj, :between?]

Bonus: Even methods and Modules can boil down to objects.

2.0.0-p648 :1 > var_1.methods.first.class
=>; Symbol
2.0.0-p648 :2 > var_1.methods.first
=> :to_s
2.0.0-p648 :3 > var_1.methods.first.class
=> Symbol
2.0.0-p648 :4 > var_1.methods.first.class.superclass
=> Object
2.0.0-p648 :5 > var_1.methods.first.class.superclass.superclass
=> BasicObject

2.0.0-p648 :1 > Module.superclass
=> Object
2.0.0-p648 :2 > Module.superclass.superclass
=>BasicObject

So next time you are working on a rails application and writing Ruby code remember that what you are truly doing is manipulating objects and the way objects talk to each other. The “class” keyword at the top of your model or controller should be a reminder of this. Having this understanding will greatly help when you are trying to expand classes and when you are working with inheritance. In a future post, I’ll show you how you can use this knowledge to develop more robust classes.

I hope if you have gotten this far you’ve enjoyed the article. Feel free to share any thoughts, corrections, and remarks about Ruby, Ruby on Rails, and anything else this article inspired you to share.

Leave a Reply