Hi Devs,
In this post i will try to explain how to use ruby mixins
for DRYing up your code. Let’s say I have Car
which can drive and honk. It can find a car with vin number or return count.
class Car
def self.find(vin)
puts "return car with VIN number #{vin}"
end
def self.count
puts "return number of cars"
end
def honk
puts "I like to yell at people"
end
def drive
puts "will try to avoid speed tickets"
end
end
# A Car can drive, honk
car = Car.new
car.drive
car.honk
# Find car by its VIN number
Car.find("123")
# Get total number of cars
Car.count
Now we get a requirement to add Truck
s to our inventory which do most of the things aCar
does. Let’s start DRYing the code.
First I will extract instance
methods using a module and use include
to make them available. In ruby we use include
to add instance
level methods from a module.
module VehicleBehavior
def honk
puts "I like to yell at people"
end
def drive
puts "will try to avoid speed tickets"
end
end
class Car
include VehicleBehavior
def self.find(vin)
puts "return car with VIN number #{vin}"
end
def self.count
puts "return number of cars"
end
end
Making progress. Now i will extract the class
level methods into a module and use theextend
keyword to make them available. In ruby we use extend
to add class
level methods from a module.
module VehicleBehavior
def honk
puts "I like to yell at people"
end
def drive
puts "will try to avoid speed tickets"
end
end
module VehicleData
def self.find(vin)
puts "return car with VIN number #{vin}"
end
def self.count
puts "return number of cars"
end
end
class Car
include VehicleBehavior
extend VehicleData
end
Good. But is there a way to combine both modules into one ? yes.
Every time a class includes module – Ruby will trigger the self.included
method on that module. It will also pass class as a parameter. In our case the Car
class will be the klass
argument.
module Vehicle
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def find(vin)
puts "return car with VIN number #{vin}"
end
def count
puts "return number of cars"
end
end
def honk
puts "I like to yell at people"
end
def drive
puts "will try to avoid speed tickets"
end
end
class Car
include Vehicle
end
Looking good. But is there is a cleaner way to do this Yes. ActiveSupport::Concern
require 'active_support/concern'
module Vehicle
extend ActiveSupport::Concern
def honk
puts "I like to yell at people"
end
def drive
puts "will try to avoid speed tickets"
end
class_methods do
def find(vin)
puts "return car with VIN number #{vin}"
end
def count
puts "return number of cars"
end
end
end
class Car
include Vehicle
end
car = Car.new
Car.find("blah")
car.drive
car.honk
ActiveSupport::Concern makes the syntax better and also has the advantage of gracefully handling module dependencies.
Now our Truck
class is as simple as this
class Truck
include Vehicle
end
Pretty cool right? Less code! Less bugs!! YaY.