Reputation: 11
I am trying to find an elegant way to handle some shared validations between two controllers.
Example:
I have two Accounts controllers. One to process accounts associations to a user synchronously (using, for instance, a PORO that contains the logic for this case), and another for treating the association asynchronously with a worker. Please assume that the logic differs in each scenario and the fact that being sync/async isn't the only difference.
Then I have this two controllers:
module Accounts
class AssociationsController < ApplicationController
def create
return already_associated_account_error if user_has_some_account_associated?
# action = call some account association PORO
render json: action.response, status: action.status_code
end
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
Now I have another controller in which I'd want to apply the same validation:
module Accounts
class AsyncAssociationsController < ApplicationController
def create
return already_associated_account_error if user_has_some_account_associated?
# Perform asynchronously some account association WORKER
render json: 'Your request is being processed', status: :ok
end
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
...
HOW and WHERE could I place the validation logic in ONLY ONE SPOT and use it in both controllers? I think in extracting to a concern at first, but I'm not sure if they are intended for this cases of validation logic only.
Upvotes: 1
Views: 376
Reputation: 5037
For this you should use concerns. It's what's they are designed for.
Under the controllers
directory make a concerns
directory (if it isn't already there) and inside that make the file association_concern.rb
with this content:
module AssociationConcern
extend ActiveSupport::Concern
private
def user_has_some_account_associated?
params[:accounts].any? { |account_number| user_account_associated?(account_number) }
end
def user_account_associated?(account_number)
current_user.accounts.exists?(number: account_number)
end
def already_associated_account_error
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
Anything that is common to the controllers can go in the concern
Then in your controllers simply include AssociationConcern
class AssociationsController < ApplicationController
include AssociationConcern
def create
return already_associated_account_error if user_has_some_account_associated?
# action = call some account association PORO
render json: action.response, status: action.status_code
end
end
Upvotes: 2
Reputation: 5313
Make them inherit from some new controller and add a before_action
, like this:
module Accounts
class AbstractAssociationsController < ApplicationController
before_action :check_for_associated_account, only: [:create]
def check_for_associated_account
if user_has_associated_account?
render json: 'You already have associated one or more accounts', status: :unprocessable_entity
end
end
end
end
module Accounts
class AssociationsController < AbstractAssociationsController
def create
# action = call some account association PORO
render json: action.response, status: action.status_code
end
end
end
Then, depending on whether the logic is really different, you can define the user_has_associated_account?
in either this abstract controller, separate controllers or delegate it to some PORO class.
Upvotes: 0