Application Logic vs Business Logic in Rails applications

Business logic consists of business domain description and business operations.

Business domain description - a set of domain models and their relations. Business operations (use cases, scenarios) - the logic of how we create, destroy and modify stuff in our system.

As for application logic, it is much easier to start explanation with examples:

  • email confirmation field
  • consent with some policy
  • captcha

Usually, it is not an integral part of your business domain. It's an implementation detail caused by specifics of the medium.

When we need email confirmation, we won’t store it twice in the database, right? We ask it twice to reduce the chance of mistake in an email.

The same logic applies to captcha. The simple reason you need it is because there are spammers on the internet, and you want to filter them out. It has nothing to do with your business domain.

Why would you put such logic to the core of your system?

Of course the opposite is possible. If you’re a captcha-generating system, then captcha will be a first-class citizen of your business domain.

Alright, let’s try a practical example.

Application logic in Rails models

Let’s say we have a form to implement:

Form

This is a registration form, but our Rails developers' brains immediately convert it to user resource creation in terms of REST.

So, almost instantly, we create this model:

class User < ApplicationRecord
  validates_presence_of :username, :email, :address
  validates_confirmation_of :email
  validates_acceptance_of :terms_of_service
end

Well, because Rails gives us this kind of stuff, right?

We could debate if validations are a part of domain model at all, but we won’t.

What we know from the above, is that email confirmation is not a part of business logic here, so it shouldn’t be in your model, if you pretend it to be a domain model.

Why bother?

Not because of the purism, but out of simple practicality.

Because one day you decide to create a user from a console, and you’ll be unpleasantly surprised when you realize that you have to pass both email and email confirmation fields.

What about terms of services?

Well, I always thought that it depends on a situation in this case. But what if you have to import 200 new users, but you don’t have their consent? You’ll be faking their agreement?

If this validation remains in the model, then you will have to eiher fake consents, or hack a model by executing save(with_validation: false) during the import.

The trick is that the column in database in this case is a part of your domain, but the validation isn’t. Validation - is a scenario-specific applicaion logic here.

If not convinced, imagine this problem at scale. Once you have 100 fields serving different purposes in your model, and a quarter of them will be application-specific, it will be much harder to distinguish them and keep the whole situation under control.

What to do?

Use Shape Objects for application specific validations:

class User < ApplicationRecord
  validates_presence_of :username, :email, :address
end

class RegistrationUser < User
  include ApplicationShape

  validates_confirmation_of :email
  validates_acceptance_of :terms_of_service
end

What about passwords?

If we use has_secure_password method in the model, then it already forces us to provide password_confirmation.

But the confirmation part is definitely the application logic. What to do now?

Luckily there is an option! Leave this in your model:

has_secure_password validations: false

and put validates_confirmation_of :password into your Shape object.

You’re done! 🙌

  • Domain Model
  • Rails ActiveRecord is not (only) ActiveRecord
  • Shape Objects