Skip to content

Instantly share code, notes, and snippets.

@aitor
Last active July 23, 2019 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aitor/d93eade1226ba0abb1e005da64aa7cc8 to your computer and use it in GitHub Desktop.
Save aitor/d93eade1226ba0abb1e005da64aa7cc8 to your computer and use it in GitHub Desktop.
Service / Usecase Objects in vanilla Ruby
describe AuthenticateManagerService do
subject(:context) { described_class.call(username, password) }
describe '.call' do
context 'when the context is successful' do
let(:username) { 'correct_user' }
let(:password) { 'magic' }
it 'succeeds' do
expect(context).to be_success
end
end
context 'when the context is not successful' do
let(:username) { 'correct_user' }
let(:password) { 'wrong_password' }
it 'fails' do
expect(context).to be_failure
end
end
end
end
module Api
module V1
class AuthsController < ApplicationController
def create
execution = AuthenticateManagerService.call(manager_params[:email], manager_params[:password])
if execution.success?
# We'll serialize this properly with a serializer
# ManagerSerializer.new(execution.result).serialized_json
render json: { data: execution.result }
else
render json: { error: execution.errors }, status: :unauthorized
end
end
def manager_params
params.require(:manager).permit(:email, :password)
end
end
end
end
> irb
irb(main):001:0> require_relative "service"
=> true
irb(main):002:0> exec = AuthenticateManagerService.call("fwefe", "magic")
=> #<AuthenticateManagerService:0x00007fb64b890ef0 @email="fwefe", @password="magic", @result={:email=>"fwefe", :authenticated=>true}>
irb(main):003:0> exec.success?
=> true
irb(main):004:0> exec.result
=> {:email=>"fwefe", :authenticated=>true}
# app/services/base_service.rb
class BaseService
attr_reader :result
def self.call(*args)
new(*args).call
end
def call
@result = nil
payload
self
end
def success?
errors.empty?
end
def errors
# IRL we may use ActiveModel::Errors, a thin Rails wrapper for a Hash
# cf. https://github.com/rails/rails/blob/master/activemodel/lib/active_model/errors.rb
# eg. @errors ||= ActiveModel::Errors.new(self)
# or roll our own simple Error class.
@errors ||= {}
end
private def initialize(*_)
raise NotImplementedError, "Implement `initialize` for classes inheriting BaseService"
end
private def payload
raise NotImplementedError, "Implement `payload` for classes inheriting BaseService"
end
end
# app/services/application_service.rb
class ApplicationService < BaseService
end
# app/services/authenticate_manager_service.rb
class AuthenticateManagerService < ApplicationService
private
attr_reader :email, :password
def initialize(email, password)
@email = email
@password = password
end
def password_valid?
password == "magic"
end
def payload
if password_valid?
# IRL we'll probably return an appropiate real object here and not a hash
# @result = user
@result = { email: email, authenticated: true }
else
# If we go with ActiveModel::Errors we will return something like this
# errors.add(:base, I18n.t('authenticate_user_command.invalid_credentials'))
errors[:base] = "INVALID CREDENTIALS"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment