 
        When thinking about more advanced OOP structure, one can sometimes ask – how to incorporate it into a Rails application? I will try to answer that question in this blog note, introducing some advanced OOP concept into a Rails application (without any architecture gems). The result? Skinny controller, fat business-logic service.
Note: If you haven’t read it, please start with the 101: Advanced OOP structure blog note.
Let’s start with a basic Rails application – I will use a newly created app (I will try to keep my setup minimal so that we can focus on OOP stuff here – therefore no models, etc., only one interesting controller here).
Let’s consider for now that a user can reserve an item – to purchase it later. Think of it as places in theatre or on an airplane. After choosing the item, the user will have 15 minutes to make the payment; otherwise their item will return to the pool of available ones. You can think about it as a simple lock with some expiration time. If the payment has been completed, an item will be marked as sold and will not be placed in the available pool by some other code – outside of the scope of this exercise.
So, let’s create a controller for this:
# app/controllers/reservations_controller.rb
class ReservationsController < ApplicationController
  def create
    # create new lock if there isn't one for that item
  end
end
and add the following to routes.rb:
# config/routes.rb
# (...)
resources :reservations, only: :create
# (...)
We need a way to store locks and release them after precisely 15 minutes. One can choose to create a new AR model, store the date of creation and select only newer than 15 minutes, while periodically clearing locks older than, e.g., one hour. With a smart construction of indexes and usage of UPSERT or ON CONFLICT UPDATE that would be a good approach. Other may use Redis as the lock provider; Redis is a NoSQL database natively supporting locks (even distributed ones).
However, I decided to mock my lock provider. That way I can keep the scope of this blog note as close to OOP as possible, without drifting too much into the (otherwise fascinating!) world of locking algorithms and techniques.
So, my imagination-lock (let’s think about it as a gateway to some service on same server providing locks) will have the following public interface:
* command=(command_string) # send connection query to a server
                           # currently it can be only `LOCK`
* argument_1=(argument_value) # sets first argument to command
                              # for lock it's user_id
* argument_2=(argument_value) # sets first argument to command
                              # for lock it's item_id
* execute_command # execute the command with provided arguments
                  # returns 0 if locking failed, and 1 if it succeeded
Please take note, that this API is complicated on purpose – to better illustrate the difference object-oriented refactoring will make here.
I will be using this mock code for this:
# lib/lock.rb
class Lock
  attr_accessor :command, :argument_1, :argument_2
  def execute_command
    if !argument_1 || !argument_2 || command != 'LOCK'
      raise ArgumentError, 'Bad arguments provided'
    end
    [0, 1].sample # hey, external API can always fail!
  end
end
Using our library, let’s write a basic implementation in our controller:
# app/controllers/reservations_controller.rb
require Rails.root.join('lib/lock')
class ReservationsController < ApplicationController
  skip_before_action :verify_authenticity_token
  # we're trying to make the example as simple as possible
  rescue_from ArgumentError, with: :argument_error
  # but some error handling would be good ;)
  def create
    lock = Lock.new
    lock.command = 'LOCK'
    lock.argument_1 = params[:user_id]
    # in production it should be some kind of current_user
    lock.argument_2 = params[:item_id]
    if lock.execute_command == 1
      render json: {}, status: :created
    else
      render json: {}, status: :conflict
    end
  end
  private
  def argument_error
    render json: { error: 'Bad arguments' }, status: :bad_request
  end
end
Every experienced developer should now get that itch in the back of their head – there is something wrong with the code. The create method has too many lines for a controller. It contains a lot of logic, especially for a controller. A controller’s responsibility should be to extract params, call another layer and return the result. This code could use more abstraction. Testing it also will be painful – we probably will have to mock some Lock calls. But what if we can make it easier? What if we can introduce some abstraction that will make testing this stack a breeze?
Let’s try it!
First – we will be adding new layers to our Rails app. I think services would be sufficient here. Start by creating our first service and moving most of the controller code there:
# app/services/lock_service.rb
require Rails.root.join('lib/lock')
class LockService
  def initialize(user_id:, item_id:)
    @user_id = user_id
    @item_id = item_id
  end
  def call
    lock.command = 'LOCK'
    lock.argument_1 = user_id
    lock.argument_2 = item_id
    lock.execute_command == 1
  end
  private
  attr_reader :user_id, :item_id
  def lock
    @lock ||= Lock.new
  end
end
and our controller will now looks a lot better:
# app/controllers/reservations_controller.rb
class ReservationsController < ApplicationController
  skip_before_action :verify_authenticity_token
  rescue_from ArgumentError, with: :argument_error
  def create
    if lock_service.call
      render json: {}, status: :created
    else
      render json: {}, status: :conflict
    end
  end
  private
  def argument_error
    render json: { error: 'Bad arguments' }, status: :bad_request
  end
  def lock_service
    @lock_service ||= LockService.new(user_id: user_id, item_id: item_id)
  end
  def user_id
    @user_id ||= params[:user_id]
  end
  def item_id
    @item_id ||= params[:item_id]
  end
end
Now the controller finally looks like something that would pass code review! How about testing it? Unfortunately doing DI for controllers is particularly hard in Rails, probably because DHH doesn’t believe in DI. But we will find a way!
First of all, let’s create a boilerplate class that will work as a singleton (i.e., there will be only one Registry object in our system) registry for our services, register our LockService and freeze it for all changes for every env (excluding test of course):
# config/initializers/service_provider.rb
class ServiceProvider
  @services = {}
  def self.register(key, klass)
    return false if @services.key?(key) && !Rails.env.test?
    @services[key] = klass
  end
  def self.get(key)
    @services[key]
  end
  def self.[](key)
    get(key)
  end
  def self.finished_loading
    @services.freeze unless Rails.env.test?
  end
end
ServiceProvider.register :lock_service, LockService
ServiceProvider.finished_loading
and of course, use it in the controller:
# app/controllers/reservations_controller.rb
class ReservationsController < ApplicationController
  skip_before_action :verify_authenticity_token
  rescue_from ArgumentError, with: :argument_error
  def create
    if lock_service.call
      render json: {}, status: :created
    else
      render json: {}, status: :conflict
    end
  end
  private
  def argument_error
    render json: { error: 'Bad arguments' }, status: :bad_request
  end
  def lock_service_class
    @lock_service_class ||= ServiceProvider.get(:lock_service)
  end
  def lock_service
    @lock_service ||= lock_service_class.new(user_id: user_id, item_id: item_id)
  end
  def user_id
    @user_id ||= params[:user_id]
  end
  def item_id
    @item_id ||= params[:item_id]
  end
end
Please take note that in a typical Rails project, you would probably instead write the lock_class method as simply returning a LockService and mock it in the test instead. I, however, wanted to explicitly have all the class relationships specified and avoid using monkey-mocking (my term similar to monkey patching, means reopening some class or object to change the behavior of some methods, may be done using an external library that is hiding it from us).  Also – this is an article about object-oriented programming, so we should use dependency injection instead of monkey-mocking.
Ok, so we got a way to inject a mock for the LockService class in our controller – time to test it:
# test/controller/reservations_controller_test.rb
require 'test_helper'
class ReservationsControllerControllerTest < ActionDispatch::IntegrationTest
  SuccesfullLockService = Struct.new(:user_id, :item_id, keyword_init: true) do
    def call
      true
    end
  end
  FailedLockService = Struct.new(:user_id, :item_id, keyword_init: true) do
    def call
      false
    end
  end
  test 'returns HTTP bad_request when missing user_id' do
    ServiceProvider.register(:lock_service, LockService)
    post reservations_url, params: { item_id: 2 }
    assert_response :bad_request
  end
  test 'returns a JSON error when missing user_id' do
    ServiceProvider.register(:lock_service, LockService)
    post reservations_url, params: { item_id: 2 }
    assert_equal({ 'error' => 'Bad arguments' }, response.parsed_body)
  end
  test 'returns HTTP bad_request when missing item_id' do
    ServiceProvider.register(:lock_service, LockService)
    post reservations_url, params: { user_id: 2 }
    assert_response :bad_request
  end
  test 'returns a JSON error when missing item_id' do
    ServiceProvider.register(:lock_service, LockService)
    post reservations_url, params: { user_id: 1 }
    assert_equal({ 'error' => 'Bad arguments' }, response.parsed_body)
  end
  test 'returns emtpy JSON when service returns true' do
    ServiceProvider.register(:lock_service, SuccesfullLockService)
    post reservations_url, params: { user_id: 1, item_id: 2 }
    assert_equal({}, response.parsed_body)
  end
  test 'returns HTTP created when service returns true' do
    ServiceProvider.register(:lock_service, SuccesfullLockService)
    post reservations_url, params: { user_id: 1, item_id: 2 }
    assert_response :created
  end
  test 'returns empty JSON when service returns false' do
    ServiceProvider.register(:lock_service, FailedLockService)
    post reservations_url, params: { user_id: 1, item_id: 2 }
    assert_equal({}, response.parsed_body)
  end
  test 'returns HTTP conflict when service returns false' do
    ServiceProvider.register(:lock_service, FailedLockService)
    post reservations_url, params: { user_id: 1, item_id: 2 }
    assert_response :conflict
  end
  teardown do
    ServiceProvider.register(:lock_service, LockService)
  end
end
We provide two mocked lock services – one always returning false and one always returning true. Then we test the HTTP response codes. Also, we test whether the controller is handling the case of missing params correctly. Please take a note that since we’re injecting mocks into the controller, we need to re-inject the original dependency after each test – that is what teardown does.
Please take note that while this approach should be working correctly, it may fail when running the tests in parallel – manipulating a class level instance variable is not thread-safe. In normal operation, it will be all preloaded in an initializer and frozen, so there will be no manipulation of services at runtime; hence it will work correctly in, for example, multithreaded Puma application server.
Ok, so we’ve got the controller part quite right, time to move onto LockService tests. Let’s add a sparkle of DI there:
# app/services/lock_service.rb
require Rails.root.join('lib/lock')
class LockService
  def initialize(user_id:, item_id:, lock: Lock.new)
    @user_id = user_id
    @item_id = item_id
    @lock = lock
    # on a side note - that would fail miserably in Python
    # do you know why?
  end
  def call
    lock.command = 'LOCK'
    lock.argument_1 = user_id
    lock.argument_2 = item_id
    lock.execute_command == 1
  end
  private
  attr_reader :user_id, :item_id, :lock
end
while providing it with some struct as a mock, we can quickly test this PORO (plain old Ruby object):
require 'test_helper'
class LockServiceTest < ActiveSupport::TestCase
  MockedSuccessfulLock = Struct.new(:command, :argument_1, :argument_2) do
    def execute_command
      1
    end
    def inspect_fields
      [command, argument_1, argument_2]
    end
  end
  MockedFailedLock = Struct.new(:command, :argument_1, :argument_2) do
    def execute_command
      0
    end
    def inspect_fields
      [command, argument_1, argument_2]
    end
  end
  test 'sets fields correctly when calling LockService' do
    lock_object = MockedSuccessfulLock.new
    service = LockService.new(user_id: 1, item_id: 2, lock: lock_object)
    service.call
    assert_equal ['LOCK', 1, 2], lock_object.inspect_fields
  end
  test 'returns false when Lock returns 0' do
    lock_object = MockedFailedLock.new
    service = LockService.new(user_id: 1, item_id: 2, lock: lock_object)
    assert !service.call
  end
  test 'returns true when Lock returns 1' do
    lock_object = MockedSuccessfulLock.new
    service = LockService.new(user_id: 1, item_id: 2, lock: lock_object)
    assert service.call
  end
end
In conclusion – we’ve started with the controller that was doing all the work. We’ve created an additional layer of abstraction inside our Rails application, which in turn resulted in much more readable and testable code – all that while doing a real-life task inside the Rails application. Nifty!
As always, you can find the example application at our GitHub.
)