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
@@ -5,17 +5,16 @@ module Scorpion
5
5
  module Rails
6
6
  # Handles building a scorpion to handle a single request and populating
7
7
  # all the dependencies automatically.
8
+ #
9
+ # The host class must respond to #scorpion, #assign_scorpion(scorpion) and
10
+ # #free_scorpion.
8
11
  module Nest
12
+ include Scorpion::Object
9
13
 
10
14
  # ============================================================================
11
15
  # @!group Attributes
12
16
  #
13
17
 
14
- # @!attribute
15
- # @return [Scorpion] the scorpion used to fetch dependencies.
16
- attr_reader :scorpion
17
- private :scorpion
18
-
19
18
  # @!attribute
20
19
  # @return [Scorpion::Nest] the nest used to conceive scorpions.
21
20
  def nest
@@ -23,12 +22,17 @@ module Scorpion
23
22
  end
24
23
  private :nest
25
24
 
25
+ def scorpion( scope = nil )
26
+ # Make sure a scorpion is always available. Will be freed on the next
27
+ # call to #with_scorpion
28
+ ensure_scorpion( super ) unless scope
29
+ super
30
+ end
31
+
26
32
  #
27
33
  # @!endgroup Attributes
28
34
 
29
35
  def self.included( base )
30
- # Setup dependency injection
31
- base.send :include, Scorpion::Object
32
36
 
33
37
  # @!attribute [rw]
34
38
  # @return [Scorpion::Nest] the singleton nest used by controllers.
@@ -60,27 +64,26 @@ module Scorpion
60
64
  # Fetch a scorpion and feed the controller it's dependencies, then yield
61
65
  # to perform the action within the context of that scorpion.
62
66
  def with_scorpion( &block )
63
- assign_scorpion( nest.conceive )
64
-
65
- prepare_scorpion( @scorpion ) if respond_to?( :prepare_scorpion, true )
66
-
67
- hunt = Scorpion::Hunt.new @scorpion, nil, nil
68
- hunt.inject self
67
+ ensure_scorpion( scorpion )
68
+ scorpion.inject self
69
69
 
70
70
  yield
71
71
  ensure
72
72
  free_scorpion
73
73
  end
74
74
 
75
- private
75
+ def conceive_scorpion
76
+ nest.conceive
77
+ end
76
78
 
77
- def assign_scorpion( scorpion )
78
- @scorpion = scorpion
79
- end
79
+ def ensure_scorpion( existing )
80
+ scorpion = existing
81
+ scorpion = assign_scorpion( conceive_scorpion ) unless existing
82
+
83
+ prepare_scorpion( scorpion ) if respond_to?( :prepare_scorpion, true )
84
+ scorpion
85
+ end
80
86
 
81
- def free_scorpion
82
- @scorpion = nil
83
- end
84
87
  end
85
88
  end
86
89
  end
@@ -7,6 +7,7 @@ module Scorpion
7
7
  initializer "scorpion.configure" do |app|
8
8
  ::ActionController::Base.send :include, Scorpion::Rails::Controller
9
9
  ::ActiveJob::Base.send :include, Scorpion::Rails::Job
10
+ ::ActionMailer::Base.send :include, Scorpion::Rails::Mailer
10
11
 
11
12
  ::Scorpion::Rails::ActiveRecord.install!
12
13
  end
@@ -6,18 +6,65 @@ module Scorpion
6
6
  base.let( :scorpion ){ Scorpion::Rspec.scorpion_nest.conceive }
7
7
  base.send :extend, Scorpion::Rspec::Helper::Methods
8
8
 
9
+ [ ActionController::Base, ActiveJob::Base, ActionMailer::Base ].each do |intercept|
10
+ base.infest_nest intercept
11
+ end
12
+
9
13
  super
10
14
  end
11
15
 
12
16
 
13
17
  module Methods
14
18
 
19
+ # Intercept calls to conceive_scorpion in classes that include
20
+ # {Scorpion::Rails::Nest}
21
+ # @param [Class] klass that includes {Scorpion::Rails::Nest}
22
+ # @return [void]
23
+ def infest_nest( klass )
24
+ return unless klass < Scorpion::Rails::Nest
25
+
26
+ before( :each ) do
27
+ allow_any_instance_of( klass ).to receive( :conceive_scorpion )
28
+ .and_wrap_original do |m|
29
+ # When hunting for dependencies in controllers, jobs, etc. first
30
+ # consider the dependencies defined in the specs.
31
+ Scorpion::ChainHunter.new( scorpion, m.call )
32
+ end
33
+ end
34
+ end
35
+
36
+ # Set up scorpion hunting rules for the spec.
15
37
  def scorpion( &block )
16
38
  before( :each ) do
17
39
  scorpion.prepare &block
18
40
  end
19
41
  end
20
42
 
43
+ # Specify a specific hunting contract and prepare a `let` block of the
44
+ # same name.
45
+ def hunt( name, contract, value = :unspecified, &block )
46
+ block ||= ->{ value == :unspecified ? instance_double( contract ) : value }
47
+
48
+ let( name, &block )
49
+
50
+ before( :each ) do
51
+ scorpion.prepare do |hunter|
52
+ if value == :unspecified
53
+ hunter.hunt_for contract do
54
+ send( name )
55
+ end
56
+ else
57
+ hunter.hunt_for contract, return: value
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def hunt!( name, contract, value = :unspecified, &block )
64
+ hunt name, contract, value, &block
65
+ before(:each){ send name }
66
+ end
67
+
21
68
  end
22
69
 
23
70
  end
@@ -19,7 +19,6 @@ module Scorpion
19
19
 
20
20
  private
21
21
  def method_missing( *args, &block )
22
- binding.pry
23
22
  @__stinger__.sting! @__instance__.__send__( *args, &block )
24
23
  end
25
24
  end
@@ -1,5 +1,5 @@
1
1
  module Scorpion
2
- VERSION_NUMBER = "0.4.0"
2
+ VERSION_NUMBER = "0.5.0"
3
3
  VERSION_SUFFIX = ""
4
4
  VERSION = "#{VERSION_NUMBER}#{VERSION_SUFFIX}"
5
5
  end
data/scorpion.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "rspec", '~> 3.00'
27
27
  spec.add_development_dependency "rspec-rails", '~> 3.00'
28
28
  spec.add_development_dependency 'combustion', '~> 0.5.3'
29
+ spec.add_development_dependency 'sqlite3'
29
30
  end
@@ -1,8 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
+ module Test
4
+ class Attr; end
5
+ end
6
+
7
+
3
8
  describe Scorpion::Attribute do
4
9
  it "responds to trait? methods" do
5
10
  attr = Scorpion::Attribute.new :name, :contract, :formatted
6
11
  expect( attr ).to be_formatted
7
12
  end
13
+
14
+ it "resolves contract strings to constants" do
15
+ attr = Scorpion::Attribute.new :name, 'Test::Attr'
16
+ expect( attr.contract ).to be Test::Attr
17
+ end
8
18
  end
@@ -39,14 +39,14 @@ describe Scorpion::Hunt do
39
39
  end
40
40
 
41
41
  it "finds matching argument in parent" do
42
- hunt.arguments << "Hello"
42
+ hunt.dependencies[:label] = "Hello"
43
43
 
44
44
  expect( hunt.fetch String ).to eq "Hello"
45
45
  end
46
46
 
47
47
  it "finds matching argument in grandparent" do
48
- hunt = Scorpion::Hunt.new scorpion, String, nil, "Hello"
49
- hunt.send :push, Regexp, nil, [], nil
48
+ hunt = Scorpion::Hunt.new scorpion, String, nil, label: "Hello"
49
+ hunt.send :push, Regexp, nil, [], {}, nil
50
50
 
51
51
  expect( scorpion ).to receive( :execute ) do |hunt|
52
52
  next if hunt.contract == String
@@ -88,6 +88,12 @@ describe Scorpion::Hunt do
88
88
  hunt.inject target
89
89
  expect( target.sailor? ).to be_falsy
90
90
  end
91
+
92
+ it "invokes on_injected" do
93
+ expect( target ).to receive( :on_injected )
94
+ hunt.inject target
95
+ end
96
+
91
97
  end
92
98
 
93
99
  end
@@ -94,17 +94,28 @@ describe Scorpion::Hunter do
94
94
  expect( hunter.fetch_by_traits Test::Hunter::Implicit, [] ).to be_a Test::Hunter::Implicit
95
95
  end
96
96
 
97
+ it "initialize explicit contracts" do
98
+ zoo = hunter.new Test::Hunter::Zoo
99
+ expect( zoo ).to be_a Test::Hunter::Zoo
100
+ expect( zoo.scorpion ).to eq hunter
101
+ end
102
+
103
+ it "delegates hunting definitions" do
104
+ hunter.hunt_for Test::Hunter::Zoo, return: :nyc
105
+ expect( hunter.fetch Test::Hunter::Zoo ).to eq :nyc
106
+ end
107
+
97
108
  context "child dependencies" do
98
109
  it "passes initializer args to child dependencies" do
99
110
  zoo = Test::Hunter::Zoo.new
100
- city = hunter.fetch Test::Hunter::City, zoo
111
+ city = hunter.fetch Test::Hunter::City, zoo: zoo
101
112
 
102
113
  expect( city.park.zoo ).to be zoo
103
114
  end
104
115
 
105
116
  it "passes self to child dependencies" do
106
117
  zoo = Test::Hunter::Zoo.new
107
- city = hunter.fetch Test::Hunter::City, zoo
118
+ city = hunter.fetch Test::Hunter::City, zoo: zoo
108
119
 
109
120
  expect( city.park.city ).to be city
110
121
  end
@@ -1,14 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Scorpion::ObjectConstructor do
4
- it 'defined an initializer' do
4
+
5
+ it 'defines an initializer' do
5
6
  klass = Class.new do
6
7
  include Scorpion::Object
7
8
 
8
9
  initialize logger: String
9
10
  end
10
11
 
11
- expect( klass.instance_method(:initialize).arity ).to eq 1
12
+ expect( klass.instance_method(:initialize).arity ).to eq -1
12
13
  end
13
14
 
14
15
  it "executes initializer code" do
@@ -19,7 +20,7 @@ describe Scorpion::ObjectConstructor do
19
20
  initialize label: String, &b
20
21
  end
21
22
 
22
- klass.new "home"
23
+ klass.new label: "home"
23
24
  end.to yield_control
24
25
  end
25
26
 
@@ -27,13 +28,13 @@ describe Scorpion::ObjectConstructor do
27
28
  klass = Class.new do
28
29
  include Scorpion::Object
29
30
 
30
- initialize label: String do |&block|
31
+ initialize label: String do |label, &block|
31
32
  block.call
32
33
  end
33
34
  end
34
35
 
35
36
  expect do |b|
36
- klass.new "apples", &b
37
+ klass.new label: "apples", &b
37
38
  end.to yield_control
38
39
  end
39
40
 
@@ -44,6 +45,62 @@ describe Scorpion::ObjectConstructor do
44
45
  initialize label: String
45
46
  end
46
47
 
47
- expect( klass.new( "apples" ).label ).to eq "apples"
48
+ expect( klass.new( label: "apples" ).label ).to eq "apples"
49
+ end
50
+
51
+ it "invokes all initializers" do
52
+ klass = Class.new do
53
+ include Scorpion::Object
54
+
55
+ initialize label: String do |label:|
56
+ @label = label.reverse
57
+ end
58
+ end
59
+
60
+ derived = Class.new( klass ) do
61
+ initialize logger: Logger do |logger:|
62
+ @logger = logger.to_s.reverse.to_sym
63
+ end
64
+ end
65
+
66
+ expect( derived.new( logger: :unset, label: "Super" ).label ).to eq "repuS"
67
+ expect( derived.new( logger: :unset, label: "Super" ).logger ).to eq :tesnu
48
68
  end
69
+
70
+
71
+ context "inheritance" do
72
+ let( :klass ) do
73
+ Class.new do
74
+ include Scorpion::Object
75
+
76
+ initialize label: String do |label:|
77
+ @label = label.reverse
78
+ end
79
+ end
80
+ end
81
+
82
+ let( :derived ) do
83
+ Class.new( klass )
84
+ end
85
+
86
+ it "inherits super initializer block" do
87
+ expect( derived.new( label: "Super" ).label ).to eq "repuS"
88
+ end
89
+
90
+ it "inherits initializer_injections" do
91
+ expect( klass.initializer_injections.count ).to eq 1
92
+ expect( klass.initializer_injections ).to eq derived.initializer_injections
93
+ end
94
+
95
+ it "can override initializer_injections" do
96
+ more_derived = Class.new( derived ) do
97
+ initialize do
98
+ end
99
+ end
100
+
101
+ expect( more_derived.initializer_injections.count ).to eq 0
102
+ end
103
+
104
+ end
105
+
49
106
  end
@@ -63,6 +63,14 @@ describe Scorpion::Object do
63
63
  allow( hunt ).to receive( :scorpion ).and_return scorpion
64
64
  end
65
65
 
66
+ it "stings scopes" do
67
+ scope = double
68
+
69
+ expect( scope ).to receive( :with_scorpion ).with( scorpion )
70
+
71
+ Test::Object::Mouse.spawn( hunt ).scorpion( scope )
72
+ end
73
+
66
74
  describe ".spawn" do
67
75
 
68
76
  it "can spawn" do
@@ -76,7 +84,7 @@ describe Scorpion::Object do
76
84
  end
77
85
 
78
86
  it "can inherit" do
79
- mouse = Test::Object::Mouse.spawn hunt, name: 'name'
87
+ mouse = Test::Object::Mouse.spawn hunt, { name: 'name' }, {}
80
88
  expect( mouse.family ).to eq 'mouse'
81
89
  expect( mouse.options ).to include name: 'name'
82
90
  end
@@ -87,10 +95,6 @@ describe Scorpion::Object do
87
95
  end.to yield_control
88
96
  end
89
97
 
90
- it "invokes on_injected" do
91
- expect_any_instance_of( Test::Object::Mouse ).to receive( :on_injected )
92
- Test::Object::Mouse.spawn hunt
93
- end
94
98
  end
95
99
 
96
100
  describe "accessors" do
@@ -10,7 +10,6 @@ end
10
10
 
11
11
  describe Scorpion::Rails::Controller, type: :controller do
12
12
  controller ActionController::Base do
13
- include Scorpion::Rails::Controller
14
13
 
15
14
  depend_on do
16
15
  service Test::Nest::Service
@@ -42,6 +41,29 @@ describe Scorpion::Rails::Controller, type: :controller do
42
41
  expect( subject ).to respond_to :scorpion
43
42
  end
44
43
 
44
+ it "retrieves scorpion from `env`" do
45
+ expect( subject.env ).to receive( :[] )
46
+ .with( Scorpion::Rails::Controller::ENV_KEY )
47
+ .at_least( :once )
48
+ .and_call_original
49
+
50
+ get :index
51
+ end
52
+
53
+ it "stores the scorpion in `env`" do
54
+ expect( subject.env ).to receive( :[]= )
55
+ .with( Scorpion::Rails::Controller::ENV_KEY, kind_of( Scorpion ) )
56
+ .at_least( :once )
57
+ .and_call_original
58
+
59
+ get :index
60
+ end
61
+
62
+ it "prepares a scorpion outside of a request when accessed" do
63
+ expect( subject.env[Scorpion::Rails::Controller::ENV_KEY] ).to be_nil
64
+ expect( subject.scorpion ).not_to be_nil
65
+ end
66
+
45
67
  it "initializes non-lazy dependencies" do
46
68
  expect( subject.cache ).to be_present
47
69
  end
@@ -107,5 +129,15 @@ describe Scorpion::Rails::Controller, type: :controller do
107
129
 
108
130
  get :index
109
131
  end
132
+
133
+
134
+ it "scopes relations" do
135
+ allow( subject ).to receive( :index ) do
136
+ expect( subject.scorpion( Todo ).all.scorpion ).to be subject.scorpion
137
+ controller.render nothing: true
138
+ end
139
+
140
+ get :index
141
+ end
110
142
  end
111
143
  end