Objects
Let’s create a class to represent blog posts.
In Python
class BlogPost:
def __init__(self, title, body):
self.title = title
self.body = body
self.published = False
def publish(self):
self.published = True
Let’s play with it:
post = BlogPost(
title="How I built my blog using Kubernetes, Lambda, and React.",
body="Wordpress was an insult to my intelligence and so I ...")
post.publish()
I want to be able to print the title of blog posts. There are two ways to accomplish this.
The first one is by simply printing the attribute:
The second one is to add a __str__
method to our class:
class BlogPost:
def __init__(self, title, body):
self.title = title
self.body = body
self.published = False
def publish(self):
self.published = True
def __str__(self):
return self.title
Now we can print the title more easily:
What if you want to change the title of a post?
Easy:
post.title = "How I built build my blog using modern instead of simple tools."
In Ruby
Let’s do the same in Ruby:
class BlogPost
def initialize(title, body)
@title = title
@body = body
@published = false
end
def publish
@published = true
end
end
Playing with classes is just as easy:
post = BlogPost.new(title="How I built my blog using Kubernetes, Lambda, and React.",
body="Wordpress was an insult to my intelligence and so I ...")
post.publish
I want to print the title of a post. Let’s try:
Oh no! undefined method `title'
In Ruby, accessing instance variables isn’t possible. You need a getter:
class BlogPost
def initialize(title, body)
@title = title
@body = body
@published = false
end
def publish
@published = true
end
def title
@title
end
end
You can’t set attributes directly either — you need a setter:
class BlogPost
def initialize(title, body)
@title = title
@body = body
@published = false
end
def publish
@published = true
end
def title
@title
end
def title=(new_title)
@title = new_title
end
end
Now we can play:
puts post.title
post.title = "How I built build my blog using modern instead of simple tools."
What about the __str__
trick we used in Python?
class BlogPost
def initialize(title, body)
@title = title
@body = body
@published = false
end
def publish
@published = true
end
def title
@title
end
def title=(new_title)
@title = new_title
end
def to_s
@title
end
end
Ruby vs Python Objects
Wait so, it looks like both are readable enough. True, but check this out:
class BlogPost:
count = 0
def __init__(self, title, body):
self.title = title
self.body = body
self.published = False
BlogPost.count += 1
def publish(self):
self.published = True
def __str__(self):
return self.title
We can access the number of posts with BlogPost.count
or post.count
.
In Ruby:
class BlogPost
@@count = 0
def initialize(title, body)
@title = title
@body = body
@published = false
@@count += 1
end
def publish
@published = true
end
def title
@title
end
def title=(new_title)
@title = new_title
end
def to_s
@title
end
def count
@@count
end
end
Now we can access post.count
but we can’t access BlogPost.count
like in Python.
Since this is a class variable, we need to be able to access it from the class itself.
class BlogPost
@@count = 0
def initialize(title, body)
@title = title
@body = body
@published = false
@@count += 1
end
def publish
@published = true
end
def title
@title
end
def title=(new_title)
@title = new_title
end
def to_s
@title
end
def count
@@count
end
def self.count
@@count
end
end
Now we can do BlogPost.count
. But we’d rather not be able to do post.count
because it
could be confused with a regular instance variable.
class BlogPost
@@count = 0
def initialize(title, body)
@title = title
@body = body
@published = false
@@count += 1
end
def publish
@published = true
end
def title
@title
end
def title=(new_title)
@title = new_title
end
def to_s
@title
end
def self.count
@@count
end
end
Now we can only access count
from the BlogPost
class. Can we set the class variable though?
Let’s try:
Of course not! We never defined a setter for this variable.
What about in Python?
It works. We can even do it from the class:
Ruby objects are more straighforward?
I don’t know about you but I think that it’s easier to see the difference
between class and instance attributes in Ruby.
Setters and getters allow you to clearly specify which attributes are
readable and writable. You can protect your class attributes by not
implementing a setter. In Python, it’s easy to accidentally write to
the count
attribute — which can break your program.
By default, both post.count
and BlogPost.count
returns the value
of the attribute but it would be easier to notice that it’s a class
attribute if it was only accessible from the class.
What do you think about dunder methods (__str__
)? Personally, I find
to_s
to be clearer. It clearly means that calling this method will
return a string representation of this object. In Python, you will
never do post.__str__
for example. Instead, you’ll do str(post)
.
The str
method will call the __str__
method for you and return
the result. Sure, all roads lead to Rome but in terms of straighforwardness,
they’re not all equal.
More on dunders
Check this out:
class Matrix:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, to):
return Matrix(self.x + to.x, self.y + to.y)
def __str__(self):
return f"({self.x}, {self.y})"
Let’s play:
a = Matrix(1, 1)
b = Matrix(2, 2)
print(a + b)
You should get (3, 3)
back.
The same thing in Ruby:
class Matrix
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def +(to)
return Matrix.new(@x + to.x, @y + to.y)
end
def to_s
return "(#{@x}, #{@y})"
end
end
Play with it:
a = Matrix.new(1, 1)
b = Matrix.new(2, 2)
puts a + b
As you can see, there’s no need to know about special dunder methods in Ruby.
If you want your classes to respond to the +
operator, simply implement a +
method in your class.
I find this to be more readable than Python’s __add__
. Behind the scenes,
calling +
with your objects will call add
that will call your object’s
__add__
method.
Neo needs to dodge bullets fast and implementing his matrices in Ruby will
provide for more straighforward calculations.
Class inheritance
Let’s implement a view to create objects in the DRYest way possible:
class View:
...
class ProcessFormView(View):
...
class ContextMixin:
...
class SingleObjectMixin:
...
class FormMixin:
...
class ModelFormMixin:
...
class BaseCreateView(ContextMixin, SingleObjectMixin, FormMixin, ModelFormMixin, ProcessFormView):
...
class TemplateResponseMixin:
...
class SingleObjectTemplateResponseMixin:
...
class CreateView(TemplateResponseMixin, SingleObjectTemplateResponseMixin, BaseCreateView):
def form_valid(self, form):
...
Which one of these classes has the form_valid
method that I’m overriding? In other words,
if I call super().form_valid(form)
, which class is super()
referring to? To figure that out,
Python uses an algorithm called C3
to
implement something called Method Resolution Order (MRO).
Unless you have Pycharm
installed, it’s almost impossible to figure that manually.
As a matter of fact, a whole website
was created just for helping people figure out the
method resolution order of Django’s class based views. Python has full support for multiple inheritance and developers use it lavishly.
In Ruby, multiple inheritance is impossible:
class View:
...
end
class ProcessFormView < View:
...
end
module ContextMixin
...
end
module SingleObjectMixin
...
end
module FormMixin
...
end
module ModelFormMixin
...
end
class BaseCreateView < ProcessFormView
include ContextMixin
include SingleObjectMixin
include FormMixin
include ModelFormMixin
end
module TemplateResponseMixin
...
end
module SingleObjectTemplateResponseMixin
...
end
class CreateView < BaseCreateView
include TemplateResponseMixin
include SingleObjectTemplateResponseMixin
def form_valid(form)
...
end
end
A wise man once said “Favor inheritance over composition”. He was executed for heresy.
As you can see, in Ruby, you’re forced to use composition instead of inheritance when you
need to share behavior.
In Python, mixins are simply classes. You can instantiate them and they can hold state.
In Ruby, mixins are modules and can’t be instantiated. You won’t have to worry about
MRO like in Python because:
- You’re inheriting from a single super class (no need for MRO).
- The last mixin will override anything with a similar name from the previous one.
- No constructors are being called.
In Python, you have to pretend like you’re doing composition when you’re actually
just doing multiple inheritance. In Ruby, composition is baked into the language.
Since Ruby modules aren’t classes, they’re likely to be simpler as well — a big win for readability.
Conclusion
Look at these instructions:
- Take a step to your right.
- Then 1 step to your left.
- Then left again.
And this one:
- Take a step forward.
While both languages are much easier to read than say, PHP or Java, Ruby takes it
a step further by allowing you to write code that can be understood at a single glance.