conject 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +32 -0
  5. data/NOTES.txt +61 -0
  6. data/README.md +8 -0
  7. data/Rakefile +12 -0
  8. data/TODO +9 -0
  9. data/conject.gemspec +21 -0
  10. data/lib/conject.rb +40 -0
  11. data/lib/conject/borrowed_active_support_inflector.rb +525 -0
  12. data/lib/conject/class_ext_construct_with.rb +123 -0
  13. data/lib/conject/class_finder.rb +11 -0
  14. data/lib/conject/composition_error.rb +33 -0
  15. data/lib/conject/dependency_resolver.rb +16 -0
  16. data/lib/conject/extended_metaid.rb +33 -0
  17. data/lib/conject/object_context.rb +61 -0
  18. data/lib/conject/object_definition.rb +10 -0
  19. data/lib/conject/object_factory.rb +28 -0
  20. data/lib/conject/utilities.rb +8 -0
  21. data/lib/conject/version.rb +3 -0
  22. data/rake_tasks/rspec.rake +25 -0
  23. data/spec/acceptance/dev/README +7 -0
  24. data/spec/acceptance/regression/README +12 -0
  25. data/spec/acceptance/regression/basic_composition_spec.rb +29 -0
  26. data/spec/acceptance/regression/basic_object_creation_spec.rb +42 -0
  27. data/spec/acceptance/regression/nested_contexts_spec.rb +86 -0
  28. data/spec/conject/borrowed_active_support_inflector_spec.rb +28 -0
  29. data/spec/conject/class_ext_construct_with_spec.rb +226 -0
  30. data/spec/conject/class_finder_spec.rb +36 -0
  31. data/spec/conject/composition_error_spec.rb +124 -0
  32. data/spec/conject/dependency_resolver_spec.rb +32 -0
  33. data/spec/conject/extended_metaid_spec.rb +90 -0
  34. data/spec/conject/object_context_spec.rb +186 -0
  35. data/spec/conject/object_definition_spec.rb +31 -0
  36. data/spec/conject/object_factory_spec.rb +89 -0
  37. data/spec/conject/utilities_spec.rb +30 -0
  38. data/spec/spec_helper.rb +24 -0
  39. data/spec/support/SPEC_HELPERS_GO_HERE +0 -0
  40. data/spec/support/load_path_helpers.rb +27 -0
  41. data/spec/test_data/basic_composition/fence.rb +5 -0
  42. data/spec/test_data/basic_composition/front_desk.rb +2 -0
  43. data/spec/test_data/basic_composition/grass.rb +2 -0
  44. data/spec/test_data/basic_composition/guest.rb +5 -0
  45. data/spec/test_data/basic_composition/lobby.rb +5 -0
  46. data/spec/test_data/basic_composition/nails.rb +2 -0
  47. data/spec/test_data/basic_composition/tv.rb +2 -0
  48. data/spec/test_data/basic_composition/wood.rb +2 -0
  49. data/spec/test_data/simple_stuff/some_random_class.rb +2 -0
  50. data/spike/arity_funny_business_in_different_ruby_versions.rb +34 -0
  51. data/spike/depends_on_spike.rb +146 -0
  52. data/spike/donkey_fail.rb +48 -0
  53. data/spike/donkey_journey.rb +50 -0
  54. data/spike/go.rb +11 -0
  55. data/spike/metaid.rb +28 -0
  56. data/spike/object_definition.rb +125 -0
  57. data/spike/sample.rb +125 -0
  58. data/src/user_model.rb +4 -0
  59. data/src/user_presenter.rb +10 -0
  60. data/src/user_view.rb +3 -0
  61. metadata +165 -0
@@ -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,5 @@
1
+ class Fence
2
+ construct_with :wood, :nails
3
+
4
+ public :wood, :nails
5
+ end
@@ -0,0 +1,2 @@
1
+ class FrontDesk
2
+ end
@@ -0,0 +1,2 @@
1
+ class Grass
2
+ end
@@ -0,0 +1,5 @@
1
+ class Guest
2
+ construct_with :tv, :front_desk
3
+
4
+ public :tv, :front_desk
5
+ end
@@ -0,0 +1,5 @@
1
+ class Lobby
2
+ construct_with :front_desk
3
+
4
+ public :front_desk
5
+ end
@@ -0,0 +1,2 @@
1
+ class Nails
2
+ end
@@ -0,0 +1,2 @@
1
+ class Tv
2
+ end
@@ -0,0 +1,2 @@
1
+ class Wood
2
+ end
@@ -0,0 +1,2 @@
1
+ class SomeRandomClass
2
+ end
@@ -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
+
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH << "lib"
2
+ $LOAD_PATH << "src"
3
+
4
+ require 'ruice'
5
+
6
+ presenter = Ruice[:user_presenter]
7
+ model = Ruice[:user_model]
8
+ view = Ruice[:user_view]
9
+
10
+ model.user = { :name => "Dave" }
11
+ puts "view.name=#{view.name}"
@@ -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
+