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.
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