We have several Rails applications using the same ActiveRecord, Book. For simplicity let us assume we have only these two:

  • BookWarehouseApp (that deals how new book are added, or other inventory processes)
  • BookLibraryApp (that deals with how book are being borrowed and returned)

BookWarehouseApp has read and write access to the books table in a Postgres DB. BookLibraryApp has a read-only access to the same books table (in production and dev). In test env, BookLibraryApp has write access too (to allow for test setup).

Both are tested in a classical Rails + MiniTest setup.

Now there is a lot of shared behavior between BookWarehouseApp::Book and BookLibraryApp::Book (and for example BookWarehouseApp::Book has a lot of validation/creation code that is absent of BookLibraryApp::Book, so they are not identical. Moreover, they both inherit from ApplicationRecord in their respective app and both ApplicationRecord classes have their respective life in their own app. We also have other objects that deal with books (call them BookSerializer and BookPoro).

To avoid maintaining the same code in two places, we are thinking of extracting Book, BookSerializer and BookPoro to a gem.

If Book was not an ActiveRecord, it would be fairly straightforward. But now I am bit confused about the best way to setup that gem and test it up.

Among the options, I have considered and the difficulties I see (possibly because I have never done that before):

  1. Define a Book (including ActiveModel::Model) class in the gem. It will be overridden in apps that require the gem, but it will allow us to factorize and test other poros that depend on it.

    • pros: no DB setup required to test the gem, no migration to write for future users of the gem
    • cons: we are not sharing actual Book behavior (only poros) and we are not making life easy for future users of the gem, we cannot share any ActiveRecord type hook (or at least not test it in a convincing fashion).
  2. Define a BookLike/Bookable concern/module in the scope of the gem and let BookWarehouseApp::Book and BookLibraryApp::Book extend/include it.

    • pros: no DB setup, behavior shared, no migration to write
    • cons: we are not making life easy for future users of the gem, cannot share ActiveRecord type hooks (or least not test them)
  3. Define a Book class that inherit from ActiveRecord::Base and inherit from it in the apps.

    • pros: possible to test the full behavior
    • cons: how can we deal with the fact that each app has an ApplicationRecord class with different behaviors, suppose to setup DB interaction to test the gem, suppose to write some kind of migration generator to really offer a plug and play experience to future users.

I am thinking option 2 is reasonable, but it is not obvious how to deal with the underlying schema of books. A possibility is to have a FakeBook class there (for test purposes) that uses ActiveModel attr_accessors. We could also simply give an example of migration for users of the gem (in the spirit of 'we are all adults and can copy paste a migration').

Any idea or suggestion on how to deal with this situation welcome.

  • I'm interested in answers to this. My preferred solution would be to decouple these multiple applications into their own databases with their own models that are appropriate, using data sharing mechanisms to update between the applications, but that's probably not a feasible solution.
    – Thomas Owens
    Jun 1 '20 at 15:52
  • To be precise about the setup, the second app (with read only access) is reading data off a replica.
    – sunless
    Jun 2 '20 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.