Improving Your Rails Codebase
Yes, Rails scaffolding is one of the big benefits of the framework. You, however, soon get to a point where it’s obvious that certain things are not a good fit for any of the three directories representing the MVC in your codebase. There are many ways (patterns) to address this, we’ll touch on a few.
Let’s take the example of a Fintech company enabling registered users to complete the KYC process by setting up their own profiles, and then doing some verification of the submitted information.
- The data required for KYC include a photo, proof of address, bank account, and NIN (National Identification Number).
- Users may skip the KYC setup stage while creating an account.
- Users need to be KYC compliant before they can make transactions above a set limit (say NGN 1k).
- The company verifies the data submitted by the user before approving the user’s profile as KYC compliant.
- The company sends periodic reminders to registered users who aren’t KYC compliant.
Secondly, while setting up the KYC profile, users don’t want to wait until you’ve verified their information before they can successfully make a submission. If you need more information on the submitted KYC data you may notify the user later.
Let’s get a bit technical. Just a bit.
You probably need some:
- model to represent the KYC entity.
- view to render the form to get the information.
- controller action to map the route and initiate the setup process.
Asides from the above, you want another process that is responsible for how you handle the submitted data. You might want to:
- Store the photo in a 3rd party service e.g S3, Filestack, Cloudinary, etc.
- Make some external API calls to verify the bank details, address, and NIN.
- Send an email to the user to confirm a successful KYC data submission, or inform them of any problems that occurred while verifying their submission.
To carry out the above, you definitely don’t want to have the logic in your MVC designated folders. Since this should be handled by a different process, it might be a good time to think of background workers. Uhm, but you don’t want to make an upload or call verification APIs directly in a worker, maybe create a service object to do this, then call that object from the worker? That would make your code organization neater, and testing your code easier. Sounds like a plan!
Service objects? Yeah, service objects are SOLID compliant POROs (Plain Old Ruby Objects). A service object does one thing in your domain logic and does it well.
Where should you keep your service objects? You can create a directory for them within the ‘app’ directory of your project. Having it there would be great for autoloading constants from your POROs.
# app/workers/address_verification_worker.rbclass AddressVerificationWorker
end# app/services/address_verification_service.rbclass AddressVerificationService
Some folks prefer domain objects to service objects, while I think both have reasonable use cases, this piece isn’t about the debate.
If everything goes well up until this point, you should be able to tell which users are KYC compliant, which ones are currently in the process of being verified, and those who belong to neither group.
You want to send periodic reminders to the last set of users. This can be in form of emails, in-app pop-ups, push notifications, SMS, whatever. As much as possible, you want to ensure that it does not impact the request/response cycle of users interacting with your app. Again, it’s better to run this flow through a different process, probably in the background.
A rake task would be useful for defining operations that would be scheduled for the periodic reminders. You might be tempted to put all the associated logic in the rake task😈.
This is your coding angel saying ‘Resist that temptation’😇.
Use a PORO (e.g a service object) to make testing easy. If the operation would run for a long time, you should consider using a background process.
By the way, whatever makes testing (specs) easy probably makes debugging easier. Just saying :)
This paradigm isn’t exclusive to Rails. Feel free to adapt as applicable to you.
I hope this gives you some idea of what should be synchronous or asynchronous, what needs to be part of your user flow and what doesn’t, how to ‘hand out responsibilities’ to entities in your codebase, and arrange your files.