micon 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rake_ext'
2
2
 
3
3
  project(
4
4
  name: "micon",
5
- version: "0.1.7",
5
+ gem: true,
6
6
  summary: "Assembles and Manages Components of Your Application",
7
7
 
8
8
  author: "Alexey Petrushin",
@@ -1,3 +1,5 @@
1
+ require 'yaml'
2
+
1
3
  module Micon
2
4
  end
3
5
 
@@ -5,6 +7,7 @@ end
5
7
  support
6
8
 
7
9
  metadata
10
+ config
8
11
  core
9
12
  helper
10
13
 
@@ -0,0 +1,46 @@
1
+ class Micon::Config
2
+ attr_reader :micon, :name
3
+ def initialize micon, name
4
+ @micon, @name = micon, name
5
+ end
6
+
7
+ def load
8
+ files = []
9
+ files.push *config_paths.collect{|path| find_file(path, $LOAD_PATH)}
10
+ files.push *runtime_config_paths.collect{|path| find_file(path, [micon.runtime_path])} if micon.runtime_path?
11
+
12
+ config = {}
13
+ files.compact.each do |f|
14
+ c = YAML.load_file(f)
15
+ next unless c
16
+ raise "component config must be a Hash (#{f})!" unless c.is_a? Hash
17
+ c.each{|k, v| config[:"#{k}="] = v}
18
+ end
19
+
20
+ config.empty? ? nil : config
21
+ end
22
+
23
+ protected
24
+ def config_paths
25
+ fs_name = name.to_s.gsub(/::/, '/')
26
+ paths = ["/components/#{fs_name}.yml"]
27
+ paths << "/components/#{fs_name}.#{micon.mode}.yml" if micon.mode?
28
+ paths
29
+ end
30
+
31
+ def runtime_config_paths
32
+ fs_name = name.to_s.gsub(/::/, '/')
33
+ paths = ["/config/#{fs_name}.yml"]
34
+ if micon.mode?
35
+ paths << "/config/#{fs_name}.#{micon.mode}.yml"
36
+ paths << "/config/#{micon.mode}/#{fs_name}.yml"
37
+ end
38
+ paths
39
+ end
40
+
41
+ def find_file path, directories
42
+ files = directories.collect{|dir| "#{dir}#{path}"}.select{|f| File.exist? f}
43
+ raise "multiple configs for :#{name} component" if files.size > 1
44
+ files.first
45
+ end
46
+ end
@@ -107,66 +107,6 @@ class Micon::Core
107
107
  end
108
108
  end
109
109
 
110
- # def get_constant_component key
111
- # sname = @registry[key] || autoload_component_definition(key, false)
112
- #
113
- # case sname
114
- # when nil
115
- # nil
116
- # when :instance
117
- # must_be.never_called
118
- # when :application
119
- # return nil unless @constants.include? key
120
- #
121
- # o = @application[key]
122
- # unless o
123
- # return create_object(key, @application)
124
- # else
125
- # return o
126
- # end
127
- # else # custom
128
- # must_be.never_called
129
- # end
130
- # end
131
- #
132
- # def get_constant namespace, const
133
- # original_namespace = namespace
134
- # namespace = nil if namespace == Object or namespace == Module
135
- # target_namespace = namespace
136
- #
137
- # # Name hack (for anonymous classes)
138
- # namespace = eval "#{name_hack(namespace)}" if namespace
139
- #
140
- # class_name = namespace ? "#{namespace.name}::#{const}" : const
141
- #
142
- # simple_also_tried = false
143
- # begin
144
- # simple_also_tried = (namespace == nil)
145
- #
146
- # if result = get_constant_component(class_name.to_sym)
147
- # if @loaded_classes.include?(class_name)
148
- # raise_without_self "something wrong is goin on, constant '#{const}' in '#{original_namespace}' namespace already has been defined!"
149
- # end
150
- #
151
- # real_namespace = namespace ? namespace : Object
152
- # if real_namespace.const_defined?(const)
153
- # raise_without_self "component trying to redefine constant '#{const}' that already defined in '#{real_namespace}'!"
154
- # end
155
- #
156
- # real_namespace.const_set const, result
157
- #
158
- # @loaded_classes[class_name] = [real_namespace, const]
159
- #
160
- # return result
161
- # elsif namespace
162
- # namespace = Module.namespace_for(namespace.name)
163
- # class_name = namespace ? "#{namespace.name}::#{const}" : const
164
- # end
165
- # end until simple_also_tried
166
- #
167
- # return nil
168
- # end
169
-
170
110
  def []= key, value
171
111
  raise "can't assign nill as :#{key} component!" unless value
172
112
 
@@ -296,6 +236,7 @@ class Micon::Core
296
236
  # I intentially broke the Metadata incapsulation to provide better performance, don't refactor it.
297
237
  @registry = {} # @loaded_classes, @constants = {}, {}
298
238
  @metadata = Micon::Metadata.new(@registry)
239
+ @stack = {}
299
240
 
300
241
  @application, @custom_scopes = {}, {}
301
242
 
@@ -319,59 +260,85 @@ class Micon::Core
319
260
  # @loaded_classes.clear
320
261
  end
321
262
 
263
+ #
264
+ # :mode, :runtime_path, used in component configuration
265
+ # - 'app/runtime'
266
+ # - :development, :test, :production
267
+ #
268
+ def runtime_path; @runtime_path || raise(":runtime_path not defined!") end
269
+ def runtime_path= runtime_path
270
+ runtime_path, force = runtime_path
271
+ raise "some components has been already initialized before You set :runtime_path!" unless empty? or force
272
+ @runtime_path = runtime_path
273
+ end
274
+ def runtime_path?; !!@runtime_path end
275
+
276
+ def mode; @mode || raise(":mode not defined!") end
277
+ def mode= mode
278
+ mode, force = mode
279
+ raise "some components has been already initialized before You set :mode!" unless empty? or force
280
+ @mode = mode
281
+ end
282
+ def mode?; !!@mode end
283
+
322
284
  protected
323
285
  def autoload_component_definition key, bang = true
324
286
  begin
325
287
  load "components/#{key.to_s.gsub(/::/, '/')}.rb"
326
288
  rescue LoadError
327
- end
289
+ end
328
290
  sname = @registry[key]
329
291
  raise_without_self "'#{key}' component not managed!" if bang and !sname
330
292
  sname
331
293
  end
332
294
 
333
295
  def create_object key, container = nil
334
- initializer, dependencies = @metadata.initializers[key]
296
+ initializer, dependencies, config = @metadata.initializers[key]
335
297
  raise "no initializer for :#{key} component!" unless initializer
336
-
337
- dependencies.each{|d| self[d]}
338
- @metadata.call_before key
339
-
340
- if container
341
- unless o = container[key]
342
- o = initializer.call
343
- container[key] = o
344
- else
345
- # complex case, there's an circular dependency, and the 'o' already has been
346
- # initialized in dependecies or callbacks
347
- # here's the sample case:
348
- #
349
- # app.register :environment, :application do
350
- # p :environment
351
- # 'environment'
352
- # end
353
- #
354
- # app.register :conveyors, :application, depends_on: :environment do
355
- # p :conveyors
356
- # 'conveyors'
357
- # end
358
- #
359
- # app.after :environment do
360
- # app[:conveyors]
361
- # end
362
- #
363
- # app[:conveyors]
364
-
365
- o = container[key]
298
+
299
+ raise "component :#{key} used before it's initialization is finished!" if @stack.include? key
300
+ begin
301
+ dependencies.each{|d| self[d]}
302
+ @metadata.call_before key
303
+
304
+ # we need to check container first, in complex cases (circullar dependency)
305
+ # the object already may be initialized.
306
+ # See "should allow to use circullar dependency in :after callback".
307
+ @stack[key] = true
308
+ o = (container && container[key]) || initializer.call
309
+
310
+ unless config == false
311
+ unless config
312
+ # loading and caching config
313
+ config = get_config key
314
+ config = false unless config # we use false to differentiate from nil
315
+ @metadata.initializers[key] = [initializer, dependencies, config]
316
+
317
+ apply_config o, config if config
318
+ else
319
+ apply_config o, config
320
+ end
366
321
  end
367
- else
368
- o = initializer.call
322
+
323
+ container[key] = o if container
324
+
325
+ raise "initializer for component :#{key} returns nill!" unless o
326
+ ensure
327
+ @stack.delete key
369
328
  end
370
- raise "initializer for component :#{key} returns nill!" unless o
371
-
329
+
372
330
  @metadata.call_after key, o
373
- o
374
- end
331
+ o
332
+ end
333
+
334
+ def get_config key
335
+ ::Micon::Config.new(self, key).load
336
+ end
337
+
338
+ def apply_config component, config
339
+ # config already have keys like "#{k}="
340
+ config.each{|k, v| component.send(k, v)}
341
+ end
375
342
 
376
343
  def name_hack namespace
377
344
  if namespace
data/readme.md CHANGED
@@ -6,6 +6,8 @@ Concentrate on business logic and interfaces and Micon will provide automatic co
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.
10
+
9
11
  ## Usage
10
12
 
11
13
  Let's suppose you are building the Ruby on Rails clone, there are lots of modules let's try to deal with them
@@ -89,4 +91,5 @@ there's no multithreading in ruby.
89
91
 
90
92
  Copyright (c) Alexey Petrushin [http://4ire.net](http://4ire.net), released under the MIT license.
91
93
 
92
- [ioc]: http://en.wikipedia.org/wiki/Inversion_of_control
94
+ [ioc]: http://en.wikipedia.org/wiki/Inversion_of_control
95
+ [rad_core]: https://github.com/alexeypetrushin/rad_core
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Configuration" do
4
+ before{self.micon = Micon::Core.new}
5
+
6
+ it "should configure component if config provided" do
7
+ micon.register(:logger){OpenStruct.new}
8
+ with_load_path "#{spec_dir}/basic/lib" do
9
+ micon[:logger].level.should == :info
10
+ end
11
+ end
12
+
13
+ it "should merge in order: conf <- conf.mode <- runtime <- runtime.mode" do
14
+ micon.register(:object){OpenStruct.new}
15
+ with_load_path "#{spec_dir}/order/lib" do
16
+ micon.runtime_path = "#{spec_dir}/order/app"
17
+ micon.mode = :production
18
+ micon[:object].tap do |o|
19
+ o.a.should == 'object.production.yml'
20
+ o.b.should == 'runtime.object.yml'
21
+ o.c.should == 'runtime.object.production.yml'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1 @@
1
+ c: 'runtime.object.production.yml'
@@ -0,0 +1,2 @@
1
+ b: 'runtime.object.yml'
2
+ c: 'runtime.object.yml'
@@ -0,0 +1,3 @@
1
+ a: 'object.production.yml'
2
+ b: 'object.production.yml'
3
+ c: 'object.production.yml'
@@ -0,0 +1,3 @@
1
+ a: 'object.yml'
2
+ b: 'object.yml'
3
+ c: 'object.yml'
@@ -3,13 +3,8 @@ require 'spec_helper'
3
3
  describe "Micelaneous" do
4
4
  with_load_path "#{spec_dir}/autoload/lib"
5
5
 
6
- before do
7
- self.micon = Micon::Core.new
8
- end
9
-
10
- after do
11
- remove_constants :TheRouter, :TheRad
12
- end
6
+ before{self.micon = Micon::Core.new}
7
+ after{remove_constants :TheRouter, :TheRad}
13
8
 
14
9
  describe "autoloading" do
15
10
  it "should autoload component definition" do
@@ -22,27 +17,57 @@ describe "Micelaneous" do
22
17
  # TheRad::TheView.should == "TheView"
23
18
  # end
24
19
  end
25
-
26
- it "should not initialize twice (from error)" do
27
- check = mock
28
- check.should_receive(:environment).once.ordered
29
- check.should_receive(:router).once.ordered
20
+
21
+ describe "complex circullar dependencies" do
22
+ it "should not initialize twice (from error)" do
23
+ micon.register :kit do
24
+ micon[:kit]
25
+ 'kit'
26
+ end
27
+ lambda{micon[:kit]}.should raise_error(/component :kit used before it's initialization is finished/)
28
+ end
30
29
 
31
- micon.register :environment do
32
- check.environment
33
- 'environment'
30
+ it "should not initialize twice if called from dependency (from error)" do
31
+ micon.register :environment do
32
+ micon[:router]
33
+ 'environment'
34
+ end
35
+
36
+ micon.register :router, depends_on: :environment do
37
+ 'router'
38
+ end
39
+
40
+ -> {micon[:router]}.should raise_error(/component .* used before it's initialization is finished/)
34
41
  end
35
42
 
36
- micon.register :router, depends_on: :environment do
37
- check.router
38
- 'router'
43
+ it "should allow to use circullar dependency in :after callback" do
44
+ check = mock
45
+ check.should_receive(:initialized).once
46
+ micon.register :kit do
47
+ check.initialized
48
+ 'kit'
49
+ end
50
+ micon.after :kit do
51
+ micon[:kit]
52
+ end
53
+ micon[:kit].should == 'kit'
39
54
  end
40
- micon.after :environment do
41
- # some code that needs :router
42
- micon[:router]
43
- end
44
55
 
45
- micon[:router]
56
+ it "should allow circullar dependencies in :after callback" do
57
+ micon.register :environment do
58
+ 'environment'
59
+ end
60
+
61
+ micon.register :router, depends_on: :environment do
62
+ 'router'
63
+ end
64
+
65
+ micon.after :environment do
66
+ micon[:router]
67
+ end
68
+
69
+ micon[:router]
70
+ end
46
71
  end
47
72
 
48
73
  it "helper method generation" do
@@ -1,13 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Application and Instance scopes" do
4
- before do
5
- self.micon = Micon::Core.new
6
- end
4
+ before{self.micon = Micon::Core.new}
7
5
 
8
6
  it "dependencies" do
9
7
  micon.register(:another_object, depends_on: :the_object){"another_object"}
10
- -> {micon[:another_object]}.should raise_error(/the_object/)
8
+ -> {micon[:another_object]}.should raise_error(/the_object.*not managed/)
11
9
  micon.register(:the_object){"the_object"}
12
10
  micon[:another_object]
13
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: micon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-06-17 00:00:00.000000000 +04:00
12
+ date: 2011-07-04 00:00:00.000000000 +04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
  description:
@@ -21,6 +21,7 @@ files:
21
21
  - Rakefile
22
22
  - readme.md
23
23
  - lib/micon/class.rb
24
+ - lib/micon/config.rb
24
25
  - lib/micon/core.rb
25
26
  - lib/micon/helper.rb
26
27
  - lib/micon/metadata.rb
@@ -30,6 +31,12 @@ files:
30
31
  - lib/micon/support.rb
31
32
  - lib/micon.rb
32
33
  - spec/callbacks_spec.rb
34
+ - spec/config_spec/basic/lib/components/logger.yml
35
+ - spec/config_spec/order/app/config/object.production.yml
36
+ - spec/config_spec/order/app/config/object.yml
37
+ - spec/config_spec/order/lib/components/object.production.yml
38
+ - spec/config_spec/order/lib/components/object.yml
39
+ - spec/config_spec.rb
33
40
  - spec/constants_spec/get_constant_component/lib/components/TheController.rb
34
41
  - spec/constants_spec.rb
35
42
  - spec/custom_scope_spec.rb