Class: Slayer::Service
- Inherits:
-
Object
- Object
- Slayer::Service
- Defined in:
- lib/slayer/service.rb
Overview
Slayer Services are objects that should implement re-usable pieces of application logic or common tasks. To prevent circular dependencies Services are required to declare which other Service classes they depend on. If a circular dependency is detected an error is raised.
In order to enforce the lack of circular dependencies, Service objects can only call other Services that are declared in their dependencies.
Class Attribute Summary collapse
-
.deps ⇒ Object
readonly
Returns the value of attribute deps.
Class Method Summary collapse
- .after_each_method ⇒ Object
- .before_each_method ⇒ Object
-
.dependencies(*deps) ⇒ Array<Class>
List the other Service class that this service class depends on.
- .method_added(name) ⇒ Object
- .raise_if_not_allowed ⇒ Object
- .singleton_method_added(name) ⇒ Object
- .transitive_dependencies(dependency_hash = {}, visited = []) ⇒ Object
Class Attribute Details
.deps ⇒ Object (readonly)
Returns the value of attribute deps.
66 67 68 |
# File 'lib/slayer/service.rb', line 66 def deps @deps end |
Class Method Details
.after_each_method ⇒ Object
126 127 128 129 |
# File 'lib/slayer/service.rb', line 126 def after_each_method(*) @@allowed_services.pop @@allowed_services = nil if @@allowed_services.empty? end |
.before_each_method ⇒ Object
105 106 107 108 109 110 111 112 113 114 |
# File 'lib/slayer/service.rb', line 105 def before_each_method(*) @deps ||= [] @@allowed_services ||= nil # Confirm that this method call is allowed raise_if_not_allowed @@allowed_services ||= [] @@allowed_services << (@deps + [self]) end |
.dependencies(*deps) ⇒ Array<Class>
List the other Service class that this service class depends on. Only dependencies that are included in this call my be invoked from class or instances methods of this service class.
If no dependencies are provided, no other Service classes may be used by this Service class.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/slayer/service.rb', line 41 def self.dependencies(*deps) raise(ServiceDependencyError, "There were multiple dependencies calls of #{self}") if @deps deps.each do |dep| unless dep.is_a?(Class) raise(ServiceDependencyError, "The object #{dep} passed to dependencies service was not a class") end unless dep < Slayer::Service raise(ServiceDependencyError, "The object #{dep} passed to dependencies was not a subclass of #{self}") end end unless deps.uniq.length == deps.length raise(ServiceDependencyError, "There were duplicate dependencies in #{self}") end @deps = deps # Calculate the transitive dependencies and raise an error if there are circular dependencies transitive_dependencies end |
.method_added(name) ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/slayer/service.rb', line 156 def method_added(name) return if self == Slayer::Service return if @__last_methods_added && @__last_methods_added.include?(name) with = :"#{name}_with_before_each_method" without = :"#{name}_without_before_each_method" @__last_methods_added = [name, with, without] define_method with do |*args, &block| self.class.before_each_method name begin send without, *args, &block rescue raise ensure self.class.after_each_method name end end alias_method without, name alias_method name, with @__last_methods_added = nil end |
.raise_if_not_allowed ⇒ Object
116 117 118 119 120 121 122 123 124 |
# File 'lib/slayer/service.rb', line 116 def raise_if_not_allowed if @@allowed_services allowed = @@allowed_services.last if !allowed || !allowed.include?(self) raise(ServiceDependencyError, "Attempted to call #{self} from another #{Slayer::Service}"\ ' which did not declare it as a dependency') end end end |
.singleton_method_added(name) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/slayer/service.rb', line 131 def singleton_method_added(name) return if self == Slayer::Service return if @__last_methods_added && @__last_methods_added.include?(name) with = :"#{name}_with_before_each_method" without = :"#{name}_without_before_each_method" @__last_methods_added = [name, with, without] define_singleton_method with do |*args, &block| before_each_method name begin send without, *args, &block rescue raise ensure after_each_method name end end singleton_class.send(:alias_method, without, name) singleton_class.send(:alias_method, name, with) @__last_methods_added = nil end |
.transitive_dependencies(dependency_hash = {}, visited = []) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/slayer/service.rb', line 68 def transitive_dependencies(dependency_hash = {}, visited = []) return @transitive_dependencies if @transitive_dependencies @deps ||= [] # If we've already visited ourself, bail out. This is necessary to halt # execution for a circular chain of dependencies. #halting-problem-solved return dependency_hash[self] if visited.include?(self) visited << self dependency_hash[self] ||= [] # Add each of our dependencies (and it's transitive dependency chain) to our # own dependencies. @deps.each do |dep| dependency_hash[self] << dep unless visited.include?(dep) child_transitive_dependencies = dep.transitive_dependencies(dependency_hash, visited) dependency_hash[self].concat(child_transitive_dependencies) end dependency_hash[self].uniq end # NO CIRCULAR DEPENDENCIES! if dependency_hash[self].include? self raise(ServiceDependencyError, "#{self} had a circular dependency") end # Store these now, so next time we can short-circuit. @transitive_dependencies = dependency_hash[self] return @transitive_dependencies end |