scorpion-ioc 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|