conject 0.0.1
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/.gitignore +4 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/NOTES.txt +61 -0
- data/README.md +8 -0
- data/Rakefile +12 -0
- data/TODO +9 -0
- data/conject.gemspec +21 -0
- data/lib/conject.rb +40 -0
- data/lib/conject/borrowed_active_support_inflector.rb +525 -0
- data/lib/conject/class_ext_construct_with.rb +123 -0
- data/lib/conject/class_finder.rb +11 -0
- data/lib/conject/composition_error.rb +33 -0
- data/lib/conject/dependency_resolver.rb +16 -0
- data/lib/conject/extended_metaid.rb +33 -0
- data/lib/conject/object_context.rb +61 -0
- data/lib/conject/object_definition.rb +10 -0
- data/lib/conject/object_factory.rb +28 -0
- data/lib/conject/utilities.rb +8 -0
- data/lib/conject/version.rb +3 -0
- data/rake_tasks/rspec.rake +25 -0
- data/spec/acceptance/dev/README +7 -0
- data/spec/acceptance/regression/README +12 -0
- data/spec/acceptance/regression/basic_composition_spec.rb +29 -0
- data/spec/acceptance/regression/basic_object_creation_spec.rb +42 -0
- data/spec/acceptance/regression/nested_contexts_spec.rb +86 -0
- data/spec/conject/borrowed_active_support_inflector_spec.rb +28 -0
- data/spec/conject/class_ext_construct_with_spec.rb +226 -0
- data/spec/conject/class_finder_spec.rb +36 -0
- data/spec/conject/composition_error_spec.rb +124 -0
- data/spec/conject/dependency_resolver_spec.rb +32 -0
- data/spec/conject/extended_metaid_spec.rb +90 -0
- data/spec/conject/object_context_spec.rb +186 -0
- data/spec/conject/object_definition_spec.rb +31 -0
- data/spec/conject/object_factory_spec.rb +89 -0
- data/spec/conject/utilities_spec.rb +30 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/SPEC_HELPERS_GO_HERE +0 -0
- data/spec/support/load_path_helpers.rb +27 -0
- data/spec/test_data/basic_composition/fence.rb +5 -0
- data/spec/test_data/basic_composition/front_desk.rb +2 -0
- data/spec/test_data/basic_composition/grass.rb +2 -0
- data/spec/test_data/basic_composition/guest.rb +5 -0
- data/spec/test_data/basic_composition/lobby.rb +5 -0
- data/spec/test_data/basic_composition/nails.rb +2 -0
- data/spec/test_data/basic_composition/tv.rb +2 -0
- data/spec/test_data/basic_composition/wood.rb +2 -0
- data/spec/test_data/simple_stuff/some_random_class.rb +2 -0
- data/spike/arity_funny_business_in_different_ruby_versions.rb +34 -0
- data/spike/depends_on_spike.rb +146 -0
- data/spike/donkey_fail.rb +48 -0
- data/spike/donkey_journey.rb +50 -0
- data/spike/go.rb +11 -0
- data/spike/metaid.rb +28 -0
- data/spike/object_definition.rb +125 -0
- data/spike/sample.rb +125 -0
- data/src/user_model.rb +4 -0
- data/src/user_presenter.rb +10 -0
- data/src/user_view.rb +3 -0
- metadata +165 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'simplecov' # SimpleCov must come first!
|
2
|
+
# This start/config code could alternatively go in .simplecov in project root:
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "/spec/"
|
5
|
+
add_filter "/borrowed_active_support_inflector.rb" # We stole it precious, our specs only hit about 47% of the functions, just to prove we have this library ready to go
|
6
|
+
end
|
7
|
+
|
8
|
+
PROJECT_ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
|
9
|
+
|
10
|
+
$LOAD_PATH << "#{PROJECT_ROOT}/lib"
|
11
|
+
$LOAD_PATH << "#{PROJECT_ROOT}/spec/samples"
|
12
|
+
ENV["APP_ENV"] = "rspec"
|
13
|
+
|
14
|
+
require 'conject'
|
15
|
+
|
16
|
+
# Load all support files
|
17
|
+
Dir["#{PROJECT_ROOT}/spec/support/*.rb"].each do |support|
|
18
|
+
require support
|
19
|
+
end
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.include LoadPathHelpers
|
23
|
+
end
|
24
|
+
|
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module LoadPathHelpers
|
3
|
+
def test_data_path(rel_path='')
|
4
|
+
"#{PROJECT_ROOT}/spec/test_data/#{rel_path}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def append_load_path(path)
|
8
|
+
@appended_load_paths ||= []
|
9
|
+
@appended_load_paths << path
|
10
|
+
$LOAD_PATH << path
|
11
|
+
end
|
12
|
+
|
13
|
+
def append_test_load_path(path)
|
14
|
+
append_load_path test_data_path(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def restore_load_path
|
18
|
+
(@appended_load_paths || []).each do |path|
|
19
|
+
remove_load_path path
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_load_path(path)
|
24
|
+
$LOAD_PATH.delete path
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
$: << "../lib"
|
2
|
+
require 'object_context'
|
3
|
+
require 'object_context/utilities'
|
4
|
+
|
5
|
+
class A
|
6
|
+
end
|
7
|
+
|
8
|
+
class B
|
9
|
+
def initialize
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class C
|
14
|
+
def initialize(*x)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class D
|
19
|
+
def initialize(x)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
#
|
25
|
+
# RUBY 1.9.2 says Object#initialize is arity -1
|
26
|
+
# RUBY 1.9.3 (and 1.8.7??) say it's 0 (as expected)
|
27
|
+
# This discrepancy is being handled by ObjectContext::Utilities.has_zero_arg_constructor?
|
28
|
+
#
|
29
|
+
puts "RUBY_PLATFORM: #{RUBY_PLATFORM}"
|
30
|
+
puts "RUBY_VERSION: #{RUBY_VERSION}"
|
31
|
+
[A,B,C,D].each do |c|
|
32
|
+
puts "Class #{c}#initialize arity: #{c.instance_method(:initialize).arity}"
|
33
|
+
puts "Class #{c} has_zero_arg_constructor?: #{ObjectContext::Utilities.has_zero_arg_constructor?(c)}"
|
34
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
$: << "."
|
2
|
+
require 'metaid'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class ObjectDefinition
|
6
|
+
attr_reader :component_names, :owner, :construction_type, :singleton
|
7
|
+
|
8
|
+
def initialize(opts={})
|
9
|
+
@owner = opts[:owner]
|
10
|
+
@component_names = opts[:component_names] || []
|
11
|
+
@construction_type = :instance
|
12
|
+
@singleton = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class Class
|
18
|
+
|
19
|
+
def depends_on(*syms)
|
20
|
+
#puts "Class #{name} wishes to be constructed with #{syms.inspect}"
|
21
|
+
|
22
|
+
klass = self
|
23
|
+
|
24
|
+
object_def = ObjectDefinition.new(:owner => klass, :component_names => syms)
|
25
|
+
klass.meta_def :object_definition do
|
26
|
+
object_def
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
klass.class_def_private :components do
|
31
|
+
@_components ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
syms.each do |object_name|
|
35
|
+
class_def_private object_name do
|
36
|
+
components[object_name]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
klass.class_def_private :set_components do |component_map|
|
41
|
+
required = object_def.component_names.to_set
|
42
|
+
provided = component_map.keys.to_set
|
43
|
+
if required != provided
|
44
|
+
msg = "Wrong components when building new #{object_def.owner}:"
|
45
|
+
|
46
|
+
missing = required - provided
|
47
|
+
msg << "Required objects not provided: #{missing.to_a.inspect}" unless missing.empty?
|
48
|
+
|
49
|
+
unexpected = provided - required
|
50
|
+
msg << "Unexpected objects: #{unexpected.to_a.inspect}" unless unexpected.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
components.clear.merge! component_map
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
# Tidbits of state that our dynamically-defined functions herein
|
58
|
+
# will close over.
|
59
|
+
object_context_prep = {
|
60
|
+
:initialize_has_been_wrapped => false, # keep track of when a class's :initialize method has been wrapped
|
61
|
+
}
|
62
|
+
# klass.meta_def :object_context_prep do
|
63
|
+
# @_object_context_prep ||= {
|
64
|
+
# :initialize_has_been_wrapped => false
|
65
|
+
# }
|
66
|
+
# end
|
67
|
+
|
68
|
+
# Alias :new such that we can wrap and invoke it later
|
69
|
+
klass.meta_eval do
|
70
|
+
alias_method :actual_new, :new
|
71
|
+
end
|
72
|
+
|
73
|
+
# Override default :new behavior for this class:
|
74
|
+
klass.meta_def :new do |component_map|
|
75
|
+
|
76
|
+
# We only want to do the following one time, but we've waited until now
|
77
|
+
# in order to make sure our metaprogramming didn't get ahead of the user's
|
78
|
+
# own definition of initialize:
|
79
|
+
unless object_context_prep[:initialize_has_been_wrapped]
|
80
|
+
puts " (wrapping :initialize, should only happen once)"
|
81
|
+
|
82
|
+
# Define a new wrapper'd version of initialize that accepts and uses a component map
|
83
|
+
alias_method :actual_initialize, :initialize
|
84
|
+
class_def :initialize do |component_map|
|
85
|
+
# Apply the given components
|
86
|
+
set_components component_map
|
87
|
+
|
88
|
+
# Invoke the normal initialize method.
|
89
|
+
# User-defined initialize method may accept 0 args, or it may accept a single arg
|
90
|
+
# which will be the component map.
|
91
|
+
arg_count = method(:actual_initialize).arity
|
92
|
+
case arg_count
|
93
|
+
when 0
|
94
|
+
actual_initialize
|
95
|
+
when 1
|
96
|
+
actual_initialize component_map
|
97
|
+
else
|
98
|
+
# We're not equipped to handle this
|
99
|
+
raise "User-defined initialize method defined with #{arg_count} paramters; must either be 0, or 1 to receive the component map."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
# Make a note that the initialize wrapper has been applied
|
103
|
+
object_context_prep[:initialize_has_been_wrapped] = true
|
104
|
+
end
|
105
|
+
|
106
|
+
# Instantiate an instance
|
107
|
+
actual_new component_map
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def has_object_definition?
|
112
|
+
respond_to?(:object_definition) and !object_definition.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
class Car
|
118
|
+
depends_on :doors
|
119
|
+
|
120
|
+
def initialize(cm)
|
121
|
+
puts "User defined initialize: Construcing a new Car #{cm}. Can talk about doors=#{doors}"
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
class Doors
|
127
|
+
def initialize
|
128
|
+
puts "Doors constructed the old fashioned way"
|
129
|
+
$stdout.flush
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
c = Car.new(:doors => "whatever")
|
134
|
+
$stdout.flush
|
135
|
+
d = Doors.new
|
136
|
+
c2 = Car.new(:doors => "more whatever")
|
137
|
+
p c
|
138
|
+
p d
|
139
|
+
p c2
|
140
|
+
#p c2.components # will fail, components should be private
|
141
|
+
puts "Doors has an object def? #{Doors.has_object_definition?}"
|
142
|
+
puts "Car has an object def? #{Car.has_object_definition?}"
|
143
|
+
p Car.object_definition
|
144
|
+
puts "Car class requires components: #{Car.object_definition.component_names}"
|
145
|
+
puts "OK! (If this line prints out then the basic premise is working, though we're not really doing anything to assign components yet)"
|
146
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
$: << "."
|
2
|
+
require 'object_definition'
|
3
|
+
|
4
|
+
class Donkey
|
5
|
+
depends_on :head, :legs
|
6
|
+
|
7
|
+
def journey
|
8
|
+
head.talk
|
9
|
+
legs.walk
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Head
|
14
|
+
depends_on :eyes, :mouth
|
15
|
+
|
16
|
+
def talk
|
17
|
+
mouth.bray
|
18
|
+
eyes.roll
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Legs
|
23
|
+
def walk
|
24
|
+
puts "Clop clop!"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Eyes
|
29
|
+
def roll
|
30
|
+
puts "(eye roll)"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Mouth
|
35
|
+
def bray
|
36
|
+
puts "HAWWW!!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
donkey = Donkey.new(
|
41
|
+
:head => Head.new(
|
42
|
+
:one => "dude",
|
43
|
+
:two => "dudes",
|
44
|
+
),
|
45
|
+
:legs => Legs.new,
|
46
|
+
)
|
47
|
+
|
48
|
+
donkey.journey
|
@@ -0,0 +1,50 @@
|
|
1
|
+
$: << "."
|
2
|
+
require 'object_definition'
|
3
|
+
|
4
|
+
class Donkey
|
5
|
+
depends_on :head, :legs
|
6
|
+
|
7
|
+
def journey
|
8
|
+
head.talk
|
9
|
+
legs.walk
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class Head
|
15
|
+
depends_on :eyes, :mouth
|
16
|
+
|
17
|
+
def talk
|
18
|
+
mouth.bray
|
19
|
+
eyes.roll
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Legs
|
24
|
+
def walk
|
25
|
+
puts "Clop clop!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Eyes
|
30
|
+
def roll
|
31
|
+
puts "(eye roll)"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Mouth
|
36
|
+
def bray
|
37
|
+
puts "HAWWW!!"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
donkey = Donkey.new(
|
42
|
+
:head => Head.new(
|
43
|
+
:mouth => Mouth.new,
|
44
|
+
:eyes => Eyes.new
|
45
|
+
),
|
46
|
+
:legs => Legs.new
|
47
|
+
)
|
48
|
+
|
49
|
+
donkey.journey
|
50
|
+
|
data/spike/go.rb
ADDED
data/spike/metaid.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Metaid == a few simple metaclass helper
|
2
|
+
# (See http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html.)
|
3
|
+
class Object
|
4
|
+
# The hidden singleton lurks behind everyone
|
5
|
+
def metaclass; class << self; self; end; end
|
6
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
7
|
+
|
8
|
+
# Adds methods to a metaclass
|
9
|
+
def meta_def name, &blk
|
10
|
+
meta_eval { define_method name, &blk }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Module
|
15
|
+
# Defines an instance method within a module
|
16
|
+
def module_def name, &blk
|
17
|
+
module_eval { define_method name, &blk }
|
18
|
+
end
|
19
|
+
def module_def_private name, &blk
|
20
|
+
module_def name, &blk
|
21
|
+
private name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Class
|
26
|
+
alias class_def module_def
|
27
|
+
alias class_def_private module_def_private
|
28
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'metaid'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
class ObjectDefinition
|
5
|
+
attr_reader :component_names, :owner, :construction_type, :singleton
|
6
|
+
|
7
|
+
def initialize(opts={})
|
8
|
+
@owner = opts[:owner]
|
9
|
+
@component_names = opts[:component_names] || []
|
10
|
+
@construction_type = :instance
|
11
|
+
@singleton = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CompositionError < RuntimeError
|
16
|
+
def initialize(opts={})
|
17
|
+
object_def = opts[:object_definition]
|
18
|
+
required = opts[:required]
|
19
|
+
provided = opts[:provided]
|
20
|
+
|
21
|
+
msg = "Wrong components when building new #{object_def.owner}: "
|
22
|
+
|
23
|
+
missing = required - provided
|
24
|
+
msg << "Missing required object(s) #{missing.to_a.inspect}. " unless missing.empty?
|
25
|
+
|
26
|
+
unexpected = provided - required
|
27
|
+
msg << "Encountered unexpected object(s) #{unexpected.to_a.inspect}. " unless unexpected.empty?
|
28
|
+
|
29
|
+
super msg
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Class
|
34
|
+
|
35
|
+
def depends_on(*syms)
|
36
|
+
klass = self
|
37
|
+
|
38
|
+
object_def = ObjectDefinition.new(:owner => klass, :component_names => syms)
|
39
|
+
klass.meta_def :object_definition do
|
40
|
+
object_def
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
klass.class_def_private :components do
|
45
|
+
@_components ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
syms.each do |object_name|
|
49
|
+
class_def_private object_name do
|
50
|
+
components[object_name]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
klass.class_def_private :set_components do |component_map|
|
55
|
+
required = object_def.component_names.to_set
|
56
|
+
provided = component_map.keys.to_set
|
57
|
+
if required != provided
|
58
|
+
raise CompositionError.new(
|
59
|
+
:object_definition => object_def,
|
60
|
+
:required => required,
|
61
|
+
:provided => provided)
|
62
|
+
end
|
63
|
+
|
64
|
+
components.clear.merge! component_map
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Tidbits of state that our dynamically-defined functions herein
|
69
|
+
# will close over.
|
70
|
+
object_context_prep = {
|
71
|
+
:initialize_has_been_wrapped => false, # keep track of when a class's :initialize method has been wrapped
|
72
|
+
}
|
73
|
+
# klass.meta_def :object_context_prep do
|
74
|
+
# @_object_context_prep ||= {
|
75
|
+
# :initialize_has_been_wrapped => false
|
76
|
+
# }
|
77
|
+
# end
|
78
|
+
|
79
|
+
# Alias :new such that we can wrap and invoke it later
|
80
|
+
klass.meta_eval do
|
81
|
+
alias_method :actual_new, :new
|
82
|
+
end
|
83
|
+
|
84
|
+
# Override default :new behavior for this class:
|
85
|
+
klass.meta_def :new do |component_map|
|
86
|
+
|
87
|
+
# We only want to do the following one time, but we've waited until now
|
88
|
+
# in order to make sure our metaprogramming didn't get ahead of the user's
|
89
|
+
# own definition of initialize:
|
90
|
+
unless object_context_prep[:initialize_has_been_wrapped]
|
91
|
+
# Define a new wrapper'd version of initialize that accepts and uses a component map
|
92
|
+
alias_method :actual_initialize, :initialize
|
93
|
+
class_def :initialize do |component_map|
|
94
|
+
# Apply the given components
|
95
|
+
set_components component_map
|
96
|
+
|
97
|
+
# Invoke the normal initialize method.
|
98
|
+
# User-defined initialize method may accept 0 args, or it may accept a single arg
|
99
|
+
# which will be the component map.
|
100
|
+
arg_count = method(:actual_initialize).arity
|
101
|
+
case arg_count
|
102
|
+
when 0
|
103
|
+
actual_initialize
|
104
|
+
when 1
|
105
|
+
actual_initialize component_map
|
106
|
+
else
|
107
|
+
# We're not equipped to handle this
|
108
|
+
raise "User-defined initialize method defined with #{arg_count} paramters; must either be 0, or 1 to receive the component map."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
# Make a note that the initialize wrapper has been applied
|
112
|
+
object_context_prep[:initialize_has_been_wrapped] = true
|
113
|
+
end
|
114
|
+
|
115
|
+
# Instantiate an instance
|
116
|
+
actual_new component_map
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def has_object_definition?
|
121
|
+
respond_to?(:object_definition) and !object_definition.nil?
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|