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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -10
  3. data/README.md +414 -26
  4. data/circle.yml +5 -0
  5. data/config.ru +7 -0
  6. data/lib/scorpion.rb +87 -0
  7. data/lib/scorpion/attribute.rb +71 -0
  8. data/lib/scorpion/attribute_set.rb +96 -0
  9. data/lib/scorpion/error.rb +29 -0
  10. data/lib/scorpion/hunter.rb +54 -0
  11. data/lib/scorpion/hunting_map.rb +115 -0
  12. data/lib/scorpion/king.rb +150 -0
  13. data/lib/scorpion/locale/en.yml +5 -0
  14. data/lib/scorpion/nest.rb +37 -0
  15. data/lib/scorpion/prey.rb +94 -0
  16. data/lib/scorpion/prey/builder_prey.rb +32 -0
  17. data/lib/scorpion/prey/captured_prey.rb +44 -0
  18. data/lib/scorpion/prey/class_prey.rb +13 -0
  19. data/lib/scorpion/prey/hunted_prey.rb +14 -0
  20. data/lib/scorpion/prey/module_prey.rb +14 -0
  21. data/lib/scorpion/rails.rb +1 -1
  22. data/lib/scorpion/rails/controller.rb +89 -0
  23. data/lib/scorpion/version.rb +1 -1
  24. data/scorpion.gemspec +2 -0
  25. data/spec/internal/config/database.yml +3 -0
  26. data/spec/internal/config/routes.rb +3 -0
  27. data/spec/internal/db/combustion_test.sqlite +0 -0
  28. data/spec/internal/db/schema.rb +3 -0
  29. data/spec/internal/log/.gitignore +1 -0
  30. data/spec/internal/public/favicon.ico +0 -0
  31. data/spec/lib/scorpion/attribute_set_spec.rb +89 -0
  32. data/spec/lib/scorpion/attribute_spec.rb +8 -0
  33. data/spec/lib/scorpion/error_spec.rb +8 -0
  34. data/spec/lib/scorpion/hunter_spec.rb +69 -0
  35. data/spec/lib/scorpion/hunting_map_spec.rb +118 -0
  36. data/spec/lib/scorpion/{scorpion.rb → instance_spec.rb} +0 -0
  37. data/spec/lib/scorpion/king_spec.rb +129 -0
  38. data/spec/lib/scorpion/prey/module_prey_spec.rb +16 -0
  39. data/spec/lib/scorpion/prey_spec.rb +76 -0
  40. data/spec/lib/scorpion/rails/controller_spec.rb +111 -0
  41. data/spec/lib/scorpion_spec.rb +0 -1
  42. data/spec/spec_helper.rb +6 -0
  43. metadata +78 -6
  44. data/Procfile +0 -1
  45. 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
@@ -0,0 +1,5 @@
1
+ en:
2
+ scorpion:
3
+ errors:
4
+ messages:
5
+ unsuccessful_hunt: "Couldn't find a %{contract} builder with traits '%{traits}'"
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,13 @@
1
+ require 'scorpion/prey'
2
+
3
+ module Scorpion
4
+ class Prey
5
+ # {Prey} for a {Class} contract
6
+ class ClassPrey < Scorpion::Prey
7
+
8
+ def fetch( scorpion, *args, &block )
9
+ scorpion.spawn contract, *args, &block
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,14 @@
1
+ require 'scorpion/prey'
2
+
3
+ module Scorpion
4
+ class Prey
5
+ # {Prey} for a {Module} contract
6
+ class ModulePrey < Scorpion::Prey
7
+
8
+ def fetch( scorpion, *args, &block )
9
+ contract
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
1
  module Scorpion
2
2
  module Rails
3
- require 'scorpion/rails/railtie'
3
+ require 'scorpion/rails/controller'
4
4
  end
5
5
  end
@@ -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