Object-Oriented Ruby Basics

sjamescarter

Stephen Carter

Posted on May 8, 2023

Object-Oriented Ruby Basics

It's no surprise that Object-Oriented Programming (OOP) is the most popular programming paradigm. OOP is actually very similar to the way that we think. Our world is so incomprehensibly complex that our perception of reality is reduced to a simpler interface—much like a computer program. We don't see every single unique tree we drive past. We see objects that resemble a broad category of tree. Having low-resolution models of a variety of objects allows us to interface with the world without becoming overwhelmed. We constantly update our models of the world based on our experiences. Occasionally, we have to debug—or completely rewrite—our models due to tragedy, loss or incorrect assumptions.

However, we don't only see the world in low-resolution. We also have complex high-resolution perceptions as well. Recalling my grandparents' walnut tree with the tire swing or my parents' pear tree in the backyard returns a trove of specific memories and sensory information. I can "feel" the texture of the bark, "see" the color of the leaves, and "taste" the sweetness of the fruit. I know these two trees very well.

Classes and Instances

In OOP, we write low-resolution models called classes that generate high-resolution objects called instances. Extending the analogy above, Tree is the class and my grandparents' walnut tree is the instance. My parents' pear tree is another instance of the Tree class.

Class

class Tree
end
Enter fullscreen mode Exit fullscreen mode

This example in ruby is the beginning of a model class: Tree. Class names are always capitalized and singular in ruby. (Why Ruby? While many languages can be used for OOP, Ruby was specifically created with OOP in mind.)

OOP object models contain both data (attributes) and code (methods). This Tree class is currently lacking in both categories. Attributes are stored in variables. Instance variables begin with @ while class variables begin with @@. Attributes are read, written or modified through methods. Instance methods start with def method_name. Class methods start with def self.method_name.

class Tree

  def name= name
    @name = name
  end

  def name
    @name
  end

end
Enter fullscreen mode Exit fullscreen mode

While it's still not much, here is a Tree class that can create and retrieve tree names. The first method .name= writes the instance variable: @name. This is called a setter method. The second method .name reads the instance variable: @name. This is called a getter method.

Instance

grandpas_tree = Tree.new 
#=> #<Tree:0x00007f87799b9af8>
grandpas_tree.name = 'Grandpa\'s Tree'
#=> "Grandpa's Tree"
grandpas_tree.name 
#=> "Grandpa's Tree"
grandpas_tree
#=> #<Tree:0x00007f87799b9af8 @name="Grandpa's Tree">
Enter fullscreen mode Exit fullscreen mode

So here is the first instance of Tree. Renaming the tree is just as easy as calling the setter method again. Verify with the getter method.

grandpas_tree.name = 'Tire Swing Tree'
#=> "Tire Swing Tree"
grandpas_tree.name 
#=> "Tire Swing Tree"
Enter fullscreen mode Exit fullscreen mode

Macros

Getter and setter methods are useful and all, but there is an easier way. Macros are code that write code. Three macros that are particularly helpful are attr_reader (attribute getter), attr_writer (attribute setter) and attr_accessor (attribute getter & setter).

class Tree
  attr_reader :name
  attr_writer :name
end
Enter fullscreen mode Exit fullscreen mode

Refactoring the Tree class this way still provides the same functionality as before. Macros use symbols to represent the variables they access.

class Tree
  attr_accessor :name
end
Enter fullscreen mode Exit fullscreen mode

This is the simplest way to refactor the Tree class. Now many attributes can be added efficiently.

class Tree
  attr_accessor :name, :type, :age, :height
end
Enter fullscreen mode Exit fullscreen mode

Self

Since a class will have many instances, the word self is a designated keyword used to access a given instance within a method. Self is also used at the beginning of class methods to differentiate them from instance methods.

class Tree
  # macros
  attr_accessor :name, :type

  # class variable
  @@all = []

  # special instance method called when a new instance is created: Tree.new('Grandpa\'s Tree', 'walnut')
  def initialize(name, type) 
    @name = name
    @type = type
    # the instance is shoveled into the @@all array upon initialization
    @@all << self
  end

  # class method
  def self.all
    @@all
  end
end
Enter fullscreen mode Exit fullscreen mode

This updated Tree class creates tree instances with name and type attributes. Calling Tree.all returns an array of all the tree instances that have been created. With method chaining, Tree.all.count will return the number of Tree instances created. (The .count method is built-in to ruby.)

Tree.new('Mom\'s', 'pear')
#=> #<Tree:0x00007f877f837710 @name="Mom's", @type="pear"> 
Tree.new('Grandpa\'s', 'walnut')
#=> #<Tree:0x00007f87799b9af8 @name="Grandpa's", @type="walnut"> 
Tree.new('Christmas', 'white pine')
#=> #<Tree:0x00007f877f82d170 @name="Christmas", @type="white pine"> 
Tree.all
#=> [#<Tree:0x00007f877f837710 @name="Mom's", @type="pear">, #<Tree:0x00007f87799b9af8 @name="Grandpa's", @type="walnut">, #<Tree:0x00007f877f82d170 @name="Christmas", @type="white pine">]
Tree.all.count
#=> 2
Enter fullscreen mode Exit fullscreen mode

Inheritance

Inheritance creates a hierarchy of classes by which a child or subclass can access all of the attributes and methods of the parent or superclass. To write an inheritance the subclass is "less-than" the superclass: class Tree < Forest.

class Forest 
  attr_accessor :location, :area, :protected_status

  def protected
    @protected_status = true
  end
end

# The Tree class inherits attributes and methods from the Forest class.
class Tree < Forest
  attr_accessor :name, :type

  @@all = []

  def initialize(name, type) 
    @name = name
    @type = type
    @@all << self
  end

  def self.all
    @@all
  end
end
Enter fullscreen mode Exit fullscreen mode

While this example may be contrived, inheritance is particularly helpful when using libraries such as ActiveRecord or Sinatra. Libraries such as these have a host of methods that keep code DRY and allow the programmer to focus on writing unique methods specific for the individual app rather than writing methods that are the same in every app.

As a general rule in Ruby, methods cap out at 5 lines and classes cap out at 100 lines of code. This keeps classes and methods from becoming overly complicated and unmanageable. Complying with this standard encourages the simplest, cleanest code.

Conclusion

Classes, instances, attributes, methods, macros, self and inheritance are some of the basics in OOP. These tools allow programmers to write code that models the world around them. It's just a hunch, but I'm guessing you'll notice a lot more trees today.

Credit

Photo by Johann Siemens on Unsplash

💖 💪 🙅 🚩
sjamescarter
Stephen Carter

Posted on May 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related