@vrybas.(blogs).here

Ruby / Rails / Agile / Productivity / Vim / ЗОЖ

A Way to Organize POROs in Rails

Service layer

So, now we came to a point where our app no longer fit to vanilla MVC and we want to extract a service layer. By “Service Layer” I mean a collection of Plain Old Ruby Objects, which hold pure business logic. What kind of POROs it might be? Here’s good examples by Bryan Helmkamp.

But, anyway, the question is – where do we put them in our Rails app files structure?

We got several options here:

  1. Dump everything to lib/ dir
  2. Call them non-ActiveRecord Models and put them in app/models/
  3. Use Cases and Entities (DDD approach). So app/use_cases/ & app/entities/
  4. ???

Let’s go through these options one after another.

1. lib/

If class/module is not Model, View or Controller, it suppose to go to lib/, isn’t it? Well, this approach got some disadvantages.

When you start to extract your business logic to POROs, on a big project you’re going to have a few dozen of them, maybe hundred(s). Pretty soon you open your lib/ – and it scares you. Even worse when it gets explored by a new team member.

Another thing – it’s pretty easy to loose track on what is used where and for what reason. Especially in dynamic metaprogramming-friendly language like Ruby.

So, slowly but surely, lib/ becomes “a big ball of mud”.

2. non-ActiveRecord Models

We can say that PORO, which returns data structure(or for whatever other reason), is non-ActiveRecord model and put it in models/

Well, that’s better. Sort of. Part is in lib/, part is in models/. A slightly less mess, but in two places now.

Besides, there’s a problem each time to decide, whether this PORO is a “model” or not. And it’s even harder for others to guess, where to find that class(in models/ or still in lib/), when they explore your code.

3. Use Cases & Entities

The concept of Use Cases & Entities comes from DDD and from Bob Martin’s talk “Architecture: The Lost Years”. So, the core of your system suppose be in UseCases(verbs e.g. CreateUser) and Entities(nouns e.g. User).

But the problem with this – it takes you far away from “The Rails Way”. In one case I want to use my ActiveRecord model as Entitly, but in the other case it should be a separate class with it’s corresponding Repository class. Sometimes, my Controller is pretty much the UseCase, but sometimes I want to extract a few classes from it.

Decisions, decisions. It’s all confusing. Besides, everyone in the team need to share the exact same architectural concepts (which is solved by “The Rails Way” at first place). Pretty quick the codebase can become more mess and hard to follow through, than it was before.

4. Namespaces FTW!

So, there’s another approach, which is used by every single gem and almost any other Ruby program – the namespaced classes and modules.

Disclaimer: As usual, code examples are highly synthetic and short to keep them easy to follow. Your “results may vary”.

Example with Controllers

Let’s say you got a dashboard in your app.

1
2
3
4
class DashboardController < ApplicationController
    def index
    end
end

And you want to show a list of users on this dashboard. But user list should be paginated, sortable, and current_user-specific. Now, I think it’s a good idea to create a UserList class, which will handle all this logic.

But where do I put this class? Is this a Service / Model / Use Case / Entity / Query Object / Policy Object? When using namespaces, the answer is – I don’t really care.

All I need to do is to create one more folder in controllers/ with the same name as controller.

1
2
3
4
controllers
├── dashboard_controller
   └── user_list.rb
└── dashboard_controller.rb

and put my UserList class there

1
2
3
4
5
6
7
# app/controllers/dashboard_controller/user_list.rb

class DashboardController
  class UserList
    # codes codes codes
  end
end

and then it can be used in DashboardController like this

1
2
3
4
5
6
7
8
9
class DashboardController < ApplicationController
  def index
    @users = UserList.new(current_user,
                          page: params[:page],
                          per_page: params[:per_page],
                          sort_by: params[:sort_by],
                          order_asc: params[:order_asc]).all
  end
end

Note that we don’t even need to specify the namespace.

Example with Models

Pretty much the same thing with models.

1
2
3
4
5
class User < ActiveRecord::Base
  def can_follow?(user)
    FollowingPolicy.new(self, user).can_follow?
  end
end

with files structure

1
2
3
4
models
├── user
   └── following_policy.rb
└── user.rb

and possible content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app/models/user/following_policy.rb
class User
  class FollowingPolicy
    attr_reader :current_user, :other_user, :account_verification

    def initialize(current_user, other_user)
      @current_user = current_user
      @other_user = other_user
      @account_verification = current_user.account_verification
    end

    # ...
  end
end

As a result – your code is more logically organized. It would be pretty easy to guess the locations of classes while exploring code – your app will still look like a Rails app.

And you’ll never afraid to extract one more single responsibility class, knowing that it is going to be small and “local” and won’t conflict with other’s classes.

What goes to lib/ then? API wrappers for one example. The application-independent code, or code which doesn’t belong to any model or controller.

Comments