Rails Patterns: Builders and Models
A huge problem with ruby on rails apps is many of them end with overly cluttered models and controller methods. An attempt to solve this would be to move what you can to your active record models, but then you end up with heavily bloated model classes that extend way beyond their intended active record design patterns. Active record models should just manage the communication between the rails app and the database. Anything else is just code smell.
In all of my experience with rails projects, I found that my active record models typically ended up having a group of methods that helped me create the model itself (think when you create a user, you also need to create a user_profile or if you want to find or create a model).
Builders
You can easily break this type of code away from the model by following a builder pattern. Builder patterns are great for creating objects with business logic based on given parameters. It adds an easy way to add business logic to model creation without adding bloat to your active record model class.
To follow this pattern, create a directory called builders
in the /models
directory.
Here is an example of my user class before and after a refactor with the builder pattern.
before:
```ruby user.rb def self.create_admin_user email, password user = User.find_or_create_by email: email, password: password user.add_role :admin end
def self.create_staff_user email, password user = User.find_or_create_by email: email, password: password user.add_role :staff end
def self.create_user_with_invite email, password user = User.find_or_create_by email: email, password: password user.send_invitation end
after:
```ruby user.rb
# nothing to see here
```ruby /builders/user_builder.rb class Builders::UserBuilder def self.create_admin_user email, password user = User.find_or_create_by email: email, password: password user.add_role :admin end
def self.create_staff_user email, password
user = User.find_or_create_by email: email, password: password
user.add_role :staff
end
def self.create_user_with_invite email, password
user = User.find_or_create_by email: email, password: password
user.send_invitation
end end ```
Non-AR models
Models also can get bloated with controller-like actions that have groups of methods that should be broken out into their own classes that the controllers will leverage. An example of this type of class would be one that manages to send an email to a user. These models don’t extend active record, because they focus is performing business logic on active_record models, not storing them in the database. Models take in a limited scope of the Params of a request, perform their function, and then return their results to the controller.
Before breaking out email sending, active record models would end up with a collection of email sending methods (send_password_reset, send_notification, etc). All of these should be moved to a model classes because classes have one purpose and active record models are for Savin data, not sending emails.
An example of this could be:
ruby user_invitor.rb
class UserInvitor
def invite_email email
Invitation.create email: email
UserMailer.invite_email(email).deliver_later
end
end