Adapter Design Pattern in Ruby

Introduction

The Adapter design pattern is used, when there are two or more objects, which need to communicate to each other, but unable to do so, because their interfaces do not match. And the adapter is kind of a bridge between these objects.

This design pattern is heavily used in Rails. Particularly in ActiveRecord adapters are implemented to communicate with different databases and provide a common interface, so you don't bother whether it is PostgreSQL, MySQL or any other database. In ActiveJob adapters are used to communicate with background job providers, and so on.

Example

It is always a good idea to write an adapter if you don't want to depend on particular implementation of some functionality.

Say, you want to count and delete some pages in a PDF. The simpliest solution is to use a prawn gem. Let's define an autonomous service which will be used in the application:

module PDFService
  class Document
    DEFAULT_ADAPTER = PDFService::PrawnAdapter

    attr_reader :adapter, :document
    delegate :count_pages, :delete_page, to: :document

    def initialize(options = {}, adapter = nil)
      @adapter = (adapter || DEFAULT_ADAPTER)
      @document = adapter::Document.new(options)
    end
  end
end

And a corresponding PrawnAdapter, which is default for this service:

module PDFService
  module PrawnAdapter
    class Document
      attr_reader :document
      delegate :delete_page, to: :document

      def initialize(options = {})
        @document = Prawn::Document.new(options)
      end

      def count_pages
        document.page_count
      end
    end
  end
end

The caller now can either use the default handler or explicitly specify which adapter to use:

service = PDFService::Document.new(template: @document.path)
service.count_pages
service.delete_page(1)

The service is built independent from a prawn gem. If the other day we decide to use another gem, we won't need to change a single line of code in the application but simply add another adapter file.

Conclusion

Adapter design pattern provides the following benefits:

  • Maintainable code
  • Encapsulated logic
  • Ease in business-logic extension