scorpion-ioc 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +14 -10
- data/README.md +414 -26
- data/circle.yml +5 -0
- data/config.ru +7 -0
- data/lib/scorpion.rb +87 -0
- data/lib/scorpion/attribute.rb +71 -0
- data/lib/scorpion/attribute_set.rb +96 -0
- data/lib/scorpion/error.rb +29 -0
- data/lib/scorpion/hunter.rb +54 -0
- data/lib/scorpion/hunting_map.rb +115 -0
- data/lib/scorpion/king.rb +150 -0
- data/lib/scorpion/locale/en.yml +5 -0
- data/lib/scorpion/nest.rb +37 -0
- data/lib/scorpion/prey.rb +94 -0
- data/lib/scorpion/prey/builder_prey.rb +32 -0
- data/lib/scorpion/prey/captured_prey.rb +44 -0
- data/lib/scorpion/prey/class_prey.rb +13 -0
- data/lib/scorpion/prey/hunted_prey.rb +14 -0
- data/lib/scorpion/prey/module_prey.rb +14 -0
- data/lib/scorpion/rails.rb +1 -1
- data/lib/scorpion/rails/controller.rb +89 -0
- data/lib/scorpion/version.rb +1 -1
- data/scorpion.gemspec +2 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +3 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/scorpion/attribute_set_spec.rb +89 -0
- data/spec/lib/scorpion/attribute_spec.rb +8 -0
- data/spec/lib/scorpion/error_spec.rb +8 -0
- data/spec/lib/scorpion/hunter_spec.rb +69 -0
- data/spec/lib/scorpion/hunting_map_spec.rb +118 -0
- data/spec/lib/scorpion/{scorpion.rb → instance_spec.rb} +0 -0
- data/spec/lib/scorpion/king_spec.rb +129 -0
- data/spec/lib/scorpion/prey/module_prey_spec.rb +16 -0
- data/spec/lib/scorpion/prey_spec.rb +76 -0
- data/spec/lib/scorpion/rails/controller_spec.rb +111 -0
- data/spec/lib/scorpion_spec.rb +0 -1
- data/spec/spec_helper.rb +6 -0
- metadata +78 -6
- data/Procfile +0 -1
- data/lib/scorpion/rails/railtie.rb +0 -15
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'scorpion/attribute_set'
|
2
|
+
|
3
|
+
module Scorpion
|
4
|
+
# Identifies objects that are served by {Scorpion scorpions} that feed on
|
5
|
+
# {Scorpion#hunt! hunted} prey.
|
6
|
+
module King
|
7
|
+
|
8
|
+
# ============================================================================
|
9
|
+
# @!group Attributes
|
10
|
+
#
|
11
|
+
|
12
|
+
# @!attribute
|
13
|
+
# @return [Scorpion] the scorpion used to hunt down prey.
|
14
|
+
attr_reader :scorpion
|
15
|
+
|
16
|
+
# @!attribute
|
17
|
+
# @return [Scorpion::AttributeSet] the set of injected attributes and their
|
18
|
+
# settings.
|
19
|
+
def injected_attributes
|
20
|
+
self.class.injected_attributes
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# @!endgroup Attributes
|
25
|
+
|
26
|
+
# Feeds one of the {#injected_attributes} to the object.
|
27
|
+
# @param [Scorpion::Attribute] attribute to be fed.
|
28
|
+
# @param [Object] food the value of the attribute
|
29
|
+
# @visibility private
|
30
|
+
#
|
31
|
+
# This method is used by the {#scorpion} to feed the king. Do not call it
|
32
|
+
# directly.
|
33
|
+
def feed( attribute, food )
|
34
|
+
send "#{ attribute.name }=", food
|
35
|
+
end
|
36
|
+
|
37
|
+
# Crown the object as a king and prepare it to be fed.
|
38
|
+
def self.crown( base )
|
39
|
+
base.extend Scorpion::King::ClassMethods
|
40
|
+
if base.is_a? Class
|
41
|
+
base.class_exec do
|
42
|
+
|
43
|
+
# Span a new instance of this class with all non-lazy dependencies
|
44
|
+
# satisfied.
|
45
|
+
# @param [Scorpion] scorpion that will hunt for dependencies.
|
46
|
+
def self.spawn( scorpion, *args, &block )
|
47
|
+
new( *args, &block ).tap do |king|
|
48
|
+
king.instance_variable_set :@scorpion, scorpion
|
49
|
+
# Go hunt for dependencies that are not lazy and initialize the
|
50
|
+
# references.
|
51
|
+
scorpion.feed! king
|
52
|
+
king.send :on_fed
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.included( base )
|
60
|
+
crown( base )
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.prepended( base )
|
65
|
+
crown( base )
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Called after the king has been initialized and feed all its required
|
73
|
+
# dependencies. It should be used in place of #initialize when the
|
74
|
+
# constructor needs access to injected attributes.
|
75
|
+
def on_fed
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convenience method to ask the {#scorpion} to hunt for an object.
|
79
|
+
# @see Scorpion#hunt!
|
80
|
+
def hunt!( contract, *args, &block )
|
81
|
+
scorpion.hunt! contract, *args, &block
|
82
|
+
end
|
83
|
+
|
84
|
+
# Convenience method to ask the {#scorpion} to hunt for an object.
|
85
|
+
# @see Scorpion#hunt_by_traits!
|
86
|
+
def hunt_by_traits!( contract, traits, *args, &block )
|
87
|
+
scorpion.hunt_by_traits! contract, *args, &block
|
88
|
+
end
|
89
|
+
|
90
|
+
module ClassMethods
|
91
|
+
|
92
|
+
# Tells a {Scorpion} what to inject into the class when it is constructed
|
93
|
+
# @return [nil]
|
94
|
+
# @see AttributeSet#define
|
95
|
+
def feed_on( &block )
|
96
|
+
injected_attributes.define &block
|
97
|
+
build_injected_attributes
|
98
|
+
end
|
99
|
+
alias_method :inject, :feed_on
|
100
|
+
alias_method :depend_on, :feed_on
|
101
|
+
|
102
|
+
# @!attribute
|
103
|
+
# @return [Scorpion::AttributeSet] the set of injected attriutes.
|
104
|
+
def injected_attributes
|
105
|
+
@injected_attributes ||= begin
|
106
|
+
attr = AttributeSet.new
|
107
|
+
attr.inherit! superclass.injected_attributes if superclass.respond_to? :injected_attributes
|
108
|
+
attr
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def build_injected_attributes
|
115
|
+
injected_attributes.each do |attr|
|
116
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
117
|
+
def #{ attr.name }
|
118
|
+
@#{ attr.name } ||= begin
|
119
|
+
attr = injected_attributes[ :#{ attr.name } ]
|
120
|
+
scorpion.hunt!( attr.contract, attr.traits )
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def #{ attr.name }=( value )
|
125
|
+
@#{ attr.name } = value
|
126
|
+
end
|
127
|
+
|
128
|
+
def #{ attr.name }?
|
129
|
+
!!@#{ attr.name }
|
130
|
+
end
|
131
|
+
RUBY
|
132
|
+
|
133
|
+
unless attr.public?
|
134
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
135
|
+
private :#{ attr.name }=
|
136
|
+
private :#{ attr.name }?
|
137
|
+
RUBY
|
138
|
+
end
|
139
|
+
|
140
|
+
if attr.private?
|
141
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
142
|
+
private :#{ attr.name }
|
143
|
+
RUBY
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/scorpion/nest.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Scorpion
|
2
|
+
# A scorpion factory
|
3
|
+
class Nest
|
4
|
+
|
5
|
+
# ============================================================================
|
6
|
+
# @!group Associations
|
7
|
+
#
|
8
|
+
|
9
|
+
# @!attribute
|
10
|
+
# @return [Scorpion] the mother scorpion that that will {#conceive} new
|
11
|
+
# scorpions for each request.
|
12
|
+
attr_reader :mother
|
13
|
+
|
14
|
+
#
|
15
|
+
# @!endgroup Associations
|
16
|
+
|
17
|
+
def initialize( mother = nil )
|
18
|
+
@mother = mother || Scorpion::Hunter.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def prepare( &block )
|
22
|
+
mother.prepare &block
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Scorpion] a new scorpion used to hunt for dependencies.
|
26
|
+
def conceive
|
27
|
+
mother.replicate
|
28
|
+
end
|
29
|
+
|
30
|
+
# Free up any persistent resources
|
31
|
+
def destroy
|
32
|
+
mother.destroy
|
33
|
+
@mother = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Scorpion
|
2
|
+
# Prey that can be fed to a {Scorpion::King} by a {Scorpion}.
|
3
|
+
class Prey
|
4
|
+
|
5
|
+
require 'scorpion/prey/captured_prey'
|
6
|
+
require 'scorpion/prey/class_prey'
|
7
|
+
require 'scorpion/prey/module_prey'
|
8
|
+
require 'scorpion/prey/builder_prey'
|
9
|
+
require 'scorpion/prey/hunted_prey'
|
10
|
+
|
11
|
+
# ============================================================================
|
12
|
+
# @!group Attributes
|
13
|
+
#
|
14
|
+
|
15
|
+
# @!attribute
|
16
|
+
# @return [Class,Module,Symbol] contract describing the desired behavior of the prey.
|
17
|
+
attr_reader :contract
|
18
|
+
|
19
|
+
# @!attribute
|
20
|
+
# @return [Array<Symbol>] the traits available on the prey.
|
21
|
+
attr_reader :traits
|
22
|
+
|
23
|
+
#
|
24
|
+
# @!endgroup Attributes
|
25
|
+
|
26
|
+
def initialize( contract, traits = nil )
|
27
|
+
@contract = contract
|
28
|
+
@traits = Set.new( Array( traits ) )
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Boolean] if the prey satisfies the required contract and traits.
|
32
|
+
def satisfies?( contract, traits = nil )
|
33
|
+
satisfies_contract?( contract ) && satisfies_traits?( traits )
|
34
|
+
end
|
35
|
+
|
36
|
+
# Fetch an instance of the prey.
|
37
|
+
# @param [Scorpion] scorpion hunting for the prey.
|
38
|
+
# @param [Array<Object>] arguments to the consructor of the prey.
|
39
|
+
# @param [#call] block to pass to constructor.
|
40
|
+
# @return [Object] the hunted prey.
|
41
|
+
def fetch( scorpion, *args, &block )
|
42
|
+
fail "Not Implemented"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Release the prey, freeing up any long held resources.
|
46
|
+
def release
|
47
|
+
end
|
48
|
+
|
49
|
+
# Replicate the Prey.
|
50
|
+
# @return [Prey] a replication of the prey.
|
51
|
+
def replicate
|
52
|
+
dup
|
53
|
+
end
|
54
|
+
|
55
|
+
def ==( other )
|
56
|
+
return unless other
|
57
|
+
self.class == other.class &&
|
58
|
+
contract == other.contract &&
|
59
|
+
traits == other.traits
|
60
|
+
end
|
61
|
+
alias_method :eql?, :==
|
62
|
+
|
63
|
+
def hash
|
64
|
+
self.class.hash ^
|
65
|
+
contract.hash ^
|
66
|
+
traits.hash
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @return [Boolean] true if the pray satisfies the given contract.
|
72
|
+
def satisfies_contract?( contract )
|
73
|
+
if self.contract.is_a? Symbol
|
74
|
+
self.contract == contract
|
75
|
+
else
|
76
|
+
self.contract <= contract
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Boolean] true if the pray satisfies the given contract.
|
81
|
+
def satisfies_traits?( traits )
|
82
|
+
return true if traits.blank?
|
83
|
+
|
84
|
+
Array( traits ).all? do |trait|
|
85
|
+
case trait
|
86
|
+
when Symbol then self.traits.include? trait
|
87
|
+
when Module then self.contract <= trait
|
88
|
+
else fail ArgumentError, "Unsupported trait"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'scorpion/prey'
|
2
|
+
|
3
|
+
module Scorpion
|
4
|
+
class Prey
|
5
|
+
# {Prey} for an explicit builder block
|
6
|
+
class BuilderPrey < Scorpion::Prey
|
7
|
+
|
8
|
+
# ============================================================================
|
9
|
+
# @!group Attributes
|
10
|
+
#
|
11
|
+
|
12
|
+
# @!attribute
|
13
|
+
# @return [#call(scorpion)] the builder to use to fetch instances of the prey.
|
14
|
+
attr_reader :builder
|
15
|
+
|
16
|
+
#
|
17
|
+
# @!endgroup Attributes
|
18
|
+
|
19
|
+
|
20
|
+
def initialize( contract, traits = nil, &builder )
|
21
|
+
@builder = builder
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
# @see Scorpion::Prey#fetch
|
26
|
+
def fetch( scorpion, *args, &block )
|
27
|
+
builder.call( scorpion, *args, &block )
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'scorpion/prey'
|
2
|
+
|
3
|
+
module Scorpion
|
4
|
+
class Prey
|
5
|
+
class CapturedPrey < Scorpion::Prey
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# ============================================================================
|
9
|
+
# @!group Attributes
|
10
|
+
#
|
11
|
+
|
12
|
+
# @!attribute
|
13
|
+
# @return [Object] the instance that was captured.
|
14
|
+
attr_reader :instance
|
15
|
+
|
16
|
+
# @!attribute
|
17
|
+
# @return [Scorpion::Prey] the actual prey to hunt. Used to fetch the
|
18
|
+
# single {#instance}.
|
19
|
+
attr_reader :specific_prey
|
20
|
+
private :specific_prey
|
21
|
+
|
22
|
+
|
23
|
+
delegate [:contract,:traits,:satisfies?] => :specific_prey
|
24
|
+
|
25
|
+
#
|
26
|
+
# @!endgroup Attributes
|
27
|
+
|
28
|
+
def initialize( specific_prey )
|
29
|
+
@specific_prey = specific_prey
|
30
|
+
end
|
31
|
+
|
32
|
+
# @see Prey#fetch
|
33
|
+
def fetch( scorpion, *args, &block )
|
34
|
+
@instance ||= specific_prey.fetch( scorpion, *args, &block )
|
35
|
+
end
|
36
|
+
|
37
|
+
# @see Prey#release
|
38
|
+
def release
|
39
|
+
@instance = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'scorpion/prey'
|
2
|
+
|
3
|
+
module Scorpion
|
4
|
+
class Prey
|
5
|
+
# {Prey} for a contract that implements #hunt
|
6
|
+
class HuntedPrey < Scorpion::Prey
|
7
|
+
|
8
|
+
# @see Scorpion::Prey#fetch
|
9
|
+
def fetch( scorpion, *args, &block )
|
10
|
+
contract.hunt( scorpion, *args, &block )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/scorpion/rails.rb
CHANGED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'scorpion/nest'
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
|
4
|
+
module Scorpion
|
5
|
+
module Rails
|
6
|
+
# Adds a scorpion nest to rails controllers to automatically support
|
7
|
+
# injection into rails controllers.
|
8
|
+
module Controller
|
9
|
+
|
10
|
+
# ============================================================================
|
11
|
+
# @!group Attributes
|
12
|
+
#
|
13
|
+
|
14
|
+
# @!attribute
|
15
|
+
# @return [Scorpion] the scorpion used to fetch dependencies.
|
16
|
+
attr_reader :scorpion
|
17
|
+
private :scorpion
|
18
|
+
|
19
|
+
# @!attribute
|
20
|
+
# @return [Scorpion::Nest] the nest used to conceive scorpions.
|
21
|
+
def nest
|
22
|
+
self.class.nest
|
23
|
+
end
|
24
|
+
private :nest
|
25
|
+
|
26
|
+
#
|
27
|
+
# @!endgroup Attributes
|
28
|
+
|
29
|
+
|
30
|
+
def self.included( base )
|
31
|
+
# Setup dependency injection
|
32
|
+
base.send :include, Scorpion::King
|
33
|
+
base.around_action :with_scorpion
|
34
|
+
|
35
|
+
# @!attribute [rw]
|
36
|
+
# @return [Scorpion::Nest] the singleton nest used by controllers.
|
37
|
+
base.class_attribute :nest_instance
|
38
|
+
base.class_exec do
|
39
|
+
|
40
|
+
# @!attribute
|
41
|
+
# @return [Scorpion::Nest] the nest used to conceive scorpions to
|
42
|
+
# hunt for objects on each request.
|
43
|
+
def self.nest
|
44
|
+
nest_instance
|
45
|
+
end
|
46
|
+
def self.nest=( value )
|
47
|
+
nest_instance.destroy if nest_instance
|
48
|
+
self.nest_instance = value
|
49
|
+
end
|
50
|
+
|
51
|
+
# Prepare the nest for conceiving scorpions.
|
52
|
+
# @see HuntingMap#chart
|
53
|
+
def self.scorpion_nest( &block )
|
54
|
+
nest.prepare &block
|
55
|
+
end
|
56
|
+
end
|
57
|
+
base.nest ||= Scorpion.instance.build_nest
|
58
|
+
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Fetch a scorpion and feed the controller it's dependencies
|
65
|
+
def with_scorpion( &block )
|
66
|
+
@scorpion = nest.conceive
|
67
|
+
|
68
|
+
@scorpion.prepare do |hunter|
|
69
|
+
hunter.hunt_for AbstractController::Base do
|
70
|
+
self
|
71
|
+
end
|
72
|
+
# Allow dependencies to access the current request/response
|
73
|
+
hunter.hunt_for ActionDispatch::Request do |hunter|
|
74
|
+
hunter.hunt!( AbstractController::Base ).request
|
75
|
+
end
|
76
|
+
hunter.hunt_for ActionDispatch::Response do |hunter|
|
77
|
+
hunter.hunt!( AbstractController::Base ).response
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@scorpion.feed! self
|
82
|
+
|
83
|
+
yield
|
84
|
+
ensure
|
85
|
+
@scorpion = nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|