Mixins are a bit of a touchy spot for me. I am in a love hate relationship when it comes to them. In some cases, they work brilliantly and in other cases they hide complexity.
I have a few rules that I try to follow when I am considering using mixins.
- Does it hide complexity / indirection?
- Will it benefit the code base to share code?
- Will it override methods or will methods need to be overriden?
Moving methods into mixins to just move them is not sufficient enough to warrant the need for mixins. It is something that should be used to refactor once a pattern is established. Mixins should only be used for adding abilities to classes.
Let’s take a look at an example. We need an ability to revoke tokens mixed into three separate classes.
module Revokable
# Sets when the token was revoked
# @return [void]
def revoke
@revoked_at = Time.now
end
# Check to see if the token was revoked
# @return [TrueClass,FalseClass]
def revoked?
!!@revoked_at
end
end
The personal token represents a token that belongs to a user and never expires, but it can be revoked.
class PersonalToken
include Revokable
attr_accessor :id, :token, :user_id
end
The access token represents a token that is only available for a limited time.
class AccessToken
include Revokable
attr_accessor :id, :token, :user_id, :refresh_token_id
def expire
@expired_at = Time.now
end
def expired?
!!@expired
end
end
The refresh token never expires but is only used for getting another access token.
class RefreshToken
include Revokable
attr_accessor :id, :token, :user_id
end
We have accomplished is adding an ability to three classes without hiding a lot of complexity. If you find that your classes have too many methods defined, that should be a sign that it is too complex. But, do not immediately reach for mixins just because it makes the class less cluttered. It actually hides the mess as opposed to solving the issue.
Use mixins wisely!