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