scorpion-ioc 0.4.0 → 0.5.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/Gemfile +0 -1
  4. data/README.md +4 -1
  5. data/lib/scorpion.rb +38 -11
  6. data/lib/scorpion/attribute.rb +4 -1
  7. data/lib/scorpion/chain_hunter.rb +58 -0
  8. data/lib/scorpion/dependency.rb +7 -3
  9. data/lib/scorpion/dependency/builder_dependency.rb +5 -1
  10. data/lib/scorpion/dependency/class_dependency.rb +10 -7
  11. data/lib/scorpion/dependency_map.rb +11 -2
  12. data/lib/scorpion/error.rb +6 -0
  13. data/lib/scorpion/hunt.rb +32 -21
  14. data/lib/scorpion/hunter.rb +15 -2
  15. data/lib/scorpion/locale/en.yml +1 -0
  16. data/lib/scorpion/method.rb +24 -0
  17. data/lib/scorpion/nest.rb +5 -0
  18. data/lib/scorpion/object.rb +30 -23
  19. data/lib/scorpion/object_constructor.rb +23 -14
  20. data/lib/scorpion/rails.rb +1 -0
  21. data/lib/scorpion/rails/active_record/association.rb +5 -6
  22. data/lib/scorpion/rails/active_record/model.rb +4 -5
  23. data/lib/scorpion/rails/active_record/relation.rb +2 -4
  24. data/lib/scorpion/rails/controller.rb +31 -2
  25. data/lib/scorpion/rails/job.rb +12 -0
  26. data/lib/scorpion/rails/mailer.rb +42 -0
  27. data/lib/scorpion/rails/nest.rb +23 -20
  28. data/lib/scorpion/rails/railtie.rb +1 -0
  29. data/lib/scorpion/rspec/helper.rb +47 -0
  30. data/lib/scorpion/stinger.rb +0 -1
  31. data/lib/scorpion/version.rb +1 -1
  32. data/scorpion.gemspec +1 -0
  33. data/spec/lib/scorpion/attribute_spec.rb +10 -0
  34. data/spec/lib/scorpion/hunt_spec.rb +9 -3
  35. data/spec/lib/scorpion/hunter_spec.rb +13 -2
  36. data/spec/lib/scorpion/object_constructor_spec.rb +63 -6
  37. data/spec/lib/scorpion/object_spec.rb +9 -5
  38. data/spec/lib/scorpion/rails/controller_spec.rb +33 -1
  39. data/spec/lib/scorpion/rspec/helper_spec.rb +10 -0
  40. data/spec/spec_helper.rb +5 -0
  41. metadata +20 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9f9e729630d83f9132e46118f0a74a9bcc9946f
4
- data.tar.gz: 40a34e024069aaf348ce03b37065d83098920e40
3
+ metadata.gz: 93ebd370c4020bf58ad8fb775368a16a8949ebca
4
+ data.tar.gz: 5d3d644a4688b8f310f065e8d3431574c7fe142c
5
5
  SHA512:
6
- metadata.gz: 32a04845861b9ea89cd5dcd19c667a0a339a424513c7b610f9909c3995e37d45707f837f3b10c8ce2d844ef12698268ffaa50c2afde083f54cd3161c4877d792
7
- data.tar.gz: 435e3fbc679d5ad65fc7f5d2cce00adb802b5c73b91d1995f15c9012f6b0a8a99cee97c70cf7907b391d71fe831b8f8b38958249ef89ced063a9b0654a5f2f2e
6
+ metadata.gz: 2fb8f746aaf8ec843dc91ce3ea468c89d04c8cf1f442aebe65facb4a73a3a71c25f2f2204f3438f389a0c3323781b9565f32f5ef854936774047ffd8a8625847
7
+ data.tar.gz: 49fca90cccdcba2f232709c08a1f703371fbf293c53ee0fd5652eae969cf6a653f3d12a2f347b387b93bdd2070911cded579b867c6699b78c96adfa092160395
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
2
  --format Fuubar
3
-
data/Gemfile CHANGED
@@ -15,7 +15,6 @@ group :test do
15
15
  gem 'simplecov', github: "colszowka/simplecov"
16
16
  gem 'ruby_gntp', '~> 0.3.4'
17
17
  gem 'awesome_print'
18
- gem 'sqlite3'
19
18
 
20
19
  gem "codeclimate-test-reporter", group: :test, require: nil
21
20
  end
data/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  Add IoC to rails with minimal fuss and ceremony.
10
10
 
11
+ (Also check out [shog](http://github.com/phallguy/shog) for better rails logs)
12
+
11
13
  <!-- MarkdownTOC depth=4 -->
12
14
 
13
15
  - [Dependency Injection](#dependency-injection)
@@ -519,6 +521,7 @@ class SessionsController < ActionController::Base
519
521
 
520
522
  def create
521
523
  user = User.with_scorpion( scorpion ).find params[:id]
524
+ user = scorpion( User ).find params[:id]
522
525
  sign_in if user.check_password( params[:password] )
523
526
  end
524
527
  end
@@ -538,4 +541,4 @@ end
538
541
 
539
542
  [The MIT License (MIT)](http://opensource.org/licenses/MIT)
540
543
 
541
- Copyright (c) 2015 Paul Alexander
544
+ Copyright (c) 2015 Paul Alexander
data/lib/scorpion.rb CHANGED
@@ -6,10 +6,12 @@ module Scorpion
6
6
 
7
7
  require 'scorpion/version'
8
8
  require 'scorpion/error'
9
+ require 'scorpion/method'
9
10
  require 'scorpion/object'
10
11
  require 'scorpion/object_constructor'
11
12
  require 'scorpion/attribute_set'
12
13
  require 'scorpion/hunter'
14
+ require 'scorpion/chain_hunter'
13
15
  require 'scorpion/dependency_map'
14
16
  require 'scorpion/hunt'
15
17
  require 'scorpion/dependency'
@@ -20,18 +22,19 @@ module Scorpion
20
22
  # Hunts for an object that satisfies the requested `contract` and `traits`.
21
23
  # @param [Class,Module,Symbol] contract describing the desired behavior of the dependency.
22
24
  # @param [Array<Symbol>] traits required of the dependency
25
+ # @param [Hash<Symbol,Object>] dependencies to inject into the object.
23
26
  # @return [Object] an object that satisfies the contract and traits.
24
27
  # @raise [UnsuccessfulHunt] if a matching object cannot be found.
25
- def fetch_by_traits( contract, traits, *args, &block )
26
- hunt = Hunt.new( self, contract, traits, *args, &block )
28
+ def fetch_by_traits( contract, traits, *arguments, **dependencies, &block )
29
+ hunt = Hunt.new( self, contract, traits, *arguments, **dependencies, &block )
27
30
  execute hunt
28
31
  end
29
32
 
30
33
  # Hunts for an object that satisfies the requested `contract` regardless of
31
34
  # traits.
32
35
  # @see #fetch_by_traits
33
- def fetch( contract, *args, &block )
34
- fetch_by_traits( contract, nil, *args, &block )
36
+ def fetch( contract, *arguments, **dependencies, &block )
37
+ fetch_by_traits( contract, nil, *arguments, **dependencies, &block )
35
38
  end
36
39
 
37
40
  # Creates a new object and feeds it it's dependencies.
@@ -39,14 +42,33 @@ module Scorpion
39
42
  # @param [Array<Object>] args to pass to the constructor.
40
43
  # @param [#call] block to pass to the constructor.
41
44
  # @return [Scorpion::Object] the spawned object.
42
- def spawn( hunt, object_class, *args, &block )
45
+ def spawn( hunt, object_class, *arguments, **dependencies, &block )
43
46
  if object_class < Scorpion::Object
44
- object_class.spawn hunt, *args, &block
47
+ object_class.spawn hunt, *arguments, **dependencies, &block
45
48
  else
46
- object_class.new *args, &block
49
+ object_class.new *arguments, &block
47
50
  end
48
51
  end
49
52
 
53
+ # Inject the {#target} with all non-lazy dependencies.
54
+ # @param [Scorpion::Object] target to inject.
55
+ # @return [target]
56
+ def inject( target )
57
+ hunt = Scorpion::Hunt.new self, nil, nil
58
+ hunt.inject target
59
+
60
+ target
61
+ end
62
+
63
+ # Explicitly spawn an instance of {#object_class} and inject it's dependencies.
64
+ # @param [Array<Object>] args to pass to the constructor.
65
+ # @param [#call] block to pass to the constructor.
66
+ # @return [Scorpion::Object] the spawned object.
67
+ def new( object_class, *arguments, **dependencies, &block )
68
+ hunt = Hunt.new( self, object_class, nil, *arguments, **dependencies, &block )
69
+ Scorpion::Dependency::ClassDependency.new( object_class ).fetch( hunt )
70
+ end
71
+
50
72
  # Execute the `hunt` returning the desired dependency.
51
73
  # @param [Hunt] hunt to execute.
52
74
  # @return [Object] an object that satisfies the hunt contract and traits.
@@ -63,6 +85,11 @@ module Scorpion
63
85
 
64
86
  # Free up any captured dependency and release any long-held resources.
65
87
  def destroy
88
+ reset
89
+ end
90
+
91
+ # Reset the hunting map and clear all dependencies.
92
+ def reset
66
93
  end
67
94
 
68
95
  # @return [Scorpion::Nest] a nest that uses this scorpion as the mother.
@@ -106,14 +133,14 @@ module Scorpion
106
133
 
107
134
  # Hunt for dependency from the primary Scorpion {#instance}.
108
135
  # @see #fetch
109
- def self.fetch( *args, &block )
110
- instance.fetch *args, &block
136
+ def self.fetch( dependencies, &block )
137
+ instance.fetch dependencies, &block
111
138
  end
112
139
 
113
140
  # Hunt for dependency from the primary Scorpion {#instance}.
114
141
  # @see #fetch_by_traits
115
- def self.fetch_by_traits( *args, &block )
116
- instance.fetch_by_traits *args, &block
142
+ def self.fetch_by_traits( dependencies, &block )
143
+ instance.fetch_by_traits dependencies, &block
117
144
  end
118
145
 
119
146
  # @!attribute logger
@@ -13,7 +13,10 @@ module Scorpion
13
13
  # @!attribute
14
14
  # @return [Class,Module,Symbol] contract that describes the desired behavior
15
15
  # of the injected object.
16
- attr_reader :contract
16
+ def contract
17
+ @contract = @contract.constantize if @contract.is_a? String
18
+ @contract
19
+ end
17
20
 
18
21
  # @!attribute
19
22
  # @return [Array<Symbol>] traits that must match on instances of the {#contract}
@@ -0,0 +1,58 @@
1
+ module Scorpion
2
+
3
+ # Chains hunting calls to one or more managed scorpions.
4
+ class ChainHunter
5
+ include Scorpion
6
+
7
+ # ============================================================================
8
+ # @!group Attributes
9
+ #
10
+
11
+ # @!attribute
12
+ # @return [Array<Scorpion>] scorpions to chain hunting calls to.
13
+ attr_reader :scorpions
14
+
15
+ #
16
+ # @!endgroup Attributes
17
+
18
+
19
+ def initialize( *scorpions )
20
+ @scorpions = scorpions
21
+ end
22
+
23
+ # Prepare the scorpion for hunting.
24
+ # @see DependencyMap#chart
25
+ def prepare( &block )
26
+ if top = scorpions.first
27
+ top.prepare &block
28
+ end
29
+ end
30
+
31
+ # @see Scorpion#replicate
32
+ def replicate
33
+ self.class.new scorpions
34
+ end
35
+
36
+ # @see Scorpion#hunt
37
+ def execute( hunt )
38
+ # Try explicitly defined dependencies first
39
+ scorpions.each do |hunter|
40
+ begin
41
+ return hunter.execute( hunt, true )
42
+ rescue UnsuccessfulHunt
43
+ end
44
+ end
45
+
46
+ # Then allow implicit
47
+ scorpions.each do |hunter|
48
+ begin
49
+ return hunter.execute( hunt )
50
+ rescue UnsuccessfulHunt
51
+ end
52
+ end
53
+
54
+ unsuccessful_hunt hunt.contract, hunt.traits
55
+ end
56
+
57
+ end
58
+ end
@@ -92,7 +92,7 @@ module Scorpion
92
92
 
93
93
  # Define dependency based on the desired contract and traits.
94
94
  # @return [Dependency] the defined dependency.
95
- def define( contract, traits = nil , &builder )
95
+ def define( contract, traits = nil, &builder )
96
96
  options, traits = extract_options!( traits )
97
97
 
98
98
  if options.key?( :return )
@@ -104,8 +104,12 @@ module Scorpion
104
104
  elsif block_given?
105
105
  Scorpion::Dependency::BuilderDependency.new( contract, traits, builder )
106
106
  elsif contract.respond_to?( :create )
107
- Scorpion::Dependency::BuilderDependency.new( contract, traits ) do |scorpion,*args,&block|
108
- contract.create scorpion, *args, &block
107
+ Scorpion::Dependency::BuilderDependency.new( contract, traits ) do |hunt,*args,**dependencies,&block|
108
+ if dependencies.present?
109
+ contract.create hunt, *args, **dependencies, &block
110
+ else
111
+ contract.create hunt, *args, &block
112
+ end
109
113
  end
110
114
  else
111
115
  dependency_class( contract ).new( contract, traits, &builder )
@@ -24,7 +24,11 @@ module Scorpion
24
24
 
25
25
  # @see Scorpion::Dependency#fetch
26
26
  def fetch( hunt )
27
- builder.call( hunt, *hunt.arguments, &hunt.block )
27
+ if hunt.dependencies.any?
28
+ builder.call( hunt, *hunt.arguments, **hunt.dependencies, &hunt.block )
29
+ else
30
+ builder.call( hunt, *hunt.arguments, &hunt.block )
31
+ end
28
32
  end
29
33
 
30
34
  end
@@ -6,20 +6,23 @@ module Scorpion
6
6
  class ClassDependency < Scorpion::Dependency
7
7
 
8
8
  def fetch( hunt )
9
- hunt.scorpion.spawn hunt, hunt.contract, *resolve_arguments( hunt ), &hunt.block
9
+ resolved = resolve_dependencies( hunt )
10
+ hunt.scorpion.spawn hunt, hunt.contract, *hunt.arguments, **resolved, &hunt.block
10
11
  end
11
12
 
12
13
  private
13
14
 
14
- def resolve_arguments( hunt )
15
- arguments = hunt.arguments
16
- return arguments unless arguments.blank? && hunt.contract < Scorpion::Object
15
+ def resolve_dependencies( hunt )
16
+ dependencies = hunt.dependencies
17
+ return dependencies unless hunt.contract.respond_to? :initializer_injections
17
18
 
18
- hunt.contract.initializer_injections.each_with_object([]) do |attr,args|
19
- args << hunt.fetch_by_traits( attr.contract, attr.traits )
20
- end
19
+ hunt.contract.initializer_injections.each_with_object(dependencies.dup) do |attr,deps|
20
+ next if attr.lazy?
21
21
 
22
+ deps[attr.name] ||= hunt.fetch_by_traits( attr.contract, attr.traits )
23
+ end
22
24
  end
25
+
23
26
  end
24
27
  end
25
28
  end
@@ -30,8 +30,7 @@ module Scorpion
30
30
 
31
31
  def initialize( scorpion )
32
32
  @scorpion = scorpion
33
- @dependency_set = @active_dependency_set = []
34
- @shared_dependency_set = []
33
+ reset
35
34
  end
36
35
 
37
36
  # Find {Dependency} that matches the requested `contract` and `traits`.
@@ -126,6 +125,16 @@ module Scorpion
126
125
  self
127
126
  end
128
127
 
128
+ # Remove all dependency mappings.
129
+ def reset
130
+
131
+ @dependency_set.each &:release if @dependency_set
132
+ @shared_dependency_set.each &:release if @shared_dependency_set
133
+
134
+ @dependency_set = @active_dependency_set = []
135
+ @shared_dependency_set = []
136
+ end
137
+
129
138
  private
130
139
 
131
140
  def define_dependency( contract, traits, &builder )
@@ -21,6 +21,12 @@ module Scorpion
21
21
  end
22
22
  end
23
23
 
24
+ class ArityMismatch < Error
25
+ def initialize( block, expected_count )
26
+ super translate( :arity_mismatch, expected: expected_count, actual: block.arity )
27
+ end
28
+ end
29
+
24
30
  class BuilderRequiredError < Error
25
31
  def initialize( message = nil )
26
32
  super ( message || translate( :builder_required ) )
data/lib/scorpion/hunt.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Scorpion
2
- # Captures state for a specific hunt so that constructor arguments can be
2
+ # Captures state for a specific hunt so that constructor dependencies can be
3
3
  # shared with child dependencies.
4
4
  #
5
5
  # @example
@@ -45,7 +45,7 @@ module Scorpion
45
45
  attr_reader :trip
46
46
  private :trip
47
47
 
48
- delegate [:contract, :traits, :arguments, :block] => :trip
48
+ delegate [:contract, :traits, :dependencies, :arguments, :block] => :trip
49
49
 
50
50
  # @!attribute contract
51
51
  # @return [Class,Module,Symbol] contract being hunted for.
@@ -53,8 +53,11 @@ module Scorpion
53
53
  # @!attribute traits
54
54
  # @return [Array<Symbol>] traits being hunted for.
55
55
 
56
+ # @!attribute [r] dependencies
57
+ # @return [Hash<Symbol,Dependency>] hash of dependencies to pass to initializer of {#contract} when found.
58
+
56
59
  # @!attribute [r] arguments
57
- # @return [Array<Dependency>] dependency to pass to initializer of contract when found.
60
+ # @return [Array<Object>] positional arguments to pass to the initializer of {#contract} when found.
58
61
 
59
62
  # @!attribute block
60
63
  # @return [#call] block to pass to constructor of contract when found.
@@ -62,22 +65,22 @@ module Scorpion
62
65
  #
63
66
  # @!endgroup Attributes
64
67
 
65
- def initialize( scorpion, contract, traits, *arguments, &block )
68
+ def initialize( scorpion, contract, traits, *arguments, **dependencies, &block )
66
69
  @scorpion = scorpion
67
70
  @trips = []
68
- @trip = Trip.new contract, traits, arguments, block
71
+ @trip = Trip.new contract, traits, arguments, dependencies, block
69
72
  end
70
73
 
71
74
  # Hunt for additional dependency to satisfy the main hunt's contract and traits.
72
75
  # @see Scorpion#hunt
73
- def fetch( contract, *arguments, &block )
74
- fetch_by_traits( contract, nil, *arguments, &block )
76
+ def fetch( contract, *arguments, **dependencies, &block )
77
+ fetch_by_traits( contract, nil, *arguments, **dependencies, &block )
75
78
  end
76
79
 
77
80
  # Hunt for additional dependency to satisfy the main hunt's contract and traits.
78
81
  # @see Scorpion#hunt
79
- def fetch_by_traits( contract, traits, *arguments, &block )
80
- push contract, traits, arguments, block
82
+ def fetch_by_traits( contract, traits, *arguments, **dependencies, &block )
83
+ push contract, traits, arguments, dependencies, block
81
84
  execute
82
85
  ensure
83
86
  pop
@@ -88,6 +91,7 @@ module Scorpion
88
91
  # @return [Scorpion::Object] the injected object.
89
92
  def inject( object )
90
93
  trip.object = object
94
+
91
95
  object.injected_attributes.each do |attr|
92
96
  next if object.send "#{ attr.name }?"
93
97
  next if attr.lazy?
@@ -95,14 +99,17 @@ module Scorpion
95
99
  object.send :inject, attr, fetch_by_traits( attr.contract, attr.traits )
96
100
  end
97
101
 
102
+ object.send :on_injected
103
+
98
104
  object
99
105
  end
100
106
 
101
107
  # Allow the hunt to spawn objects.
102
108
  # @see Scorpion#spawn
103
- def spawn( klass, *arguments, &block )
104
- scorpion.spawn( self, klass, *arguments, &block )
109
+ def spawn( klass, *arguments, **dependencies, &block )
110
+ scorpion.spawn( self, klass, *arguments, **dependencies, &block )
105
111
  end
112
+ alias_method :new, :spawn
106
113
 
107
114
  private
108
115
 
@@ -111,12 +118,12 @@ module Scorpion
111
118
  end
112
119
 
113
120
  def execute_from_trips
114
- return if arguments.any?
121
+ return if dependencies.any?
115
122
 
116
123
  trips.each do |trip|
117
124
  return trip.object if contract === trip.object
118
- trip.arguments.each do |arg|
119
- return arg if contract === arg
125
+ trip.dependencies.each do |key,value|
126
+ return value if contract === value
120
127
  end
121
128
  end
122
129
 
@@ -127,10 +134,10 @@ module Scorpion
127
134
  scorpion.execute self
128
135
  end
129
136
 
130
- def push( contract, traits, arguments, block )
137
+ def push( contract, traits, arguments, dependencies, block )
131
138
  trips.push trip
132
139
 
133
- @trip = Trip.new contract, traits, arguments, block
140
+ @trip = Trip.new contract, traits, arguments, dependencies, block
134
141
  end
135
142
 
136
143
  def pop
@@ -141,15 +148,19 @@ module Scorpion
141
148
  attr_reader :contract
142
149
  attr_reader :traits
143
150
  attr_reader :arguments
151
+ attr_reader :dependencies
144
152
  attr_reader :block
145
153
 
146
154
  attr_accessor :object
147
155
 
148
- def initialize( contract, traits, arguments, block )
149
- @contract = contract
150
- @traits = traits
151
- @arguments = arguments
152
- @block = block
156
+ def initialize( contract, traits, arguments, dependencies, block )
157
+ @contract = contract
158
+ @traits = traits
159
+ @arguments = arguments
160
+ @dependencies = dependencies
161
+ @block = block
162
+
163
+ @caller = caller
153
164
  end
154
165
  end
155
166