wirer 0.4.7
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.rb +398 -0
- data/lib/wirer/container.rb +283 -0
- data/lib/wirer/dependency.rb +234 -0
- data/lib/wirer/errors.rb +30 -0
- data/lib/wirer/factory/class_mixins.rb +117 -0
- data/lib/wirer/factory/curried_dependencies.rb +33 -0
- data/lib/wirer/factory/from_args.rb +100 -0
- data/lib/wirer/factory/from_instance.rb +24 -0
- data/lib/wirer/factory/interface.rb +114 -0
- data/lib/wirer/factory/wrapped.rb +94 -0
- data/lib/wirer/service.rb +51 -0
- data/lib/wirer/version.rb +3 -0
- data/lib/wirer.rb +20 -0
- metadata +139 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
# dependency :foo, Class, :features => [:feature, :feature], :optional => true
|
2
|
+
# dependency :foo, Class, :features => [:feature, :feature], :multiple => true
|
3
|
+
#
|
4
|
+
# dependency :foo, Class, :optional => true
|
5
|
+
# dependency :foo, :feature, :another_feature, :optional => true, :constructor => true
|
6
|
+
|
7
|
+
module Wirer
|
8
|
+
|
9
|
+
class Dependency
|
10
|
+
def self.new_from_args(*args)
|
11
|
+
new(normalise_args(*args))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.new_from_arg_or_args_list(arg_or_args_list)
|
15
|
+
new(normalise_arg_or_args_list(arg_or_args_list))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.normalise_arg_or_args_list(arg_or_args_list)
|
19
|
+
case arg_or_args_list
|
20
|
+
when Hash then arg_or_args_list
|
21
|
+
when Array then normalise_args(*arg_or_args_list)
|
22
|
+
else normalise_args(arg_or_args_list)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.normalise_args(*args)
|
27
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
28
|
+
args.each do |requirement|
|
29
|
+
case requirement
|
30
|
+
when Module then options[:class] = requirement
|
31
|
+
else (options[:features] ||= []) << requirement
|
32
|
+
end
|
33
|
+
end
|
34
|
+
options
|
35
|
+
end
|
36
|
+
|
37
|
+
OPTION_NAMES = [:class, :module, :features, :prefer, :multiple, :optional, :factory]
|
38
|
+
|
39
|
+
# By default, dependencies will :prefer => :default.
|
40
|
+
# This means if you want to force one factory to be preferred over another
|
41
|
+
# in a given situation, you can just add (or wrap it with) a provided feature
|
42
|
+
# name of :default.
|
43
|
+
PREFER_DEFAULT = :default
|
44
|
+
|
45
|
+
def initialize(options = {})
|
46
|
+
required_class = options[:class] || options[:module]
|
47
|
+
case required_class
|
48
|
+
when Module
|
49
|
+
@required_class = required_class
|
50
|
+
when String
|
51
|
+
@required_class_name = required_class
|
52
|
+
when NilClass
|
53
|
+
@required_class = nil
|
54
|
+
else
|
55
|
+
raise ArgumentError, "required :class for a Dependency must be a Module or Class, or a String name of a Module or Class"
|
56
|
+
end
|
57
|
+
@required_features = options[:features] && [*options[:features]]
|
58
|
+
@multiple = options[:multiple] || false
|
59
|
+
@optional = options[:optional] || false
|
60
|
+
@factory = options[:factory] || false
|
61
|
+
|
62
|
+
if @multiple
|
63
|
+
raise ArgumentError, "preferred features don't make sense for a :multiple depedency" if options.has_key?(:prefer)
|
64
|
+
else
|
65
|
+
@preferred_features = [*options.fetch(:prefer, PREFER_DEFAULT) || []]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :required_features, :preferred_features
|
70
|
+
def multiple?; @multiple; end
|
71
|
+
def optional?; @optional; end
|
72
|
+
|
73
|
+
# Specifying :factory => true on a dependency means that you won't be given an actual instance
|
74
|
+
# of the class you asked for, but rather you'll be given a simple factory wrapper which allows
|
75
|
+
# you to construct your own instance(s), with any dependencies pre-supplied.
|
76
|
+
# See Factory::CurriedDependencies
|
77
|
+
def factory?; @factory; end
|
78
|
+
|
79
|
+
# A string class name may be supplied as the :class arg to the constructor, in which case we only
|
80
|
+
# attempt to resolve the actual class from it the first time .required_class is requested.
|
81
|
+
#
|
82
|
+
# This helps avoid introducing undesired load order dependencies between classes using Wirer::Factory::ClassDSL.
|
83
|
+
def required_class
|
84
|
+
return @required_class if defined?(@required_class)
|
85
|
+
@required_class = @required_class_name.split("::").inject(Object, :const_get)
|
86
|
+
end
|
87
|
+
|
88
|
+
def requirements_to_s
|
89
|
+
[
|
90
|
+
begin
|
91
|
+
case required_class
|
92
|
+
when ::Class then "class #{@required_class}"
|
93
|
+
when ::Module then "module #{@required_class}"
|
94
|
+
end
|
95
|
+
rescue NameError
|
96
|
+
"class or module name #{@required_class_name} (not currently loaded!)"
|
97
|
+
end,
|
98
|
+
@required_features && "features #{@required_features.inspect}"
|
99
|
+
].compact.join(" and ")
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
description = [
|
104
|
+
@factory ? "factory for #{requirements_to_s}" : requirements_to_s,
|
105
|
+
("optional" if @optional),
|
106
|
+
("multiple" if @multiple),
|
107
|
+
("preferring features #{@preferred_features.inspect}" if @preferred_features && !@preferred_features.empty?)
|
108
|
+
].compact.join(', ')
|
109
|
+
"#<#{self.class} on #{description}>"
|
110
|
+
end
|
111
|
+
|
112
|
+
def match_factories(available_factories)
|
113
|
+
candidates = available_factories.select {|f| self === f}
|
114
|
+
if !@optional && candidates.length == 0
|
115
|
+
raise DependencyFindingError, "No available factories matching #{requirements_to_s}"
|
116
|
+
end
|
117
|
+
if @multiple
|
118
|
+
candidates.map! {|c| yield c} if block_given?; candidates
|
119
|
+
else
|
120
|
+
candidate = if candidates.length > 1
|
121
|
+
if @preferred_features.empty?
|
122
|
+
raise DependencyFindingError, "More than one factory available matching #{requirements_to_s}"
|
123
|
+
else
|
124
|
+
unique_preferred_factory(candidates)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
candidates.first
|
128
|
+
end
|
129
|
+
if block_given?
|
130
|
+
yield candidate if candidate
|
131
|
+
else
|
132
|
+
candidate
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def ===(factory)
|
138
|
+
factory.is_a?(Factory::Interface) && matches_required_class(factory) && matches_required_features(factory)
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_argument(argument_name, argument, strict_type_checks=false)
|
142
|
+
if @multiple
|
143
|
+
raise ArgumentError, "expected Array for argument #{argument_name}" unless argument.is_a?(Array)
|
144
|
+
if argument.empty?
|
145
|
+
if @optional then return else
|
146
|
+
raise ArgumentError, "expected at least one value for argument #{argument_name}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
argument.each do |value|
|
150
|
+
if @factory
|
151
|
+
unless value.respond_to?(:new)
|
152
|
+
raise ArgumentError, "expected Array of factory-like objects which respond_to?(:new) for argument #{argument_name}"
|
153
|
+
end
|
154
|
+
elsif strict_type_checks && required_class && !value.is_a?(required_class)
|
155
|
+
raise ArgumentError, "expected Array of #{required_class} for argument #{argument_name}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
else
|
159
|
+
if argument.nil?
|
160
|
+
if @optional then return else
|
161
|
+
raise ArgumentError, "expected argument #{argument_name}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
if @factory
|
165
|
+
unless argument.respond_to?(:new)
|
166
|
+
raise ArgumentError, "expected factory-like object which respond_to?(:new) for argument #{argument_name}"
|
167
|
+
end
|
168
|
+
elsif strict_type_checks && required_class && !argument.is_a?(required_class)
|
169
|
+
raise ArgumentError, "expected #{required_class} for argument #{argument_name}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# if the required_class can't be resolved (ie a class of that name doesn't even exist) then nothing will match.
|
175
|
+
def matches_required_class(factory)
|
176
|
+
begin
|
177
|
+
!required_class || factory.provides_class <= required_class
|
178
|
+
rescue NameError
|
179
|
+
false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def matches_required_features(factory)
|
184
|
+
!@required_features || @required_features.all? {|feature| factory.provides_features.include?(feature)}
|
185
|
+
end
|
186
|
+
|
187
|
+
def with_options(options)
|
188
|
+
new_options = {
|
189
|
+
:multiple => @multiple,
|
190
|
+
:optional => @optional,
|
191
|
+
:class => required_class,
|
192
|
+
:features => @required_features,
|
193
|
+
:prefer => @preferred_features
|
194
|
+
}
|
195
|
+
new_required_class = options[:class] and begin
|
196
|
+
if required_class && !(new_required_class <= required_class)
|
197
|
+
raise "Required class #{new_required_class} not compatible with existing requirement for #{required_class}"
|
198
|
+
end
|
199
|
+
new_options[:class] = new_required_class
|
200
|
+
end
|
201
|
+
new_required_features = options[:features] and begin
|
202
|
+
new_options[:features] ||= []
|
203
|
+
new_options[:features] |= [*new_required_features]
|
204
|
+
end
|
205
|
+
new_preferred_features = options[:prefer] and begin
|
206
|
+
new_options[:prefer] ||= []
|
207
|
+
new_options[:prefer] |= [*new_preferred_features]
|
208
|
+
end
|
209
|
+
self.class.new(new_options)
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
def unique_preferred_factory(candidates)
|
215
|
+
max_preferred_features_count = 0
|
216
|
+
winners = []
|
217
|
+
candidates.each do |candidate|
|
218
|
+
provided = candidate.provides_features
|
219
|
+
count = @preferred_features.count {|f| provided.include?(f)}
|
220
|
+
if count > max_preferred_features_count
|
221
|
+
max_preferred_features_count = count
|
222
|
+
winners = [candidate]
|
223
|
+
elsif count == max_preferred_features_count
|
224
|
+
winners << candidate
|
225
|
+
end
|
226
|
+
end
|
227
|
+
if winners.length > 1
|
228
|
+
raise DependencyFindingError,
|
229
|
+
"More than one factory available matching #{requirements_to_s}, and tie can't be resolved using preferred_features #{@preferred_features.inspect}"
|
230
|
+
end
|
231
|
+
winners.first
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
data/lib/wirer/errors.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Wirer
|
2
|
+
|
3
|
+
# Thank you to http://rubyforge.org/projects/nestegg for the pattern
|
4
|
+
class Error < StandardError
|
5
|
+
|
6
|
+
attr_reader :cause
|
7
|
+
alias :wrapped_error :cause
|
8
|
+
|
9
|
+
def initialize(msg, cause=nil)
|
10
|
+
@cause = cause
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_backtrace(bt)
|
15
|
+
if cause
|
16
|
+
bt << "cause: #{cause.class.name}: #{cause}"
|
17
|
+
bt.concat cause.backtrace
|
18
|
+
end
|
19
|
+
super(bt)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class DependencyFindingError < Error; end
|
25
|
+
class CyclicDependencyError < Error; end
|
26
|
+
|
27
|
+
# raised when a dependency could be found, but failed to be constructed.
|
28
|
+
class DependencyConstructionError < Error; end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Wirer
|
2
|
+
# You can extend a Class instance directly with this if you want
|
3
|
+
# the class itself to be usable as a factory, exposing
|
4
|
+
# Wirer::Factory::Interface.
|
5
|
+
#
|
6
|
+
# By default, new_from_dependencies will call new on the class
|
7
|
+
# with the hash of dependencies as the last argument (or merged
|
8
|
+
# into the last argument where this is already a Hash). If you
|
9
|
+
# don't like this you may want to override the new_from_dependencies
|
10
|
+
# class method.
|
11
|
+
#
|
12
|
+
# You'll still probably want to override some of
|
13
|
+
# constructor_dependencies, provides_features, setter_dependencies;
|
14
|
+
# if you'd prefer to do this via a handy DSL, instead see
|
15
|
+
# See Wirer::Factory::ClassDSL.
|
16
|
+
module Factory::ClassMixin
|
17
|
+
include Factory::Interface
|
18
|
+
|
19
|
+
def provides_class; self; end
|
20
|
+
|
21
|
+
def new_from_dependencies(dependencies, *other_args, &block_arg)
|
22
|
+
if other_args.last.is_a?(Hash)
|
23
|
+
hash_arg = other_args.pop
|
24
|
+
other_args.push(hash_arg.merge(dependencies))
|
25
|
+
else
|
26
|
+
other_args.push(dependencies) unless dependencies.empty?
|
27
|
+
end
|
28
|
+
new(*other_args, &block_arg)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# A more convenient form of Wirer::Factory::ClassMixin, this additionally adds some
|
33
|
+
# private DSL methods which let you declare your constructor_dependencies,
|
34
|
+
# setter_dependencies and provides_features.
|
35
|
+
#
|
36
|
+
# The DSL works nicely with respect to subclassing, so you can add extra dependencies
|
37
|
+
# or features in a subclass.
|
38
|
+
module Factory::ClassDSL
|
39
|
+
include Factory::ClassMixin
|
40
|
+
|
41
|
+
def constructor_dependencies
|
42
|
+
@constructor_dependencies ||= (superclass.is_a?(Factory::Interface) ? superclass.constructor_dependencies.dup : {})
|
43
|
+
end
|
44
|
+
|
45
|
+
# the supplied implementation of setter_dependencies does not allow for them varying dependening on the
|
46
|
+
# instance passed; if you want to specify setter_dependencies on an instance-sensitive basis you'll need
|
47
|
+
# to override this yourself.
|
48
|
+
def setter_dependencies(instance=nil)
|
49
|
+
@setter_dependencies ||= (superclass.is_a?(Factory::Interface) ? superclass.setter_dependencies.dup : {})
|
50
|
+
end
|
51
|
+
|
52
|
+
def provides_features
|
53
|
+
@provides_features ||= (superclass.is_a?(Factory::Interface) ? superclass.provides_features.dup : [])
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def provides_feature(*args)
|
59
|
+
provides_features.concat(args)
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_dependency(type, arg_name, *dependency_args)
|
63
|
+
deps_hash = type == :setter ? setter_dependencies : constructor_dependencies
|
64
|
+
deps_hash[arg_name] = Dependency.new_from_args(*dependency_args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# as a convenience, will additionally define an attr_reader for this dependency name
|
68
|
+
# unless you specify :getter => false. by default it will be private, but :getter => :public
|
69
|
+
# will make it public.
|
70
|
+
def constructor_dependency(name, *args)
|
71
|
+
getter = if args.last.is_a?(Hash) then args.last.delete(:getter) end
|
72
|
+
if getter != false
|
73
|
+
attr_reader(name)
|
74
|
+
getter == :public ? public(name) : private(name)
|
75
|
+
end
|
76
|
+
add_dependency(:constructor, name, *args)
|
77
|
+
end
|
78
|
+
alias :dependency :constructor_dependency
|
79
|
+
|
80
|
+
# sugar for dependency :foo, ..., :factory => true.
|
81
|
+
#
|
82
|
+
# for factory dependencies, you'll be given a factory instance responding to 'new'
|
83
|
+
# from which you can construct your own instances of the dependency -- as opposed
|
84
|
+
# to normal dependencies where the container will give you a pre-constructed instance.
|
85
|
+
def factory_dependency(name, *args)
|
86
|
+
args.push({}) unless args.last.is_a?(Hash)
|
87
|
+
args.last[:factory] = true
|
88
|
+
constructor_dependency(name, *args)
|
89
|
+
end
|
90
|
+
|
91
|
+
# will additionally define a attr_writer method of this name, unless :setter => false
|
92
|
+
# is specified. this is private by default but made public if you specify :setter => :public.
|
93
|
+
#
|
94
|
+
# and a corresponding public attr_reader too if :accessor => true if specified.
|
95
|
+
def setter_dependency(name, *args)
|
96
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
97
|
+
getter = options.delete(:getter)
|
98
|
+
setter = options.delete(:setter)
|
99
|
+
if setter != false
|
100
|
+
attr_writer(name)
|
101
|
+
setter == :public ? public(:"#{name}=") : private(:"#{name}=")
|
102
|
+
end
|
103
|
+
if getter != false
|
104
|
+
attr_reader(name)
|
105
|
+
getter == :public ? public(name) : private(name)
|
106
|
+
end
|
107
|
+
|
108
|
+
add_dependency(:setter, name, *args)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Class
|
114
|
+
def wireable
|
115
|
+
extend Wirer::Factory::ClassDSL
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Wirer
|
2
|
+
# This doesn't implement the full Factory::Interface, rather it's a simple
|
3
|
+
# 'curried' wrapper around the new_from_dependencies method of a factory,
|
4
|
+
# where the dependency arguments are pre-supplied.
|
5
|
+
#
|
6
|
+
# You can use one of these pretty much the same as a class, in that it has a
|
7
|
+
# 'new' method, or it also implements a Proc-like interface ('call' and 'to_proc')
|
8
|
+
# so you can also treat it like a block which constructs things.
|
9
|
+
#
|
10
|
+
# Factory::Interface#curry_with_dependencies is used to make these, but you'd
|
11
|
+
# normally get one via a Wirer::Containerm by specifying a dependency with :factory => true;
|
12
|
+
# the container will then give you a curried factory from which you can construct
|
13
|
+
# your own instances, rather than supplying a single pre-constructed instance.
|
14
|
+
#
|
15
|
+
# Note: at present only constructor dependencies can be curried in this way.
|
16
|
+
class Factory::CurriedDependencies
|
17
|
+
def initialize(factory, dependencies)
|
18
|
+
@factory = factory
|
19
|
+
@dependencies = dependencies
|
20
|
+
end
|
21
|
+
|
22
|
+
def new(*args, &block_arg)
|
23
|
+
@factory.new_from_dependencies(@dependencies, *args, &block_arg)
|
24
|
+
end
|
25
|
+
|
26
|
+
alias :call :new
|
27
|
+
|
28
|
+
# this allows it to be implicitly converted into a block argument, eg: instances = args.map(&factory)
|
29
|
+
def to_proc
|
30
|
+
method(:new).to_proc
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Wirer
|
2
|
+
# This is handy if you want to create a factory instance entirely from
|
3
|
+
# supplied arguments, in particular from a supplied constructor block.
|
4
|
+
#
|
5
|
+
# This saves you having to create your own Factory class in almost all
|
6
|
+
# cases.
|
7
|
+
#
|
8
|
+
# If you specify a :class, a default implementation will be supplied
|
9
|
+
# for constructing instances from it, whereby dependencies are passed
|
10
|
+
# as the last argument to the class's new method, after any other args.
|
11
|
+
# (if the last arg is already a Hash, the dependencies will be merged
|
12
|
+
# into it).
|
13
|
+
#
|
14
|
+
# Is also aliased for convenience from Factory.new.
|
15
|
+
#
|
16
|
+
# Factory.new(
|
17
|
+
# :class => Foo,
|
18
|
+
# :dependencies => {
|
19
|
+
# :logger => Logger,
|
20
|
+
# :bars => {:class => Bar, :multiple => true}
|
21
|
+
# }
|
22
|
+
# ) do |depedencies, *args, &block|
|
23
|
+
# Foo.new(*args, dependencies, &block)
|
24
|
+
# end
|
25
|
+
class Factory::FromArgs
|
26
|
+
include Factory::Interface
|
27
|
+
|
28
|
+
OPTION_NAMES = [:class, :module, :args, :features, :dependencies, :constructor_dependencies, :setter_dependencies].freeze
|
29
|
+
|
30
|
+
def initialize(options={}, &constructor_block)
|
31
|
+
@provides_class = options[:class] || options[:module]
|
32
|
+
|
33
|
+
@constructor_dependencies = {}
|
34
|
+
(options[:constructor_dependencies] || options[:dependencies] || {}).each do |name, args|
|
35
|
+
@constructor_dependencies[name] = Dependency.new_from_arg_or_args_list(args)
|
36
|
+
end
|
37
|
+
@setter_dependencies = {}
|
38
|
+
(options[:setter_dependencies] || {}).each do |name, args|
|
39
|
+
@setter_dependencies[name] = Dependency.new_from_arg_or_args_list(args)
|
40
|
+
end
|
41
|
+
|
42
|
+
@provides_features = options[:features] || []
|
43
|
+
@constructor_block = constructor_block if constructor_block
|
44
|
+
|
45
|
+
case @provides_class
|
46
|
+
when ::Class
|
47
|
+
@initial_args = options[:args] if options[:args]
|
48
|
+
when Module
|
49
|
+
unless @constructor_block
|
50
|
+
raise ArgumentError, "when a Module is specified you need to supply a constructor block"
|
51
|
+
end
|
52
|
+
when NilClass
|
53
|
+
@provides_class = Object
|
54
|
+
unless @constructor_block
|
55
|
+
raise ArgumentError, "expected a :class or a constructor block or both"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise TypeError, ":class / :module options only accept a Class or Module"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :constructor_dependencies, :provides_class,
|
63
|
+
:provides_features, :initial_args, :wrapped_class
|
64
|
+
|
65
|
+
# Factory::FromArgs doesn't allow you to do instance-sensitive setter-dependencies;
|
66
|
+
# subclass or make your own factory if you want these.
|
67
|
+
def setter_dependencies(instance=nil); @setter_dependencies; end
|
68
|
+
|
69
|
+
def new_from_dependencies(dependencies, *other_args, &block_arg)
|
70
|
+
if @constructor_block
|
71
|
+
@constructor_block.call(dependencies, *other_args, &block_arg)
|
72
|
+
else
|
73
|
+
# The only time it allows you not to specify a constructor_block
|
74
|
+
# is when an actual Class is supplied for provides_class.
|
75
|
+
#
|
76
|
+
# In this case we supply a default construction method whereby
|
77
|
+
# dependencies are merged into a last argument:
|
78
|
+
if other_args.last.is_a?(Hash)
|
79
|
+
hash_arg = other_args.pop
|
80
|
+
other_args.push(hash_arg.merge(dependencies))
|
81
|
+
else
|
82
|
+
other_args.push(dependencies) unless dependencies.empty?
|
83
|
+
end
|
84
|
+
case @initial_args
|
85
|
+
when NilClass # forgeddit
|
86
|
+
when Array then other_args.unshift(*@initial_args)
|
87
|
+
else other_args.unshift(@initial_args)
|
88
|
+
end
|
89
|
+
@provides_class.new(*other_args, &block_arg)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module Factory
|
95
|
+
# delegates to Factory::FromArgs.new
|
96
|
+
def self.new(options, &constructor_block)
|
97
|
+
FromArgs.new(options, &constructor_block)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Wirer
|
2
|
+
# For when you have some pre-existing instance which you want to wrap as a singleton
|
3
|
+
# Factory.
|
4
|
+
# (You wouldn't normally need to do this yourself, but rather Container uses it
|
5
|
+
# under the hood if you add an existing instance to the container)
|
6
|
+
class Factory::FromInstance
|
7
|
+
include Factory::Interface
|
8
|
+
|
9
|
+
attr_reader :instance, :provides_features
|
10
|
+
|
11
|
+
def initialize(instance, *features)
|
12
|
+
@instance = instance
|
13
|
+
@provides_features = features
|
14
|
+
end
|
15
|
+
|
16
|
+
def provides_class
|
17
|
+
@instance.class
|
18
|
+
end
|
19
|
+
|
20
|
+
def new_from_dependencies(deps=nil)
|
21
|
+
@instance
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Wirer
|
2
|
+
module Factory; end
|
3
|
+
|
4
|
+
# This is the basic Factory interface around which the whole framework is built.
|
5
|
+
#
|
6
|
+
# You won't normally need to implement this interface yourself, but it's useful
|
7
|
+
# in terms of understanding how Wirer works.
|
8
|
+
#
|
9
|
+
# A Factory is responsible for creating some kind of object, given some dependencies
|
10
|
+
# and possibly some additional arguments. so new_from_dependencies is the main
|
11
|
+
# method here.
|
12
|
+
#
|
13
|
+
# It also comes with an interface (constructor_dependencies) telling you what
|
14
|
+
# the required dependencies are; this is specified via a hash of symbol argument
|
15
|
+
# names to Dependency objects which specify the criterea that must be satisfied
|
16
|
+
# for the dependency argument of that name.
|
17
|
+
#
|
18
|
+
# Wirer::Container uses this metadata to find and pass in dependencies automatically
|
19
|
+
# when constructing things from Factories.
|
20
|
+
#
|
21
|
+
# == Two-phase initialisation
|
22
|
+
#
|
23
|
+
# Factory can also support cases where some dependencies need to be passed in
|
24
|
+
# after creation time, eg when you have cyclic dependencies.
|
25
|
+
#
|
26
|
+
# This is done via specifying setter_dependencies in the same way as
|
27
|
+
# constructor_dependencies; the expectation is that after constructing an instance
|
28
|
+
# with new_from_dependencies, you must additionally fetch any setter_dependencies
|
29
|
+
# and 'inject' them into the instance via calling inject_dependency on the factory
|
30
|
+
# with the instance, dependency name and the value for that dependency.
|
31
|
+
#
|
32
|
+
# (the default implementation of inject_dependency will just call a setter method
|
33
|
+
# on the instance, eg instance.logger = logger; this will work with a private setter
|
34
|
+
# if you prefer to make private these things which are only a part of initialization)
|
35
|
+
#
|
36
|
+
# After a whole set of objects have been created and their setter_dependencies injected, it
|
37
|
+
# can be handy for them to get a notification along the lines of "hey so your whole dependency
|
38
|
+
# graph is now all wired up any ready". Factory#post_initialize should send this notification
|
39
|
+
# to an object created from the factory, where it's supported by the objects you construct.
|
40
|
+
#
|
41
|
+
# (by default it will call a :post_initialize method on the instance, if this is present;
|
42
|
+
# again this can be a private method if you wish).
|
43
|
+
module Factory::Interface
|
44
|
+
# A Module or Class which all objects constructed by this factory are kind_of?.
|
45
|
+
# The more specific you are, the more use this will be when specifying requirements
|
46
|
+
# based on a Module or Class.
|
47
|
+
def provides_class
|
48
|
+
Object # not very informative by default :)
|
49
|
+
end
|
50
|
+
|
51
|
+
# List of arbitrary objects representing features provided by this factory,
|
52
|
+
# which may be compared against required features when looking for dependencies.
|
53
|
+
# Typically symbols are used.
|
54
|
+
def provides_features
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Hash of symbol argument names to Wirer::Dependency objects, representing
|
59
|
+
# dependencies that need to be passed as arguments to new_from_dependencies
|
60
|
+
def constructor_dependencies
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Hash of symbol argument names to Wirer::Dependency objects, representing
|
65
|
+
# dependencies which need to be injected into instances *after* they have
|
66
|
+
# been constructed via new_from_dependencies
|
67
|
+
#
|
68
|
+
# if no instance is passed, should return a hash of any setter dependencies
|
69
|
+
# applying to all instances constructed from this factory. (which may be none)
|
70
|
+
#
|
71
|
+
# if an instance is passed, it may add to this hash any additional setter dependencies
|
72
|
+
# which are specific to this instance. This is useful when you have some extra set
|
73
|
+
# of dependencies which varies depending on the parameters to the constructor.
|
74
|
+
def setter_dependencies(instance=nil)
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Will be passed a hash which has keys for all of the argument names specified
|
79
|
+
# in constructor_dependencies, together with values which are the constructed
|
80
|
+
# dependencies meeting the requirements of the corresponding Wirer::Depedency.
|
81
|
+
#
|
82
|
+
# May also be passed additional non-dependency arguments supplied directly
|
83
|
+
# to the factory.
|
84
|
+
#
|
85
|
+
# The following must hold:
|
86
|
+
# factory.new_from_dependencies(dependencies, ...).is_a?(factory.provides_class)
|
87
|
+
#
|
88
|
+
# however the following is not required to hold:
|
89
|
+
# factory.new_from_dependencies(dependencies, ...).instance_of?(factory.provides_class)
|
90
|
+
def new_from_dependencies(dependencies={}, *other_args, &block_arg)
|
91
|
+
raise NotImplementedError
|
92
|
+
end
|
93
|
+
|
94
|
+
# Supplies a wrapper around the factory with a set of pre-supplied dependencies.
|
95
|
+
# The wrapper can then be used to construct instances.
|
96
|
+
# See Factory::CurriedDependencies
|
97
|
+
def curry_with_dependencies(dependencies)
|
98
|
+
Factory::CurriedDependencies.new(self, dependencies)
|
99
|
+
end
|
100
|
+
|
101
|
+
def inject_dependency(instance, attr_name, dependency)
|
102
|
+
instance.send(:"#{attr_name}=", dependency)
|
103
|
+
end
|
104
|
+
|
105
|
+
def post_initialize(instance)
|
106
|
+
instance.send(:post_initialize) if instance.respond_to?(:post_initialize, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def wrapped_with(additional_options={}, &wrapped_constructor_block)
|
110
|
+
Factory::Wrapped.new(self, additional_options, &wrapped_constructor_block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|