Once the team has started to adopt even an embryo of styleguides it's time to move onto the meatier things. Again, it helps to put some guiding lines for the team to see how and where to go. In the same principle of the styleguide it can be really helpful to define, with the team, ways to organise the code base and structure the main types of files.
Sorting things out
In RubyOnRails projects it's not rare to find fat models and fat controllers. As a team work fast and without much guiding lines or culture in place models tend to get stuffed with many methods, controllers with many actions and private methods.
Design-wise this way of working tend to break many of the SOLID principles. The good news is that it's not a lost cause and we can do something about it.
Giving a series of refreshing talks and sessions on SOLID principles can help reposition some approaches. But in general, even without pointing explicitly to theoretical stuff, we can rely on Ruby idioms and Rails features to move the needle in the good direction.
It's easy to rely on some simple concepts such as separation of responsibility and keeping things simple in all classes. Sure, this refers to SOLID, but you don't have to get the big guns out every time and those two simple concepts can already get you through a few problems.
Ruby and the Rails ecosystem are full of good libraries and practices. The first one that comes from Rails but can be pushed even further is to split the code of the fat models and controllers into additional libraries (in lib/), concerns, workers and helpers.
With a few gems we can even push that furthers by defining actors, decorators, commands, handlers and so on (read more on those patterns : https://refactoring.guru/design-patterns/ruby }.
Often teams have to work with external services, this is code that should be kept aside from the main code base, possibly by writing custom, tailored, client libraries to mask the details of connection, retries, timeout and possibly caching. The main code base should only have to initialize the client and call a few methods to just do or get what it needs.
As this code is free from business logic it should sit in the lib folder, or even be bundled into gems (private or not).
Decorators are often mistreated when they are not ignored. They are a great way to add presentation logic around an object so that it's not cluttering the model (a method called 'name' that concats 'firstname' and 'lastname' together should be in a decorator, not a model).
Actors and Commands
Many names for the same idea : interactors, actors, service objects, ... The idea is to be able to move into a simple class the code that is tasked to trigger different actions on objects that it's given when initialized.
That's a great way to slim down fat controller actions doing too many things or even fat models doing a bit more than what they should.
Rails concerns can fit in that box.
A similar concept to the actors, but with a chained approach : work is passed on from one to another and returned back through the chain. Handy in some cases.
A Rails thing, often misused or misunderstood. Those can be left to complement the decorators to present data or little pieces of code that are used through controllers or views (current_user, and so on for example).
Those are a classic anytime a project needs background or batch work done. Sidekiq, DelayedJob and a few others are good ways to handle those. Those ones rely on Redis but I would also consider options relying on SNS/SQS (Shoryuken), RabbitMQ (Sneakers) or Kafka (mostly custom code).
Workers can be teamed up with actors, libraries and handlers to get the code organized across the board : making controllers and models slimmer. The task get queued with ids of objects in the database, the worker pull the object, pass it to an actor or command object that does the work.
Whatever the state of the code base the general idea is to establish a set of guidelines as to what is used among all those how and how to organize them. If need be examples can be written and kept in the styleguides as reference, or things can stay general with a couple of articles describing general use cases.
As with styleguides, any approach becomes part of the technical culture and thus is bound to evolve through iterations (retrospectives being key for this step).
There is rarely much point in trying to jump to the top of the mountain in one step only : trying to refactor all the technical debt in one go is bound to fail. Instead, once the team has sketched out which bricks to use and how to use them, pieces of fat controllers and fat models can be carved out into the desired shapes.
It will take time, but surely there will be progress, commit after commit there code base will evolve and become easier to work with, maintain and scale.
Fancy talking more on this topic ? Contact me to discuss how I can help your team establish or refresh a set of guidelines in your projects.