Created
March 20, 2019 15:16
-
-
Save Startouf/97759c40a176b022383972934f6e1474 to your computer and use it in GitHub Desktop.
Specification pattern implementation for ActiveJob
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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