Skip to content

Instantly share code, notes, and snippets.

@jamesarosen
Created August 21, 2012 16:38
Show Gist options
  • Select an option

  • Save jamesarosen/3417116 to your computer and use it in GitHub Desktop.

Select an option

Save jamesarosen/3417116 to your computer and use it in GitHub Desktop.
An experiment in modeling a process with ActiveModel
# Our starting point:
# a Rack endpoint that performs HTTP Basic auth.
class SignInEndpoint
def call(env)
email, password = *credentials(env)
user = User.authenticate(email, password)
if user.nil? || user.deleted?
respond_with_error 'Invalid credentials'
elsif user.suspended?
respond_with_error 'Your account has been suspended'
else
process_successful_sign_in(env, user)
end
end
private
def credentials(env)
Rack::Auth::Basic::Request.new(env).credentials
end
def respond_with_error(text)
[
403,
{ 'Content-Type' => 'text/plain', 'Content-Length' => text.length },
[ text ]
]
end
def process_successful_sign_in(env, user)
session = Rack::Request.new(env).session
session['user'] ||= {}
session['user']['id'] = user.id
[ 200, { 'Content-Type' => 'text/plain', 'Content-Length' => '7' }, ['Success'] ]
end
end
# An attempt at refactoring with ActiveModel:
class SignInEndpoint
def call(env)
email, password = *credentials(env)
session = Rack::Request.new(env).session
SignInProcessor.new(email, password, session).process!
[ 200, { 'Content-Type' => 'text/plain', 'Content-Length' => '7' }, ['Success'] ]
rescue SignInProcessor::Failed => e
respond_with_error(e)
end
private
def credentials(env)
Rack::Auth::Basic::Request.new(env).credentials
end
def respond_with_error(e)
error_messages = e.errors.full_messages
total_length = error_messages.map(&:length).sum
[
403,
{ 'Content-Type' => 'text/plain', 'Content-Length' => total_length },
error_messages
]
end
end
class SignInProcessor
include ActiveModel::Validations
class Failed < Exception
attr_reader :errors
def initialize(validation_errors)
@errors = validation_errors
end
end
attr_reader :email, :password, :session
def initialize(email, password, session)
@email, @password, @session = email, password, session
end
validates_presence_of :email, :password, :session, :user
validates :validate_user_not_deleted, :if => :user?
validates :validate_user_not_suspended, :if => :user?
def process!
validate
raise Failed.new(errors) if errors.any?
session['user'] ||= {}
session['user']['id'] = user.id
end
private
def user
return @user if instance_variable_defined?(:@user)
@user = User.authenticate(email, password)
end
def user?
user.present?
end
def validate_user_not_deleted
errors.add(:user, :deleted) if user.deleted?
end
def validate_user_not_suspended
errors.add(:user, :suspended) if user.suspended?
end
end
@jish

jish commented Aug 23, 2012

Copy link
Copy Markdown

I prefer 01 to 02 + 03.

01 is pretty straightforward and easy to read. It also hides some of its logic in User.authenticate(email, password). Although I do have some tiny nitpicky pedantic gripes with 01, in their current incantations, I prefer 01.

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