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.