Rails: Facade Design Pattern for Index
Tags: ruby, rails, cleancode, designpattern
Published: 2019-12-26 16:14:10

*If you continue to read this article, I assume that you know Ruby, OOP in Ruby, RoR, Active Record, and a little bit metaprogramming.*

What is Facade Design Pattern?

> Facade Design Patter provide **a unified interface to a set of interfaces in a subsystem**. Facade defines a higher-level interface that makes the subsystem easier to use.

> source: GoF in Design Patterns - Elements of Reusable Object-Oriented Software

Perhaps you should read these articles before we start:
[Rails: Query Object Pattern Implementation](https://dev.to/kputra/rails-refactor-your-where-method-5b10)
[Rails: skinny controller, skinny model](https://dev.to/kputra/rails-skinny-controller-skinny-model-5f2k)

Let's start our journey! *(I use Rails API-only as example, but this article can be implemented in normal Rails as well)*

**Table of Contents:**
[1. Problems](#chapter-1)
[2. First Solution (bad)](#chapter-2)
[3. Second Solution (a bit better)](#chapter-3)
[4. Third Solution (much better)](#chapter-4)
[5. The Cost](#chapter-5)
[6. Final Word](#chapter-6)
<a name="chapter-1"></a>
## 1. Problems
In Fintax, the FE team asked me (the only one BE developer) to give this kind of response for index api:
```javascript
{
    "object": [
        {
            "column": "object1"
        },
        {
            "column": "object2"
        }
    ],
    "page": {
        "total_record": 20,
        "per_page": 2
    }
}
```
Okay, it's not hard. Let's move to first solution.
<a name="chapter-2"></a>
##2. First Solution (bad)
Let assume we use [will_paginate gems](https://github.com/mislav/will_paginate) (I actually use that). For beginner, these will be their controllers:
```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    users = User.where(nil)
    users = users.where(role: params[:role]) if params[:role]
    users = users.where(status: params[:status]) if params[:status]
    users = users.where(public_id: params[:public]) if params[:public]
    users = users.paginate(page: params[:page], per_page: 2)
    users_count = users.count
    render json: { object: users, page: { total_record: users_count, per_page: 2 }}, status: 200
  end
end

# app/controllers/companies_controller.rb
class CompaniesController < ApplicationController
  def index
    companies = Company.where(nil)
    companies = companies.where("name like ?", "#{params[:name]}%") if params[:name]
    companies = companies.where(tax_id: params[:tax]) if params[:tax]
    companies = companies.paginate(page: params[:page], per_page: 2)
    companies_count = companies.count
    render json: { object: companies, page: { total_record: companies_count, per_page: 2 }}, status: 200
  end
end
```
This kind of controllers is very bad, because of various reasons:
1. I have a dozen of models that need to be indexed (and this number will grow in the future), so this kind of code not only very repetitive, but also very hard to maintained;
2. The total objects per page are not dynamic, so if the FE team asked to change it, we have to change all controllers;
3. We send every columns exist in the model to FE;
4. etc.

Let's move to our next solution.
<a name="chapter-3"></a>
##3. Second Solution (a bit better)
We will use [filterable module which I explain in my previous article](https://dev.to/kputra/rails-refactor-your-where-method-5b10).

We will use pluck. To be precise, I use [pluck_all gems](https://github.com/khiav223577/pluck_all). *Why we use pluck?* So we don't send every columns to FE. We just give them what they need. *Why we don't use `as_json` or `map`?* Because pluck is better in performance.

We will make our pagination to become dynamic. Tell FE team that they can throw `per_page` parameter if someday they want to change total objects per page.

So, our controllers and models will be like this:
```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    users = User.call_index(params)
    users_count = users.count
    users = users.show_index
    render json: { object: users, page: { total_record: users_count, per_page: (params[:per_page] || 2) }}, status: 200
  end
end

# app/controllers/companies_controller.rb
class CompaniesController < ApplicationController
  def index
    companies = Company.call_index(params)
    companies_count = companies.count
    companies = companies.show_index
    render json: { object: companies, page: { total_record: companies_count, per_page: (params[:per_page] || 2) }}, status: 200
  end
end

# app/models/user.rb
class User < ApplicationRecord
  include Filterable

  scope :role,   -> (role) { where(role: role) }
  scope :status, -> (status) { where(status: status) }
  scope :public, -> (public_id) { where(public_id: public_id) }

  def self.call_index(params)
    filter(params.slice(:role, :status, :public))
    .paginate(page: params[:page], per_page: (params[:per_page] || 2)
  end

  def self.show_index
    pluck_all(:id, :role, :status, :public)
  end
end

# app/models/company.rb
class Company < ApplicationRecord
  include Filterable

  scope :name, -> (name) { where("name like ?", "#{name}%") }
  scope :tax,  -> (tax_id) { where(tax_id: tax_id) }

  def self.call_index(params)
    filter(params.slice(:name, :tax))
    .paginate(page: params[:page], per_page: (params[:per_page] || 2)
  end

  def self.show_index
    pluck_all(:id, :name, :tax)
  end
end
```
I hope you realize 2 things:

*First*, I use `pluck` after counting total object.

If I use `count` after `pluck`, I call method `count` from Array class, not `count` from Active Record. So, I'll get length of array instead of total object from active record.

*Second*, if you have already read [Rails: Refactor Your Where Method](https://dev.to/kputra/rails-refactor-your-where-method-5b10), notice that I delete `filtering_(params)` method from controllers, and filter the params in models.

If I filter the params in controller as before and add `page` and `per_page` parameters, then the `filter` method will search scope named `page` and `per_page`. Because these scopes do not exist, then rails will throw error.

Tips: There are many ways to move `params.slice()` to another method. Why we need to move it? Imagine you have 10 keys in the params hash! One of the ways are: we create class inside model, like the code below. That way, the code is more cleaner (method should do one thing), and `params.slice()` is reusable inside the model.
```ruby
# app/models/user.rb
class User < ApplicationRecord
  include Filterable

  scope :role,   -> (role) { where(role: role) }
  scope :status, -> (status) { where(status: status) }
  scope :public, -> (public_id) { where(public_id: public_id) }

  def self.call_index(params)
    filter(Whitelisting.call(params))
    .paginate(page: params[:page], per_page: (params[:per_page] || 2)
  end

  def self.show_index
    pluck_all(:id, :role, :status, :public)
  end

  class Whitelisting
    def self.call(params)
       params.slice(:role, :status, :public)
    end
  end
end
```

Okay, this is better than initial solution. Now, let's implement Facade Pattern !
<a name="chapter-4"></a>
##4. Third Solution (much better)
Let's implement Facade Pattern. We'll make `app/lib` directory, and create `index_facade.rb`.

In short, **`index_facade.rb` is the unified interface to a set of interfaces in a subsystem**.
```ruby
# app/lib/index_facade.rb
class IndexFacade
  def initialize(model, params)
    @model   = model.constantize
    @params  = params
    @objects = objects
  end

  def call
    {
      object: @objects.show_index
      page: {
        total_record: @objects.counts
        per_page: per_page
      }
    }
  end
  
  private

  def objects
    @model.call_index(@params)
  end

  def per_page
    @params[:per_page] || 2
  end
end
```
Now, we'll update our controllers:
```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    users = IndexFacade.new('User', params).call
    render json: users, status: 200
  end
end

# app/controllers/companies_controller.rb
class CompaniesController < ApplicationController
  def index
    companies = IndexFacade.new('Company', params).call
    render json: companies, status: 200
  end
end
```
Voila! Look at our new controllers!
<a name="chapter-5"></a>
##5. The Cost
The only costs are:

In every model, we have to `include Filterable`.

In every model, we have to add `call_index` and `show_index` method, as in `User` and `Company`.

What if I want index of `Article` is giving every column and have no filter?
```ruby
# app/models/article.rb
class Article < ApplicationController
  include Filterable

  def self.call_index(params)
    filter(params.slice())
    .paginate(page: params[:page], per_page: (params[:per_page] || 2)
  end

  def self.show_index
    pluck_all
  end
end

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    articles = IndexFacade.new('Article', params).call
    render json: articles, status: 200
  end
end
```
<a name="chapter-6"></a>
##6. Final Word
If you have opinion or better implementation, or may be I was wrong about Facade Pattern, let discuss in comment.


source: myself