Skip to content

Instantly share code, notes, and snippets.

@Startouf
Created March 20, 2019 15:16
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 Startouf/97759c40a176b022383972934f6e1474 to your computer and use it in GitHub Desktop.
Save Startouf/97759c40a176b022383972934f6e1474 to your computer and use it in GitHub Desktop.
Specification pattern implementation for ActiveJob
class SpecificationJob < ApplicationJob
include JobSpecifications
around_perform do |_, block|
init_specifications # init a bunch of instance variables to remember status of specifications
block.call # calls the #perform method of jobs
run_registered_specifications # Actually run the specs
if specifications_all_passed?
execute_if_specifications_all_passed
else
execute_if_any_specification_failed if respond_to?(:execute_if_any_specification_failed)
end
end
# So our jobs can be written this way
class LikeArticleJob < SpecificationsJob
def perform(article)
@article = article
end
def check_specifications
register_prerequisite_spec IsStillInDb.new(article)
register_spec Article::NotUnpublishedSpecification.new, article
register_spec User::NotSoftDeletedSpecification.new, article.author
register_spec Article::NotTooOld.new(max_date: 2.years.ago), article
end
def execute_if_specifications_pass
LikeService.like(article)
end
end</code>
# And the specification itself is written somehow like
class Article
class NotUnpublishedSpecification < Specification
def satisfaction_evaluation_for(profile)
satisfies('Article is not unpublished') do
article.published?
end
end
end
end
class Specification
attr_accessor :satisfied_conditions
attr_accessor :unsatisfied_conditions
def satisfied_by?(*args)
satisfaction_evaluation_for(*args)
satisfied?
end
# @Abstract
# The core of the specification
# Call this method for each spec that must pass
def satisfaction_evaluation_for(*)
raise NotImplementedError, 'Implement specification conditions'
end
def nothing_evaluated?
satisfied_conditions.empty? and
unsatisfied_conditions.empty?
end
def satisfied?
if nothing_evaluated?
return RuntimeError, 'Conditions not evaluated !'
else
unsatisfied_conditions.empty?
end
end
# satisfies(condition) { block }
def satisfies(condition)
if yield == true
satisfied_conditions << condition
true
else
unsatisfied_conditions << condition
false
end
end
def satisfied_conditions
@satisfied_conditions ||= []
end
def unsatisfied_conditions
@unsatisfied_conditions ||= []
end
# Print comprehensible log of specification satisfactions
def to_s
return super if nothing_evaluated?
(satisfied_conditions.map do |cond|
"✓ #{cond}"
end + unsatisfied_conditions.map do |cond|
"x #{cond}"
end).join("\n")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment