Skip to content

Instantly share code, notes, and snippets.

@adamcooper
Last active August 29, 2015 13:56
Show Gist options
  • Save adamcooper/9262267 to your computer and use it in GitHub Desktop.
Save adamcooper/9262267 to your computer and use it in GitHub Desktop.
Sample Service Base Class and Result Class
class UserController < ActionController::Base
def new
@user = User.new
end
def create
result = SignupUser.call(params[:user])
if result.successful?
redirect_to root_url, notice: "A welcome e-mail is on it's way!"
else
@user = result.user
render
end
end
end
class SignupUser < Service::Call
attribute :name, String
attribute :email, String
attribute :wants_email, Boolean, default: ->(signup_user, attribute) { true }
class Result < Service::Result
attribute :user, User
end
def call
user = User.new(name: name, email: email, accepts_email: wants_email)
result.user = user
if user.save
UserMailer.welcome_email(user.email).deliver
result.successful!
else
result.add_errors(user)
end
result
end
end
class Service::Call
include Virtus.model
attribute :result, Service::Result, default: ->(service,_) { service.build_result }
def self.call(*args)
new(*args).call
end
protected
def build_result
klass = self.class.const_get("Result") rescue Service::Result
klass.new
end
def with_exception_catching
begin
yield
rescue StandardError => exception
result.exception = exception
result.add_errors(exception.record) if exception.respond_to?(:record)
end
end
end
class Service::Result
include Virtus.model
attribute :status, String, default: Status::ERROR
attribute :errors, Object, default: ->(sr,_) { ActiveModel::Errors.new(sr) }
attribute :exception, StandardError
module Status
SUCCESSFUL = 'successful'
ERROR = 'error'
end
def successful!; self.status = Status::SUCCESSFUL; self; end
def successful?; self.status == Status::SUCCESSFUL; end
def error!; self.status = Status::ERROR; self; end
def error?; self.status == Status::ERROR; end
def add_error_message(message)
errors.add(:base, message)
end
def add_errors(obj_with_errors)
Array(obj_with_errors).each do |obj|
self.errors.messages.merge!(obj.errors) unless obj.errors.empty?
end
end
def add_errors_or_be_successful!(obj_with_errors)
add_errors(obj_with_errors)
successful! if errors.empty?
self
end
end
@mattapayne
Copy link

Hey Adam,

Just FYI, I had to have Service::Result extend ActiveModel::Translation in Rails 4.x in order for stuff like add_errors to work properly. I was getting "undefined method `human_attribute_name'". Just thought I'd add that here in case it helps someone out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment