micon 0.1.16 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
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