Design pattern called
Adapter 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.
In Rails framework this design pattern is being heavily used. Particularly, in
ActiveRecord, adapters are implemented to communicate with different databases and supply developer with a common interface to use, 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.
Ok, let’s move on to practical cases and code examples.
It is always a good idea to write adapters if you don’t want to depend on particular implementation of some functionality(no matter in which entity this functionality is being wrapped: gem, standalone script, etc).
Say, you want to calculate pages count in pdf file, and you’ve read about simpliest solution: to use
prawn gem. Let’s implement autonomous service that we will use in application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
prawn adapter, which will be default for this service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
With this implementation we can now call
anywhere in the app to calculate pdf-document pages count. The service we built is totally autonomous and independent from
prawn. If the other day you decide to use another gem, you won’t need to change any single line of the code, all you will have to do is to add a new adapter. Cool, huh?
Let’s imagine that one of the core features of your application is communication with social networks, consider
- As your app will grow, Twitter API will also be improved, maybe version will be changed, new functionality added, etc
- Twitter gem you are using, will, of course, also be upgraded
And some day you will decide to upgrade gem version in order to gain new or improved functionality. The gem can be rewritten significantly since the day you started using it. Imagine the amount of work you will have to do, in order to upgrade for new version: check initilization, check all existing calls, rewrite tests related to different parts of your app, that are using this gem, etc. But in the same time, existing code should do the same, you still want to create statuses/update statuses/etc and maybe add some new calls to Twitter API, which weren’t available in the previous version.
Now, let’s see how adapter design pattern can rescue you from all this pain related to maintaining existing code which should do same things, and how easy it would be to migrate to a new gem version and to add new features.
First of all we want to create autonomous twitter service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Next, we need to implement adapter specific to the
twitter gem version we are currently using, with methods we need in app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
Now, having this implemented, in application we can use
1 2 3
Now we have all the stuff related to interaction with the gem(and thus with Twitter API) in one place. It is now easy to add new methods or to upgrade a gem version, since all we will have to do is to add new adapter. All the calls in the app to the service will remain the same.
Adapter design pattern usage in Rails app makes your life, as a developer, easier. Particularly, you obtain following benefits:
- Maintainable code
- Encapsulated logic
- Easy logic extension/enhancement