ruse 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed5777f12733b8e7e6ed8fc9711354f77b93db58
4
- data.tar.gz: 62796162f3a934ff9668cc39f029b7bb6b58947f
3
+ metadata.gz: c0271481168d1bc9a40d546611f6d3f142eda19a
4
+ data.tar.gz: 530a4e863f7820be1970720975f57136332ddc0c
5
5
  SHA512:
6
- metadata.gz: f5a5251bc60259ea099c2cf2f5402f8dd2b642c8b0ae736305ecfc71e28398ac911b6e68c3e017f9267ab7b1b3da5974f563d3f784ff55496e37fd4ab08fdbd1
7
- data.tar.gz: 1a1a86387be403adfa675b87b9d0b91f3ea401939da9bd8937dddec6c4a34d3cca3948e86f4bc228f0d98e92ab9244869dcc04850542140b4cbb9768203ceb06
6
+ metadata.gz: 885a610f5011e855d770749cfb4582115139b6d1a129dc27dba412ee8c0fb6f467fe8e2141bfd1a2549bdec3966cdb1d46a7126f9b83a1363d5f517f07553528
7
+ data.tar.gz: 251011507aea85f2d8b2417edc9a0b7527167b9db4356ec8d3101b64ad4bb22cd24114eaa39312e3cad6c1072bd228c232bb3d1afc51544e3fd55a73ca30e0d8
data/Rakefile CHANGED
@@ -8,3 +8,12 @@ Rake::TestTask.new do |t|
8
8
  t.test_files = FileList['specs/*_spec.rb']
9
9
  t.verbose = true
10
10
  end
11
+
12
+ # from http://erniemiller.org/2014/02/05/7-lines-every-gems-rakefile-should-have/
13
+ task :console do
14
+ require 'irb'
15
+ require 'irb/completion'
16
+ require 'ruse'
17
+ ARGV.clear
18
+ IRB.start
19
+ end
@@ -2,7 +2,9 @@ require "ruse/injector"
2
2
  require "ruse/version"
3
3
 
4
4
  module Ruse
5
- def self.create_injector
6
- Ruse::Injector.new
5
+ def self.create_injector(config=nil)
6
+ Ruse::Injector.new.tap do |i|
7
+ i.configure(config) if config
8
+ end
7
9
  end
8
10
  end
@@ -1,32 +1,136 @@
1
1
  module Ruse
2
2
  class Injector
3
3
  def get(identifier)
4
- type = resolve_type identifier
5
- args = resolve_dependencies type
6
- type.new *args
4
+ identifier = aliases[identifier] || identifier
5
+ cache_fetch(identifier) do
6
+ resolver = find_resolver identifier
7
+ raise UnknownServiceError.new(identifier) unless resolver
8
+ resolver.build identifier
9
+ end
7
10
  end
8
11
 
9
- def resolve_type(identifier)
10
- type_name = classify(identifier)
11
- unless Object.const_defined?(type_name)
12
- raise UnknownServiceError.new(type_name)
13
- end
14
- Object.const_get type_name
12
+ def configure(settings)
13
+ configuration.merge! settings
15
14
  end
16
15
 
17
- def resolve_dependencies(type)
18
- type.instance_method(:initialize).parameters.map{|_, identifier|
19
- get identifier
16
+ private
17
+
18
+ def cache_fetch(identifier, &block)
19
+ return cache[identifier] if cache.key?(identifier)
20
+ cache[identifier] = block.call
21
+ end
22
+
23
+ def cache
24
+ @cache ||= {}
25
+ end
26
+
27
+ def configuration
28
+ @configuration ||= {
29
+ aliases:{},
30
+ values: {},
31
+ }
32
+ end
33
+
34
+ def aliases
35
+ configuration[:aliases]
36
+ end
37
+
38
+ def values
39
+ configuration[:values]
40
+ end
41
+
42
+ def find_resolver(identifier)
43
+ resolvers.detect {|h|
44
+ h.can_build?(identifier)
20
45
  }
21
46
  end
22
47
 
23
- def classify(term)
48
+ def resolvers
49
+ @resolvers ||= [
50
+ ValueResolver.new(values),
51
+ TypeResolver.new(self),
52
+ ]
53
+ end
54
+ end
55
+
56
+ class UnknownServiceError < StandardError; end
57
+
58
+ class ValueResolver
59
+ attr_reader :values
60
+
61
+ def initialize(values)
62
+ @values = values
63
+ end
64
+
65
+ def can_build?(identifier)
66
+ values.key? identifier
67
+ end
68
+
69
+ def build(identifier)
70
+ values.fetch(identifier)
71
+ end
72
+ end
73
+
74
+ class TypeResolver
75
+ def initialize(injector)
76
+ @injector = injector
77
+ end
78
+
79
+ def can_build?(identifier)
80
+ type_name = self.class.classify(identifier)
81
+ load_type type_name
82
+ end
83
+
84
+ def build(identifier)
85
+ type = resolve_type identifier
86
+ object_factory.build(type)
87
+ end
88
+
89
+ def self.classify(term)
24
90
  # lifted from active_support gem: lib/active_support/inflector/methods.rb
25
91
  string = term.to_s
26
92
  string = string.sub(/^[a-z\d]*/) { $&.capitalize }
27
93
  string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{ $2.capitalize}" }.gsub('/', '::')
28
94
  end
95
+
96
+ private
97
+
98
+ def load_type(type_name)
99
+ type_name.split('::').reduce(Object){|ns, name|
100
+ if ns.const_defined? name
101
+ ns.const_get name
102
+ end
103
+ }
104
+ end
105
+
106
+ def object_factory
107
+ @object_factory ||= ObjectFactory.new(@injector)
108
+ end
109
+
110
+ def resolve_type(identifier)
111
+ type_name = self.class.classify(identifier)
112
+ load_type type_name
113
+ end
29
114
  end
30
115
 
31
- class UnknownServiceError < StandardError; end
116
+ class ObjectFactory
117
+ attr_reader :injector
118
+
119
+ def initialize(injector)
120
+ @injector = injector
121
+ end
122
+
123
+ def build(type)
124
+ args = resolve_dependencies type
125
+ type.new *args
126
+ end
127
+
128
+ private
129
+
130
+ def resolve_dependencies(type)
131
+ type.instance_method(:initialize).parameters.map{|_, identifier|
132
+ @injector.get identifier
133
+ }
134
+ end
135
+ end
32
136
  end
@@ -1,3 +1,3 @@
1
1
  module Ruse
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -3,42 +3,107 @@ require 'minitest/autorun'
3
3
  require 'ruse/injector'
4
4
 
5
5
  describe Ruse::Injector do
6
- it "retrieves an instance it can infer from an identifier" do
7
- injector = Ruse::Injector.new
8
- injector.get("SomeService").must_be_instance_of(SomeService)
6
+ def injector
7
+ @injector ||= Ruse::Injector.new
8
+ end
9
+
10
+ it "retrieves instance when identifier is a class name" do
11
+ injector.get("ServiceA").must_be_instance_of(ServiceA)
12
+ end
13
+
14
+ it "retrieves instance when identifier can be converted to a class name" do
15
+ injector.get(:service_a).must_be_instance_of(ServiceA)
16
+ end
17
+
18
+ it "retrieves instance when identifier is namespaced class name" do
19
+ injector.get("Deep::Namespaced::Service").
20
+ must_be_instance_of(Deep::Namespaced::Service)
9
21
  end
10
22
 
11
23
  it "raises UnknownServiceError for an identifier it cannot resolve" do
12
- injector = Ruse::Injector.new
13
24
  ->{
14
25
  injector.get("cannot_be_resolved")
15
26
  }.must_raise(Ruse::UnknownServiceError)
16
27
  end
17
28
 
18
29
  it "populates dependencies for the instance it retrieves" do
19
- injector = Ruse::Injector.new
20
- instance = injector.get("ServiceConsumer")
21
- instance.must_be_instance_of(ServiceConsumer)
22
- instance.service1.must_be_instance_of(SomeService)
23
- instance.service2.must_be_instance_of(OtherService)
30
+ instance = injector.get("ConsumerA")
31
+ instance.must_be_instance_of(ConsumerA)
32
+ instance.a.must_be_instance_of(ServiceA)
33
+ instance.b.must_be_instance_of(ServiceB)
34
+ end
35
+
36
+ it "retrieves an instance based on a configured alias" do
37
+ injector.configure aliases: {special_service: "ServiceA"}
38
+ injector.get(:special_service).must_be_instance_of(ServiceA)
39
+ end
40
+
41
+ it "returns same instance of a given service within a single request" do
42
+ instance = injector.get("ConsumerC")
43
+ instance.ca.a.must_be_same_as(instance.cb.a)
44
+ end
45
+
46
+ it "returns same instance of a given service for life of injector" do
47
+ instance1 = injector.get("ConsumerC")
48
+ instance2 = injector.get("ConsumerC")
49
+ instance1.must_be_same_as(instance2)
50
+ end
51
+
52
+ it "returns different instance of given service for different injectors" do
53
+ instance1 = Ruse::Injector.new.get("ConsumerC")
54
+ instance2 = Ruse::Injector.new.get("ConsumerC")
55
+ instance1.wont_be_same_as(instance2)
24
56
  end
25
57
 
26
- class SomeService; end
27
- class OtherService; end
58
+ it "can retrieve a registered value object" do
59
+ value_object = Object.new
60
+ injector.configure values: { example: value_object }
61
+ injector.get(:example).must_be_same_as(value_object)
62
+ end
63
+
64
+ class ServiceA; end
65
+ class ServiceB; end
66
+
67
+ class ConsumerA
68
+ attr_reader :a, :b
69
+ def initialize(service_a, service_b)
70
+ @a = service_a
71
+ @b = service_b
72
+ end
73
+ end
74
+
75
+ class ConsumerB
76
+ attr_reader :a
77
+ def initialize(service_a)
78
+ @a = service_a
79
+ end
80
+ end
81
+
82
+ class ConsumerC
83
+ attr_reader :ca, :cb
84
+ def initialize(consumer_a, consumer_b)
85
+ @ca = consumer_a
86
+ @cb = consumer_b
87
+ end
88
+ end
28
89
 
29
- class ServiceConsumer
30
- attr_reader :service1, :service2
31
- def initialize(some_service, other_service)
32
- @service1 = some_service
33
- @service2 = other_service
90
+ module Deep
91
+ module Namespaced
92
+ class Service
93
+ end
34
94
  end
35
95
  end
96
+
36
97
  end
37
98
 
38
- # classify will be moved
39
99
  describe "classify" do
40
100
  it "converts an underscored_term to PascalCase" do
41
- resolver = Ruse::Injector.new
101
+ resolver = Ruse::TypeResolver
42
102
  resolver.classify("camel_case").must_equal("CamelCase")
43
103
  end
104
+
105
+ it "echoes back a PascalCase term" do
106
+ resolver = Ruse::TypeResolver
107
+ resolver.classify("PascalCase").must_equal("PascalCase")
108
+ end
44
109
  end
@@ -6,4 +6,9 @@ describe Ruse do
6
6
  it "can create an injector" do
7
7
  Ruse.create_injector.must_be_kind_of Ruse::Injector
8
8
  end
9
+
10
+ it "passes configuration to the injector" do
11
+ injector = Ruse.create_injector({values: {answer: 42}})
12
+ injector.get(:answer).must_equal 42
13
+ end
9
14
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Flanagan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-04 00:00:00.000000000 Z
11
+ date: 2014-02-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler