micon 0.1.16 → 0.1.17

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.
data/readme.md CHANGED
@@ -1,95 +1,188 @@
1
- # Assembles and manages pieces of Your Application
1
+ # Micon IoC assembles and manages Your Application
2
2
 
3
- Micon is infrastructural component, invisible to user and it's main goal is to simplify development. It reduces complex monolithic application to set of simple low coupled components.
3
+ Micon is infrastructural component, invisible to user and it's main goal is to simplify development. It reduces complex monolithic application to set of simple low coupled components.
4
4
 
5
5
  Concentrate on business logic and interfaces and Micon will provide automatic configuration, life cycle management and dependency resolving.
6
6
 
7
7
  Technically it's [IoC][ioc] like framework with components, callbacks, scopes and bijections, inspired by Spring and JBoss Seam.
8
8
 
9
- Is it usefull, is there any real-life application? - I'm using it as a heart of my [web framework][rad_core], this site http://ruby-lang.info for example powered with it.
9
+ Is it usefull, is there any real-life application? - I'm using it as a heart of my [web framework][rad_core], this sites http://robotigra.ru, http://ruby-lang.info for example powered with it.
10
10
 
11
11
  ## Usage
12
-
12
+
13
13
  Let's suppose you are building the Ruby on Rails clone, there are lots of modules let's try to deal with them
14
14
 
15
- require 'micon'
16
- def app; Micon end
17
-
18
- # static (singleton) components
19
- class Environment
20
- register_as :environment
21
- end
22
-
23
- class Logger
24
- register_as :logger
25
-
26
- def info msg; end
27
- end
28
-
29
- class Router
30
- register_as :router
31
-
32
- def parse rote_filename
33
- # do something
34
- end
35
- end
36
-
37
- # callbacks, we need to parse routes right after environment is initialized
38
- app.after :environment do
39
- app[:router].parse '/config/routes.rb'
40
- end
41
-
42
- # dynamic components, will be created and destroyed for every request
43
- class Request
44
- register_as :request, scope: :request
45
- end
46
-
47
- class Application
48
- # injecting components into attributes
49
- inject request: :request, logger: :logger
50
-
51
- def do_business
52
- # now we can use injected component
53
- do_something_with request
54
- logger.info 'done'
55
- end
56
-
57
- def do_something_with request; end
58
- end
59
-
60
- # Web Server / Rack Adapter
61
- class RackAdapter
62
- def call env
63
- # activating new request scope, the session component will be created and destroyed automatically
64
- app.activate :request, {} do
65
- Application.new.do_business
66
- end
67
- end
68
- end
69
-
70
- RackAdapter.new.call({})
71
-
72
- For actual code go to spec/overview_spec.rb
15
+ ``` ruby
16
+ require 'micon'
17
+
18
+ # Here's our Web Framework, let's call it Rad
19
+
20
+ # Let's define shortcut to access the IoC API (optional
21
+ # but handy step). I don't know how You would like to call it,
22
+ # so I leave this step to You.
23
+ class ::Object
24
+ def rad; MICON end
25
+ end
26
+
27
+ # let's define some components
28
+ # the :logger is one per application, it's a static component (like singleton)
29
+ class Logger
30
+ register_as :logger
31
+ attr_accessor :log_file_path
32
+ def info msg
33
+ puts "#{msg} (writen to #{log_file_path})" unless defined?(RSpec)
34
+ end
35
+ end
36
+
37
+ # To demostrate basics of working with compnents let's configure our :logger
38
+ # explicitly (in the next example, it will be configured automatically).
39
+ rad.logger.log_file_path = '/tmp/rad.log'
40
+
41
+ # The :router requires complex initialization, so we use
42
+ # another form of component registration.
43
+ class Router
44
+ def initialize routes; @routes = routes end
45
+ def decode request;
46
+ class_name, method = @routes[request.url]
47
+ return eval(class_name), method # returning actual class
48
+ end
49
+ end
50
+ rad.register :router do
51
+ Router.new '/index' => ['PagesController', :index]
52
+ end
53
+
54
+ # The :controller component should be created and destroyed dynamically,
55
+ # for each request, we specifying that component is dynamic
56
+ # by declaring it's :scope.
57
+ # And, we don't know beforehead what it actully will be, for different
58
+ # request there can be different controllers,
59
+ # so, here we just declaring it without any initialization block, it
60
+ # will be created at runtime later.
61
+ rad.register :controller, scope: :request
62
+
63
+ # Let's define some of our controllers, the PagesController, note - we
64
+ # don't register it as component.
65
+ class PagesController
66
+ # We need access to :logger and :request, let's inject them
67
+ inject logger: :logger, request: :request
68
+
69
+ def index
70
+ # Here we can use injected component
71
+ logger.info "Application: processing #{request}"
72
+ end
73
+ end
74
+
75
+ # Request is also dynamic, and it also can't be created beforehead.
76
+ # We also registering it without initialization, it will be
77
+ # created at runtime later.
78
+ class Request
79
+ attr_reader :url
80
+ def initialize url; @url = url end
81
+ def to_s; @url end
82
+ end
83
+ # Registering without initialization block.
84
+ rad.register :request, scope: :request
85
+
86
+ # We need to integrate our application with web server, for example with the Rack.
87
+ # When the server receive web request, it calls the :call method of our RackAdapter
88
+ class RackAdapter
89
+ # Injecting components
90
+ inject request: :request, controller: :controller
91
+
92
+ def call env
93
+ # We need to tell Micon that the :request scope is started, so it will know
94
+ # that some dynamic components should be created during this scope and
95
+ # destroyed at the end of it.
96
+ rad.activate :request, {} do
97
+ # Here we manually creating the Request component
98
+ self.request = Request.new '/index'
99
+
100
+ # The :router also can be injected via :inject,
101
+ # but we can also use another way to access components,
102
+ # every component also availiable as rad.<component_name>
103
+ controller_class, method = rad.router.decode request
104
+
105
+ # Let's create and call our controller
106
+ self.controller = controller_class.new
107
+ controller.send method
108
+ end
109
+ end
110
+ end
111
+
112
+ # Let's pretend that there's a Web Server and run our application,
113
+ # You should see something like this in the console:
114
+ # Application: processing /index
115
+ RackAdapter.new.call({})
116
+ ```
117
+
118
+ The example above is a good way to demonstrate how the IoC works in general, but it will not show two **extremelly important** aspects of IoC: **auto-discovery** and **auto-configuration**.
119
+ In real-life scenario You probably will use it in a little different way, as will be shown below, and utilize these important features (there's a short article about why these features are important [You underestimate the power of IoC][article]).
120
+
121
+ I would like to repeat it one more time - **auto-discovery and auto-configuration is extremelly important features** of the IoC, don't ignore them.
122
+
123
+ Below are the same example but done with utilizing these features, this is how the Micon IoC is supposed be used in the real-life scenario. As You can see it's almost empty, because all the components are auto-discovered, auto-loaded and auto-configured. Components are located in the [spec/example_spec/lib](https://github.com/alexeypetrushin/micon/blob/master/spec/example_spec/lib) folder.
124
+
125
+ Please note that this time logger convigured automatically, with logger.yml configuration file.
126
+
127
+ ``` ruby
128
+ require 'micon'
129
+ require 'class_loader'
130
+
131
+ # Here's our Web Framework, let's call it Rad
132
+
133
+ # Let's define shortcut to access the IoC API (optional
134
+ # but handy step). I don't know how You would like to call it,
135
+ # so I leave this step to You.
136
+ class ::Object
137
+ def rad; MICON end
138
+ end
139
+
140
+ # Auto-discovering:
141
+ #
142
+ # All components (:logger, :router, :request, :controller) are
143
+ # defined in spec/example_spec/lib/components folder.
144
+ # All classes (PagesController, RackAdapter) are
145
+ # located in spec/example_spec/lib folder.
146
+ #
147
+ # Note that there's no any "require 'xxx'" clause, all components and
148
+ # classes are loaded and dependecies are resolved automatically.
149
+
150
+ # Auto-configuring
151
+ #
152
+ # Remember our manual configuration of "logger.log_file_path" from
153
+ # the previous example?
154
+ # This time it will be configured automatically, take a look at
155
+ # the spec/example_spec/lib/components/logger.yml file.
156
+ #
157
+ # Note, that there are also logger.production.yml, configs are smart
158
+ # and are merged in the following order:
159
+ # logger.yml <- logger.<env>.yml <- <runtime_path>/config/logger.yml
160
+ # (If you define :environment and :runtime_path variables).
161
+
162
+ # Let's pretend that there's a Web Server and run our application,
163
+ # You should see something like this in the console:
164
+ # Application: processing /index
165
+ RackAdapter.new.call({})
166
+ ```
167
+
168
+ For the actual code please look at [spec/example_spec.rb](https://github.com/alexeypetrushin/micon/blob/master/spec/example_spec.rb)
73
169
 
74
170
  ## Note
75
171
 
76
172
  Current wersion isn't thread-safe, instead it supported evented IO (EventMachine).
77
- Actually I implemented first wersion as thread-safe, but because there's no actual multithreading in
78
- Ruby, the only thing it does - adds complexity and performance losses, so I removed it.
79
- But if you need it it can be done very easy.
80
-
173
+ Actually I implemented first wersion as thread-safe, but because there's no actual multithreading in Ruby, the only thing it does - adds complexity and performance losses, so I removed it.
174
+ But if you need it it can be easily done.
175
+
81
176
  ## Installation
82
177
 
83
- $ sudo gem install micon
84
-
85
- ## TODO
86
-
87
- - remove threads and synchronization support, probably it will be never needed in any real situation, because
88
- there's no multithreading in ruby.
89
- - refactor specs, they are messy a little.
90
- - maybe it makes sense to add ability to add dependencies for components after component registration?
178
+ ``` bash
179
+ gem install micon
180
+ ```
181
+
182
+ ## License
91
183
 
92
184
  Copyright (c) Alexey Petrushin http://petrush.in, released under the MIT license.
93
185
 
94
186
  [ioc]: http://en.wikipedia.org/wiki/Inversion_of_control
95
- [rad_core]: https://github.com/alexeypetrushin/rad_core
187
+ [rad_core]: https://github.com/alexeypetrushin/rad_core
188
+ [article]: http://ruby-lang.info/blog/you-underestimate-the-power-of-ioc-3fh
@@ -8,17 +8,17 @@ describe "Callbacks" do
8
8
  describe "components callbacs" do
9
9
  it "basic" do
10
10
  micon.register(:the_object){"The Object"}
11
-
11
+
12
12
  check = mock
13
13
  check.should_receive(:before)
14
14
  micon.before :the_object do
15
15
  check.before
16
16
  end
17
-
17
+
18
18
  check.should_receive(:after1).ordered
19
19
  check.should_receive(:after2).ordered
20
20
  obj = nil
21
- micon.after :the_object do |o|
21
+ micon.after :the_object do |o|
22
22
  check.after1
23
23
  obj = o
24
24
  end
@@ -26,7 +26,7 @@ describe "Callbacks" do
26
26
  check.after2
27
27
  obj.object_id.should == o.object_id
28
28
  end
29
-
29
+
30
30
  micon[:the_object].should == obj
31
31
  end
32
32
 
@@ -54,8 +54,8 @@ describe "Callbacks" do
54
54
  micon.after_scope(:custom){|container| check.after2 container}
55
55
 
56
56
  micon.activate(:custom, {}){check.run}
57
- end
58
- end
57
+ end
58
+ end
59
59
 
60
60
  describe "miscellaneous" do
61
61
  it "should fire callbacks after assigning component" do
@@ -67,32 +67,32 @@ describe "Callbacks" do
67
67
  end
68
68
  micon.the_object = 'the_object'
69
69
  end
70
-
70
+
71
71
  it "should raise error if callback defined after component already created" do
72
72
  micon.register(:the_object){"the_object"}
73
73
  micon[:the_object]
74
-
74
+
75
75
  -> {micon.before(:the_object){}}.should raise_error(/already created/)
76
76
  -> {micon.after(:the_object){}}.should raise_error(/already created/)
77
77
  end
78
-
78
+
79
79
  it "should raise error if callback defined after scope already started" do
80
80
  micon.activate :custom, {} do
81
81
  -> {micon.before_scope(:custom){}}.should raise_error(/already started/)
82
82
  -> {micon.after_scope(:custom){}}.should raise_error(/already started/)
83
83
  end
84
84
  end
85
-
85
+
86
86
  it ":after with bang: false should execute callback if component already started and also register it as :after callback" do
87
87
  micon.register(:the_object){"the_object"}
88
88
  micon[:the_object]
89
-
89
+
90
90
  check = mock
91
- check.should_receive(:first).twice
91
+ check.should_receive(:first).twice
92
92
  micon.after(:the_object, bang: false){check.first}
93
-
93
+
94
94
  micon.delete :the_object
95
95
  micon[:the_object]
96
96
  end
97
- end
97
+ end
98
98
  end
data/spec/config_spec.rb CHANGED
@@ -1,18 +1,18 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Configuration" do
3
+ describe "Configuration" do
4
4
  before{self.micon = Micon::Core.new}
5
5
 
6
6
  it "should configure component if config provided" do
7
- micon.register(:logger){::OpenStruct.new}
8
- with_load_path "#{spec_dir}/basic/lib" do
7
+ micon.register(:logger){::OpenStruct.new}
8
+ with_load_path "#{spec_dir}/basic/lib" do
9
9
  micon[:logger].level.should == :info
10
10
  end
11
11
  end
12
12
 
13
13
  it "should merge in order: conf <- conf.mode <- runtime <- runtime.mode" do
14
14
  micon.register(:object){::OpenStruct.new}
15
- with_load_path "#{spec_dir}/order/lib" do
15
+ with_load_path "#{spec_dir}/order/lib" do
16
16
  micon.runtime_path = "#{spec_dir}/order/app"
17
17
  micon.mode = :production
18
18
  micon[:object].tap do |o|
@@ -1,70 +1,70 @@
1
1
  # require 'spec_helper'
2
- #
2
+ #
3
3
  # describe "Autoloading" do
4
4
  # with_load_path "#{spec_dir}/get_constant_component/lib"
5
- #
5
+ #
6
6
  # before do
7
7
  # self.micon = Micon::Core.new
8
8
  # end
9
- #
9
+ #
10
10
  # after do
11
11
  # remove_constants :TheRouter, :TheRad, :TheController, :SomeModule
12
12
  # end
13
- #
13
+ #
14
14
  # describe "get_constant_component" do
15
15
  # it "should autoload components" do
16
16
  # micon.get_constant_component(:TheController).should == "TheController"
17
17
  # end
18
- #
18
+ #
19
19
  # it "should not raise error if component not defined" do
20
20
  # micon.get_constant_component(:TheValue).should == nil
21
21
  # micon.register(:TheValue, constant: true){"TheValue"}
22
22
  # micon.get_constant_component(:TheValue).should == "TheValue"
23
23
  # end
24
- #
24
+ #
25
25
  # it "should get constants only" do
26
26
  # micon.register(:TheController){"TheController"}
27
27
  # micon.get_constant_component(:TheController).should == nil
28
- #
28
+ #
29
29
  # micon.register(:TheController, constant: true){"TheController"}
30
30
  # micon.get_constant_component(:TheController).should == "TheController"
31
31
  # end
32
32
  # end
33
- #
33
+ #
34
34
  # it 'validation' do
35
35
  # -> {micon.register 'TheController', constant: true}.should raise_error(/symbol/)
36
36
  # -> {micon.register 'TheController'.to_sym, constant: true, scope: :custom}.should raise_error(/scope/)
37
37
  # end
38
- #
38
+ #
39
39
  # it "should use constants as components" do
40
40
  # -> {::TheRouter}.should raise_error(/uninitialized constant/)
41
- #
41
+ #
42
42
  # micon.register :TheRouter, constant: true do
43
43
  # "TheRouter"
44
- # end
45
- #
44
+ # end
45
+ #
46
46
  # module ::TheRad
47
47
  # TheRouter.should == 'TheRouter'
48
48
  # end
49
49
  # ::TheRouter.should == 'TheRouter'
50
50
  # micon[:TheRouter].should == 'TheRouter'
51
51
  # end
52
- #
52
+ #
53
53
  # it "should support nested constants" do
54
- # module ::TheRad; end
54
+ # module ::TheRad; end
55
55
  # -> {::TheRad::TheView}.should raise_error(/uninitialized constant/)
56
- #
56
+ #
57
57
  # micon.register 'TheRad::TheView'.to_sym, constant: true do
58
58
  # 'TheRad::TheView'
59
- # end
60
- #
59
+ # end
60
+ #
61
61
  # module ::SomeModule
62
62
  # ::TheRad::TheView.should == 'TheRad::TheView'
63
63
  # end
64
64
  # ::TheRad::TheView.should == 'TheRad::TheView'
65
65
  # micon['TheRad::TheView'.to_sym].should == 'TheRad::TheView'
66
66
  # end
67
- #
67
+ #
68
68
  # it "should check if constant is already defined" do
69
69
  # micon.register :TheRouter, constant: true do
70
70
  # class ::TheRouter; end
@@ -6,7 +6,7 @@ describe "Custom Scope" do
6
6
  end
7
7
 
8
8
  it "activate" do
9
- container = {}
9
+ container = {}
10
10
  micon.should_not be_active(:custom)
11
11
  micon.activate :custom, container
12
12
  micon.should be_active(:custom)
@@ -15,7 +15,7 @@ describe "Custom Scope" do
15
15
 
16
16
  micon.deactivate :custom
17
17
  -> {micon.deactivate :custom}.should raise_error(/not active/)
18
-
18
+
19
19
  micon.should_not be_active(:custom)
20
20
  micon.activate :custom, container do
21
21
  micon.should be_active(:custom)
@@ -31,20 +31,20 @@ describe "Custom Scope" do
31
31
  it "get" do
32
32
  micon.register(:value, scope: :custom){"The Object"}
33
33
  container, the_object = {}, nil
34
-
34
+
35
35
  micon.activate :custom, container do
36
36
  micon[:value].should == "The Object"
37
37
  the_object = micon[:value]
38
38
  end
39
-
39
+
40
40
  micon.activate :custom, {} do
41
41
  micon[:value].object_id.should_not == the_object.object_id
42
42
  end
43
-
43
+
44
44
  micon.activate :custom, container do
45
45
  micon[:value].object_id.should == the_object.object_id
46
46
  end
47
-
47
+
48
48
  container.size.should == 1
49
49
  container[:value].should == the_object
50
50
  end
@@ -52,17 +52,17 @@ describe "Custom Scope" do
52
52
  it "set" do
53
53
  micon.register(:value, scope: :custom){"The Object"}
54
54
  container = {}
55
-
55
+
56
56
  micon.activate :custom, container do
57
57
  micon[:value].should == "The Object"
58
58
  micon[:value] = "Another Object"
59
59
  the_object = micon[:value]
60
60
  end
61
-
61
+
62
62
  micon.activate :custom, {} do
63
63
  micon[:value].should == "The Object"
64
64
  end
65
-
65
+
66
66
  micon.activate :custom, container do
67
67
  micon[:value].should == "Another Object"
68
68
  end
@@ -0,0 +1,8 @@
1
+ # The :controller component should be created and destroyed dynamically,
2
+ # for each request, we specifying that component is dynamic
3
+ # by declaring it's :scope.
4
+ # And, we don't know beforehead what it actully will be, for different
5
+ # request there can be different controllers,
6
+ # so, here we just declaring it without any initialization block, it
7
+ # will be created at runtime later.
8
+ rad.register :controller, scope: :request
@@ -0,0 +1 @@
1
+ log_file_path: /tmp/rad.production.log
@@ -0,0 +1,8 @@
1
+ # the :logger is one per application, it's a static component (like singleton)
2
+ class Logger
3
+ register_as :logger
4
+ attr_accessor :log_file_path
5
+ def info msg
6
+ puts "#{msg} (writen to #{log_file_path})" unless defined?(RSpec)
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ log_file_path: /tmp/rad.log
@@ -0,0 +1,2 @@
1
+ # Registering without initialization block.
2
+ rad.register :request, scope: :request
@@ -0,0 +1,12 @@
1
+ # The :router requires complex initialization, so we use
2
+ # another form of component registration.
3
+ class Router
4
+ def initialize routes; @routes = routes end
5
+ def decode request;
6
+ class_name, method = @routes[request.url]
7
+ return eval(class_name), method # returning actual class
8
+ end
9
+ end
10
+ rad.register :router do
11
+ Router.new '/index' => ['PagesController', :index]
12
+ end
@@ -0,0 +1,11 @@
1
+ # Let's define some of our controllers, the PagesController, note - we
2
+ # don't register it as component.
3
+ class PagesController
4
+ # We need access to :logger and :request, let's inject them
5
+ inject logger: :logger, request: :request
6
+
7
+ def index
8
+ # Here we can use injected component
9
+ logger.info "Application: processing #{request}"
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ # We need to integrate our application with web server, for example with the Rack.
2
+ # When the server receive web request, it calls the :call method of our RackAdapter
3
+ class RackAdapter
4
+ # Injecting components
5
+ inject request: :request, controller: :controller
6
+
7
+ def call env
8
+ # We need to tell Micon that the :request scope is started, so it will know
9
+ # that some dynamic components should be created during this scope and
10
+ # destroyed at the end of it.
11
+ rad.activate :request, {} do
12
+ # Here we manually creating the Request component
13
+ self.request = Request.new '/index'
14
+
15
+ # The :router also can be injected via :inject,
16
+ # but we can also use another way to access components,
17
+ # every component also availiable as rad.<component_name>
18
+ controller_class, method = rad.router.decode request
19
+
20
+ # Let's create and call our controller
21
+ self.controller = controller_class.new
22
+ controller.send method
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ # Request is also dynamic, and it also can't be created beforehead.
2
+ # We also registering it without initialization, it will be
3
+ # created at runtime later.
4
+ class Request
5
+ attr_reader :url
6
+ def initialize url; @url = url end
7
+ def to_s; @url end
8
+ end