little_boxes 0.1.0 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ module LittleBoxes
2
+ class Entry
3
+ attr_accessor :name, :memo, :box, :eager, :block, :configure, :then_block, :mutex
4
+
5
+ def initialize(name:, eager:, memo:, box:, block:, configure:, then_block:)
6
+ self.name = name
7
+ self.memo = memo
8
+ self.box = box
9
+ self.eager = eager
10
+ self.configure = configure
11
+ self.then_block = then_block
12
+ self.block = block
13
+ self.mutex = Mutex.new if @memo
14
+ end
15
+
16
+ def value
17
+ if @memo
18
+ @mutex.synchronize { @block.call(@box) }
19
+ else
20
+ @block.call(@box)
21
+ end
22
+ end
23
+
24
+ def block= block
25
+ @block = Strategy.for(
26
+ block, memo: @memo, configure: @configure, then_block: @then_block
27
+ )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module LittleBoxes
2
+ class EntryDefinition
3
+ attr_accessor :name, :eager, :memo, :block, :configure
4
+
5
+ def initialize(name, eager: false, memo: false, configure: false, then_block: nil, &block)
6
+ self.name = name
7
+ self.memo = memo
8
+ self.eager = eager
9
+ self.configure = configure
10
+ self.block = block
11
+ end
12
+
13
+ def eager!
14
+ self.eager = true
15
+ end
16
+
17
+ def for(box)
18
+ Entry.new(
19
+ name: name, box: box, block: block, memo: memo,
20
+ configure: configure, eager: eager, then_block: @then_block
21
+ )
22
+ end
23
+
24
+ def then(&block)
25
+ @then_block = block
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,79 @@
1
+ module LittleBoxes
2
+ module Strategy
3
+ module_function
4
+
5
+ def for(block, memo: false, configure: false, then_block: nil)
6
+ case [memo, configure, !!then_block]
7
+ when [true, true, true]
8
+ memo_configure_then(block, then_block)
9
+ when [true, true, false]
10
+ memo_configure(block)
11
+ when [true, false, true]
12
+ memo_then(block, then_block)
13
+ when [true, false, false]
14
+ memo(block)
15
+ when [false, true, true]
16
+ configure_then(block, then_block)
17
+ when [false, true, false]
18
+ configure(block)
19
+ when [false, false, true]
20
+ then_block(block, then_block)
21
+ else
22
+ default(block)
23
+ end
24
+ end
25
+
26
+ def memo_configure(block)
27
+ value = nil
28
+ -> (bx) { value ||= do_configure(block.call(bx), bx) }
29
+ end
30
+
31
+ def memo_then(block, then_block)
32
+ value = nil
33
+ -> (bx) { value ||= block.call(bx).tap { |v| then_block.call v, bx } }
34
+ end
35
+
36
+ def memo_configure_then(block, then_block)
37
+ value = nil
38
+ -> (bx) do
39
+ value ||= do_configure(block.call(bx), bx)
40
+ .tap{ |v| then_block.call v, bx }
41
+ end
42
+ end
43
+
44
+ def memo(block)
45
+ value = nil
46
+ -> (bx) { value ||= block.call(bx) }
47
+ end
48
+
49
+ def configure(block)
50
+ -> (bx) { do_configure(block.call(bx), bx) }
51
+ end
52
+
53
+ def then_block(block, then_block)
54
+ -> (bx) { block.call(bx).tap { |v| then_block.call v, bx } }
55
+ end
56
+
57
+ def configure_then(block, then_block)
58
+ -> (bx) do
59
+ do_configure(block.call(bx), bx).tap{ |v| then_block.call v, bx }
60
+ end
61
+ end
62
+
63
+ def default(block)
64
+ block
65
+ end
66
+
67
+ def do_configure(subject, box)
68
+ config = {box: box}
69
+
70
+ config.default_proc = Proc.new do |h, name|
71
+ h[name] = h[:box][name]
72
+ end
73
+
74
+ subject.config = config
75
+
76
+ subject
77
+ end
78
+ end
79
+ end
@@ -1,3 +1,3 @@
1
1
  module LittleBoxes
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.7"
3
3
  end
@@ -22,6 +22,4 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
24
24
  spec.add_development_dependency "rake"
25
- spec.add_runtime_dependency "forwarding_dsl"
26
- spec.add_runtime_dependency "mini_object"
27
25
  end
@@ -0,0 +1,162 @@
1
+ require_relative 'spec_helper'
2
+ require 'benchmark'
3
+ require'logger'
4
+
5
+ RSpec.describe 'Benchmark the speed', benchmark: true do
6
+ def define_class name, &block
7
+ stub_const(name, Class.new).tap do |c|
8
+ c.class_eval(&block)
9
+ end
10
+ end
11
+
12
+ def iterations
13
+ 100000
14
+ end
15
+
16
+ def test_name
17
+ @__inspect_output.
18
+ gsub(/^"/,'').
19
+ gsub(/" \([^\(]+\)/,'')
20
+ end
21
+
22
+ def measure
23
+ b = Benchmark.measure{ iterations.times { yield } }
24
+ @results << {name: test_name, time: b.real}
25
+ end
26
+
27
+ before :all do
28
+ @results = []
29
+ end
30
+
31
+ after :all do
32
+ File.open 'tmp/benchmarks.log', 'w' do |r|
33
+ @results.sort_by{|res| res[:time] }.each do |res|
34
+ r.puts "#{'%0.4f' % res[:time]}s #{res[:name]}"
35
+ end
36
+ end
37
+
38
+ puts File.read 'tmp/benchmarks.log' if ENV['BENCH_OUT']
39
+ end
40
+
41
+ it '.config -> @config -> .port -> @port' do
42
+ define_class 'Server' do
43
+ def port
44
+ config.port
45
+ end
46
+
47
+ def config
48
+ @config ||= Configuration.new
49
+ end
50
+ end
51
+
52
+ define_class 'Configuration' do
53
+ def port
54
+ @port ||= 80
55
+ end
56
+ end
57
+
58
+ server = Server.new
59
+
60
+ measure { server.port }
61
+ end
62
+
63
+ it '.config -> @config -> [:port]' do
64
+ define_class 'Server' do
65
+ def port
66
+ config[:port]
67
+ end
68
+
69
+ def config
70
+ @config ||= {port: 80}
71
+ end
72
+ end
73
+
74
+ server = Server.new
75
+
76
+ measure { server.port }
77
+ end
78
+
79
+ it '.port_proc -> @port_proc -> .call' do
80
+ define_class 'Server' do
81
+ def port
82
+ port_proc.call
83
+ end
84
+
85
+ def port_proc
86
+ @port_proc ||= lambda { 90 }
87
+ end
88
+ end
89
+
90
+ server = Server.new
91
+
92
+ measure { server.port }
93
+ end
94
+
95
+ it '.port -> @port' do
96
+ define_class 'Server' do
97
+ def port
98
+ the_port
99
+ end
100
+
101
+ def the_port
102
+ @port ||= 80
103
+ end
104
+ end
105
+
106
+ server = Server.new
107
+
108
+ measure { server.port }
109
+ end
110
+
111
+ it '@port' do
112
+ define_class 'Server' do
113
+ def initialize
114
+ @port = 80
115
+ end
116
+
117
+ def port
118
+ @port
119
+ end
120
+ end
121
+
122
+ server = Server.new
123
+
124
+ measure { server.port }
125
+ end
126
+
127
+ it 'Port' do
128
+ define_class 'Server' do
129
+ Port = 80
130
+
131
+ def port
132
+ Port
133
+ end
134
+ end
135
+
136
+ server = Server.new
137
+
138
+ measure { server.port }
139
+ end
140
+
141
+ it '@config[:port]' do
142
+ define_class 'Server' do
143
+ def port
144
+ @config[:port]
145
+ end
146
+
147
+ def initialize
148
+ @config = begin
149
+ Hash.new.tap do |h|
150
+ h.default_proc = lambda do |hash, key|
151
+ h[:port] = 80
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ server = Server.new
159
+
160
+ measure { server.port }
161
+ end
162
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe 'README.md examples', :docs do
4
+ before { stub_const 'MyApp', Module.new }
5
+
6
+ describe 'Minimal example' do
7
+ it 'has a basic example' do
8
+ module MyApp
9
+ class MainBox
10
+ include LittleBoxes::Box
11
+
12
+ let(:port) { 80 }
13
+ letc(:server) { Server.new }
14
+ end
15
+
16
+ class Server
17
+ include LittleBoxes::Configurable
18
+
19
+ dependency :port
20
+ end
21
+ end
22
+
23
+ box = MyApp::MainBox.new
24
+ # => #<MyBox :server, :logger, :log_path>
25
+
26
+ box.server.port
27
+ # => 80
28
+
29
+ expect(box.server.port).to eq 80
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "[Integration] Thread safety" do
4
+ let(:configurable) { Struct.new(:config) }
5
+ let(:dependency_block) do
6
+ ->(box) do
7
+ sleep 0.1
8
+ configurable.new
9
+ end
10
+ end
11
+ let(:box) { box_class.new }
12
+
13
+ describe 'LittleBoxes::Box#let' do
14
+ let(:box_class) do
15
+ _dependency_block = dependency_block
16
+
17
+ Class.new do
18
+ include LittleBoxes::Box
19
+ let :dependency, &_dependency_block
20
+ end
21
+ end
22
+
23
+ it 'is thread safe' do
24
+ expect(dependency_block).to receive(:call).once.and_call_original
25
+ 20.times.map { Thread.new { box.dependency } }.each(&:join)
26
+ end
27
+ end
28
+
29
+ describe 'LittleBoxes::Box#letc' do
30
+ let(:box_class) do
31
+ _dependency_block = dependency_block
32
+
33
+ Class.new do
34
+ include LittleBoxes::Box
35
+ letc :dependency, &_dependency_block
36
+ end
37
+ end
38
+
39
+ it 'is thread safe' do
40
+ expect(dependency_block).to receive(:call).once.and_call_original
41
+ 20.times.map { Thread.new { box.dependency } }.each(&:join)
42
+ end
43
+ end
44
+
45
+ describe 'LittleBoxes::Box#get' do
46
+ let(:box_class) do
47
+ _dependency_block = dependency_block
48
+
49
+ Class.new do
50
+ include LittleBoxes::Box
51
+ get :dependency, &_dependency_block
52
+ end
53
+ end
54
+
55
+ it 'does not use a mutex' do
56
+ expect_any_instance_of(Mutex).to_not receive(:synchronize)
57
+ 20.times.map { Thread.new { box.dependency } }.each(&:join)
58
+ end
59
+ end
60
+
61
+ describe 'LittleBoxes::Box#getc' do
62
+ let(:box_class) do
63
+ _dependency_block = dependency_block
64
+
65
+ Class.new do
66
+ include LittleBoxes::Box
67
+ getc :dependency, &_dependency_block
68
+ end
69
+ end
70
+
71
+ it 'does not use a mutex' do
72
+ expect_any_instance_of(Mutex).to_not receive(:synchronize)
73
+ 20.times.map { Thread.new { box.dependency } }.each(&:join)
74
+ end
75
+ end
76
+ end
@@ -1,341 +1,329 @@
1
1
  require_relative '../spec_helper'
2
- require 'ostruct'
3
2
 
4
- describe LittleBoxes::Box do
5
- subject{ described_class.new }
3
+ RSpec.describe 'Box' do
4
+ def define_class name, &block
5
+ stub_const(name.to_s, Class.new).tap do |c|
6
+ c.class_eval(&block)
7
+ end
8
+ end
6
9
 
7
- describe '#customize' do
8
- it 'allows customizing dependencies' do
9
- subject.let(:loglevel) { 0 }
10
- subject.let(:logger) { double('logger', loglevel: loglevel) }
10
+ before do
11
+ define_class :FoldersBox do
12
+ include Box
11
13
 
12
- subject.customize(:logger) do |l|
13
- l.let(:loglevel) { 1 }
14
- end
15
-
16
- expect(subject.logger.loglevel).to eq 1
14
+ letc(:collection) { FoldersCollection.new }
15
+ get(:some_other_logger) { SomeOtherLogger.new }
17
16
  end
18
17
 
19
- it 'supports overriding lets attributes from the outside' do
20
- server_class = Class.new do
21
- include LittleBoxes::Dependant
22
- dependency :logger
23
- end
18
+ define_class :MainBox do
19
+ include Box
24
20
 
25
- subject.let(:logger) { :logger }
21
+ let(:logger) { |c| Logger.new level: c.log_level }
22
+ get(:log_level) { 'INFO' }
23
+ letc(:server) { Server.new }
24
+ getc(:users_collection) { UsersCollection.new }
25
+ letc(:users_api) { UsersApi }
26
26
 
27
- subject.let(:server) { server_class.new }
28
-
29
- subject.customize(:server) do
30
- let(:logger) { :new_logger }
27
+ letc(:task) { Task.new }.then do |task, box|
28
+ task.logger = :specific_logger
29
+ task.log_level = box.log_level
30
+ end
31
+ import FoldersBox
32
+
33
+ eagerc(:http_client) { HttpClient }
34
+ eager(:api_client) { |b| ApiClient.tap { |ac| ac.logger = b.logger } }
35
+ letc(:some_client) { SomeClient }
36
+ box(:folders, FoldersBox)
37
+ box(:files) do
38
+ eagerc(:rest_client) { RestClient }
31
39
  end
32
40
 
33
- expect(subject.server.logger).to be :new_logger
41
+ letc(:client_with_defaults) { |b| b.client_with_defaults_class.new }
42
+ letc(:client_with_defaults_class) { ClientWithDefaults }
43
+ let(:client_with_class_defaults) { |b| b.client_with_class_defaults_class.new }
44
+ letc(:client_with_class_defaults_class) { ClientWithClassDefaults }
34
45
  end
35
- end
36
46
 
37
- describe '#let' do
38
- it 'has freely defined registers with just a lambda' do
39
- subject.let(:loglevel) { 0 }
40
- expect(subject.loglevel).to eq 0
47
+ define_class :Server do
48
+ include Configurable
49
+
50
+ dependency :logger
51
+ public :logger
41
52
  end
42
53
 
43
- it 'memoizes the result' do
44
- n = 0
45
- subject.let(:loglevel) { n = n + 1 }
54
+ define_class :Logger do
55
+ attr_accessor :level
46
56
 
47
- expect(subject.loglevel).to eq 1
48
- expect(subject.loglevel).to eq 1
57
+ def initialize(attrs)
58
+ @level = attrs[:level]
59
+ end
49
60
  end
50
61
 
51
- it 'allows referencing other dependencies within such lambda' do
52
- subject.let(:loglevel) { 0 }
53
- subject.let(:logger) { double('logger', loglevel: loglevel) }
54
- expect(subject.logger.loglevel).to eq 0
62
+ define_class :SomeOtherLogger do
55
63
  end
56
64
 
57
- it 'has instances that have dependencies' do
58
- server_class = Class.new do
59
- include LittleBoxes::Dependant
60
- dependency :logger
61
- end
65
+ define_class :UsersCollection do
66
+ include Configurable
62
67
 
63
- subject.let(:logger) { double('logger') }
64
- subject.let(:server) { server_class.new }
65
- expect(subject.server.logger).to be subject.logger
68
+ dependency :logger
69
+ public :logger
66
70
  end
67
71
 
68
- it 'unknown dependencies raise exception' do
69
- server_class = Class.new do
70
- include LittleBoxes::Dependant
71
- dependency :unknown_dep
72
- end
72
+ define_class :FoldersCollection do
73
+ include Configurable
73
74
 
74
- subject.let(:server) { server_class.new }
75
- expect{ subject.server.unknown_dep }.to raise_error(LittleBoxes::MissingDependency)
75
+ dependency :logger
76
+ public :logger
76
77
  end
77
78
 
78
- it 'has classes that have class dependencies' do
79
- server_class = Class.new do
80
- include LittleBoxes::Dependant
81
- class_dependency :host
82
- end
79
+ define_class :UsersApi do
80
+ include Configurable
83
81
 
84
- subject.let(:host) { 'localhost' }
85
- subject.let(:server_class) { server_class }
86
- expect(subject.server_class.host).to eq 'localhost'
82
+ class_dependency :logger
83
+ public_class_method :logger
87
84
  end
88
85
 
89
- it 'supports defaults' do
90
- server_class = Class.new do
91
- include LittleBoxes::Dependant
92
- dependency :log, default: ->(d){ d.logger }
86
+ define_class :Task do
87
+ include Configurable
88
+
89
+ def logger= value
90
+ @config[:logger] = value
93
91
  end
94
92
 
95
- subject.let(:logger) { :logger }
93
+ dependency :logger
94
+ public :logger
96
95
 
97
- subject.let(:server) { server_class.new }
96
+ def log_level= value
97
+ @config[:log_level] = value
98
+ end
98
99
 
99
- expect(subject.server.log).to be :logger
100
+ dependency :log_level
101
+ public :log_level
100
102
  end
101
103
 
102
- it 'assigns dependencies with lambdas' do
103
- server_class = Class.new do
104
- include LittleBoxes::Dependant
105
-
106
- dependency :logger
107
- end
104
+ define_class :HttpClient do
105
+ include Configurable
108
106
 
109
- subject.let(:server) { server_class.new }
107
+ class_dependency :logger
108
+ public_class_method :logger
109
+ end
110
110
 
111
- subject.let(:logger) { :old }
112
- expect(subject.server.logger).to be :old
111
+ define_class :RestClient do
112
+ include Configurable
113
113
 
114
- subject.let(:logger) { :new }
115
- expect(subject.server.logger).to be :new
114
+ class_dependency :logger
115
+ public_class_method :logger
116
116
  end
117
117
 
118
- it 'supports overriding specific attributes' do
119
- server_class = Class.new do
120
- include LittleBoxes::Dependant
121
- dependency :logger
118
+ define_class :ApiClient do
119
+ class << self
120
+ attr_accessor :logger
122
121
  end
122
+ end
123
123
 
124
- subject.let(:logger) { :logger }
125
- subject.let(:new_logger) { :new_logger }
126
-
127
- subject.let(:server) do
128
- build { server_class.new }
129
- let(:logger) { new_logger }
130
- end
124
+ define_class :SomeClient do
125
+ include Configurable
131
126
 
132
- expect(subject.server.logger).to be :new_logger
127
+ class_dependency :idontexist
128
+ public_class_method :idontexist
133
129
  end
134
- end
135
130
 
136
- describe '#obtain' do
137
- it 'does not memoize the result' do
138
- n = 0
139
- subject.obtain(:loglevel) { n = n + 1 }
131
+ define_class :ClientWithDefaults do
132
+ include Configurable
140
133
 
141
- expect(subject.loglevel).to eq 1
142
- expect(subject.loglevel).to eq 2
134
+ dependency(:dep_with_default) { |b| b.object_id }
135
+ class_dependency(:class_dep_with_default) { |b| b.object_id }
136
+ class_dependency(:another_class_dep_with_default) { |b| b.object_id }
143
137
  end
144
138
 
145
- it 'doesn not inject anything' do
146
- server_class = Class.new do
147
- include LittleBoxes::Dependant
148
- dependency :logger
149
- end
139
+ define_class :ClientWithClassDefaults do
140
+ include Configurable
150
141
 
151
- subject.let(:logger) { double('logger') }
152
- subject.obtain(:server) { server_class.new.tap { |s| s.logger = :old_logger } }
153
- expect(subject.server.logger).to be :old_logger
142
+ class_dependency(:another_class_dep_with_default) { |b| b.object_id }
154
143
  end
155
144
  end
156
145
 
157
- describe '#define' do
158
- it 'does not memoize the result' do
159
- n = 0
160
- subject.define(:loglevel) { n = n + 1 }
161
-
162
- expect(subject.loglevel).to eq 1
163
- expect(subject.loglevel).to eq 2
146
+ let(:box) { MainBox.new }
147
+ define_method(:logger) { box.logger }
148
+ define_method(:server) { box.server }
149
+ define_method(:log_level) { box.log_level }
150
+ define_method(:users_collection) { box.users_collection }
151
+ define_method(:users_api) { box.users_api }
152
+ define_method(:task) { box.task }
153
+ define_method(:http_client) { HttpClient }
154
+ define_method(:rest_client) { RestClient }
155
+ define_method(:api_client) { ApiClient }
156
+ define_method(:some_client) { box.some_client }
157
+
158
+ describe 'box' do
159
+ it 'memoizes' do
160
+ expect(logger).to be logger
164
161
  end
165
162
 
166
- it 'has instances that have dependencies' do
167
- server_class = Class.new do
168
- include LittleBoxes::Dependant
169
- dependency :logger
170
- end
171
-
172
- subject.let(:logger) { double('logger') }
173
- subject.define(:server) { server_class.new }
174
- expect(subject.server.logger).to be subject.logger
163
+ it 'doesn\'t share between instances' do
164
+ expect(logger).not_to be MainBox.new.logger
175
165
  end
166
+ end
176
167
 
177
- it 'unknown dependencies raise exception' do
178
- server_class = Class.new do
179
- include LittleBoxes::Dependant
180
- dependency :unknown_dep
181
- end
182
-
183
- subject.define(:server) { server_class.new }
184
- expect{ subject.server.unknown_dep }.to raise_error(LittleBoxes::MissingDependency)
168
+ describe 'logger' do
169
+ it 'is a new instance every time (get)' do
170
+ expect(log_level).to eq 'INFO'
171
+ expect(log_level).not_to be log_level
185
172
  end
186
173
 
187
- it 'has classes that have class dependencies' do
188
- server_class = Class.new do
189
- include LittleBoxes::Dependant
190
- class_dependency :host
191
- end
174
+ it 'has the log_level (relying on other deps)' do
175
+ expect(logger.level).to eq log_level
176
+ end
192
177
 
193
- subject.let(:host) { 'localhost' }
194
- subject.define(:server_class) { server_class }
195
- expect(subject.server_class.host).to eq 'localhost'
178
+ it 'has memoized log_level (let memoizes)' do
179
+ expect(logger.level).to be logger.level
196
180
  end
181
+ end
197
182
 
198
- it 'supports defaults' do
199
- server_class = Class.new do
200
- include LittleBoxes::Dependant
201
- dependency :log, default: ->(d){ d.logger }
202
- end
183
+ describe 'server (letc)' do
184
+ it 'is configured' do
185
+ expect(server.logger).to be logger
186
+ end
203
187
 
204
- subject.let(:logger) { :logger }
188
+ it 'is memoized' do
189
+ expect(server).to be server
190
+ end
205
191
 
206
- subject.define(:server) { server_class.new }
192
+ it 'respects previously configured dependencies' do
193
+ expect(task.logger).to be :specific_logger
194
+ end
207
195
 
208
- expect(subject.server.log).to be :logger
196
+ it 'has access to the box' do
197
+ expect(task.log_level).to eq box.log_level
209
198
  end
210
199
 
211
- it 'assigns dependencies with lambdas' do
212
- server_class = Class.new do
213
- include LittleBoxes::Dependant
200
+ pending 'test for get, getc, let'
201
+ end
214
202
 
215
- dependency :logger
216
- end
203
+ describe 'users_collection (getc)' do
204
+ it 'doesn\'t memoize' do
205
+ expect(users_collection).not_to be users_collection
206
+ end
217
207
 
218
- subject.define(:server) { server_class.new }
208
+ it 'has a logger' do
209
+ expect(users_collection.logger).to be_a Logger
210
+ end
219
211
 
220
- subject.let(:logger) { :old }
221
- expect(subject.server.logger).to be :old
212
+ it 'is main box\'s logger' do
213
+ expect(users_collection.logger).to be logger
214
+ end
222
215
 
223
- subject.let(:logger) { :new }
224
- expect(subject.server.logger).to be :new
216
+ it 'memoizes the logger' do
217
+ expect(users_collection.logger).to be users_collection.logger
225
218
  end
226
219
  end
227
220
 
228
- describe '#box' do
229
- it 'supports boxes' do
230
- subject.box :loggers do
231
- let(:null) { :null_logger }
232
- let(:file) { :file_logger }
233
- end
234
-
235
- expect(subject.loggers.null).to be :null_logger
221
+ describe 'users_api (class configurable)' do
222
+ it 'has a logger' do
223
+ expect(users_api.logger).to be_a Logger
236
224
  end
237
225
 
238
- it 'supports finding dependencies upwards in the ancestry' do
239
- server_class = Class.new do
240
- include LittleBoxes::Dependant
241
- dependency :logger
242
- end
243
-
244
- subject.let(:logger) { :logger }
245
-
246
- subject.box :servers do
247
- let(:one) { server_class.new }
248
- end
249
-
250
- expect(subject.servers.one.logger).to be :logger
226
+ it 'is main box\'s logger' do
227
+ expect(users_api.logger).to be logger
251
228
  end
229
+ end
252
230
 
253
- it 'defaults within boxes' do
254
- server_class = Class.new do
255
- include LittleBoxes::Dependant
256
- dependency :log, default: ->(a){ a.logger }
257
- end
258
-
259
- subject.let(:logger) { :logger }
260
-
261
- subject.box :servers do
262
- let(:apache) { server_class.new }
263
- end
231
+ describe 'http_client (configured eager loading)' do
232
+ it 'loads on box initialization' do
233
+ box
234
+ expect(http_client.logger).to be_a Logger
235
+ end
264
236
 
265
- expect(subject.servers.apache.log).to be :logger
237
+ it 'doesn\'t eager load the dependencies' do
238
+ expect(box).not_to receive(:logger)
239
+ box
266
240
  end
267
241
  end
268
242
 
269
- describe '#inspect' do
270
- it 'has nice inspect' do
271
- subject.let(:loglevel) { 0 }
272
- subject.let(:logger) { 0 }
273
- expect(subject.inspect).to eq "<LittleBoxes::Box box: loglevel logger>"
243
+ describe 'api_client (configured eager loading)' do
244
+ it 'loads on box initialization' do
245
+ box
246
+ expect(api_client.logger).to be_a Logger
274
247
  end
275
- end
276
-
277
- describe '#path' do
278
- it 'has a path' do
279
- subject.box :second do
280
- box :third do
281
- end
282
- end
283
248
 
284
- expect(subject.second.third.path).to eq [subject, subject.second, subject.second.third]
249
+ it 'doesn\'t eager load the dependencies' do
250
+ expect(box).not_to receive(:logger)
251
+ box
285
252
  end
286
253
  end
287
254
 
288
- describe '#name' do
289
- it 'names boxes' do
290
- subject.name = :first
291
-
292
- subject.box :second do
293
- box :third do
294
- end
295
- end
296
-
297
- expect(subject.second.third.path.map(&:name)).to eq [:first, :second, :third]
255
+ describe 'error handling (unmet dependency)' do
256
+ it 'sends a pretty error' do
257
+ expect { some_client.idontexist }.to raise_error(
258
+ LittleBoxes::DependencyNotFound,
259
+ 'Dependency idontexist not found'
260
+ )
298
261
  end
299
262
  end
300
263
 
301
- describe '#reset' do
302
- it 'has a reset' do
303
- first = subject
264
+ describe 'nested boxes' do
265
+ describe 'given a box' do
266
+ it 'initializes second level box on first level box initialization' do
267
+ expect(FoldersBox).to receive(:new)
268
+ box
269
+ end
304
270
 
305
- first.box :second do
306
- let(:logger) { Object.new }
271
+ it 'allows to navigate to element of second level box' do
272
+ expect(box.folders.collection).to be_a FoldersCollection
307
273
  end
308
274
 
309
- old_logger = first.second.logger
275
+ it 'configures looking up the tree' do
276
+ expect(box.folders.collection.logger).to be(logger)
277
+ end
310
278
 
311
- first.reset
312
- expect(first.second.logger).not_to be old_logger
279
+ it 'has access to parent' do
280
+ expect(box.folders.parent).to be(box)
281
+ end
282
+ end
283
+
284
+ describe 'inline box' do
285
+ it 'eager loads eager loadable stuff on the second level' do
286
+ box
287
+ expect(rest_client.logger).to be logger
288
+ end
313
289
  end
314
290
  end
315
291
 
316
- describe '#root' do
317
- it 'has a root' do
318
- first = subject
292
+ describe 'imported boxes' do
293
+ describe 'given a box' do
294
+ it 'imports entries' do
295
+ expect(box.collection).to be_a(FoldersCollection)
296
+ end
297
+
298
+ it 'keeps memoization options' do
299
+ expect(box.collection).to be(box.collection)
300
+ end
319
301
 
320
- first.box :second do
321
- box(:third) { }
302
+ it 'keeps configuration options' do
303
+ expect(box.collection.logger).to be_a Logger
322
304
  end
323
305
 
324
- expect(first.second.third.root).to eq first
306
+ it 'does not run entries procs' do
307
+ expect(SomeOtherLogger).not_to receive(:new)
308
+ box
309
+ end
325
310
  end
326
311
  end
327
312
 
328
- describe '.new' do
329
- it 'executes block on initialize' do
330
- subject = described_class.new do
331
- let(:loglevel) { 0 }
332
- end
313
+ describe 'defaults' do
314
+ it 'is supported at instance level' do
315
+ client = box.client_with_defaults
316
+ expect(client.dep_with_default).to eq box.object_id
317
+ end
333
318
 
334
- expect(subject.loglevel).to eq 0
319
+ it 'is supported at class level' do
320
+ client_class = box.client_with_defaults_class
321
+ expect(client_class.class_dep_with_default).to eq box.object_id
335
322
  end
336
323
 
337
- it 'can build instances' do
338
- expect(subject).to be_a(described_class)
324
+ it 'is supported at class level and accessible at instance level' do
325
+ client = box.client_with_class_defaults
326
+ expect(client.another_class_dep_with_default).to eq box.object_id
339
327
  end
340
328
  end
341
329
  end