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.
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
+