3 Ways to Create Classes in Ruby
Classes are first-class objects in Ruby.
All classes happen to be instances of Class
. In other words, classes are Class
objects
just like 'hello'
and 'world'
are String
objects.
Out of the box, there are 3 ways to create classes in Ruby.
Use the class
Keyword
In the vast majority of the cases, you would use the class
keyword to create a class.
This is usually called a class definition:
class Customer
attr_reader :full_name, :card_type, :card_number
def initialize(full_name, card_type, card_number)
@full_name = full_name
@card_type = card_type
@card_number = card_number
end
end
# Creating and using a Customer object:
customer = Customer.new('Thuva Tharma', 'visa', 1234)
customer.full_name #=> Thuva Tharma
customer.card_type #=> visa
customer.card_number #=> 1234
If you're a Rubyist, you've seen and done this so many times. So, let's move on.
Use Class.new
Just like how we created a customer with Customer.new
in the example above, we can create
a class with Class.new
:
Customer = Class.new do
attr_reader :full_name, :card_type, :card_number
def initialize(full_name, card_type, card_number)
@full_name = full_name
@card_type = card_type
@card_number = card_number
end
end
This is functionally equivalent to regular class definition, but more explicit.
By convention, classes are referenced by constants in Ruby. This is why we're assigning
the object returned by Class.new
to Customer
constant, but we can assign it to
a regular variable as well. On the other hand, if you don't use a constant in a
regular class definition, you will get an error:
SyntaxError: class/module name must be CONSTANT
Now, why would you ever want to use Class.new
over regular class definition? Let's
look at creating a custom error class (for good reason, custom error classes should
inherit from StandardError
):
class CustomerError < StandardError
end
# This usually gets shortened as:
class CustomerError < StandardError; end
You can pass in an argument to Class.new
to specify the super class for the class
you're creating. With this in mind, you can define CustomerError
more elegantly:
CustomerError = Class.new(StandardError)
This is such a minor improvement, but a good use-case nonetheless. Another use-case
for Class.new
might be in testing. If you need to define classes only to be used
by your tests, you don't want to pollute the global namespace with the test class
constants. You can use Class.new
to store the test classes in local or instance
variables.
Use Struct
Struct
is available as part of Ruby's standard library, and you don't need to
require
anything to use it. Struct
lets you create classes for data container
objects without any boilerplate:
Customer = Struct.new(:full_name, :card_type, :card_number)
This has all the features of Customer
class we created with regular class
definition as well as using Class.new
above. In addition, the customer objects
will have equality as per value object
semantics:
customer1 = Customer.new('Thuva Tharma', 'visa', 1234)
customer2 = Customer.new('Thuva Tharma', 'visa', 1234)
customer3 = Customer.new('Thuva Tharma', 'amex', 1234)
customer1 == customer2 #=> true
customer1 == customer3 #=> false
On the other hand, we lose arity checking for arguments passed in to Customer.new
.
In other words, you don't get ArgumentError
if you pass in less arguments:
# full_name, card_type, card_number are all nil
Customer.new
# card_type, card_number are nil
Customer.new('Thuva Tharma')
# card_number is nil
Customer.new('Thuva Tharma', 'visa')
# full_name, card_type, card_number are all present
Customer.new('Thuva Tharma', 'visa', 1234)
This is not good. But, if you limit using Struct
to create inner classes for
value objects, it's still safe and useful:
class Customer
# Internal: Class to create credit card value objects
CreditCard = Struct.new(:holder, :type, :number)
attr_reader :full_name, :card
def initialize(full_name, card_type, card_number)
@full_name = full_name
@card = CreditCard.new(full_name, card_type, card_number)
end
end
Conclusion
In Ruby, you can create classes using the class
keyword (class definition),
Class.new
, and Struct
. Although you would use regular class definition
in the vast majority of cases, Class.new
and Struct
would sometimes come
in handy if you know how to use them.