seat-belt 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -0
  3. data/.travis.yml +6 -0
  4. data/Changelog.md +55 -0
  5. data/Gemfile +15 -0
  6. data/Guardfile +13 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +705 -0
  9. data/Rakefile +1 -0
  10. data/ext/.gitkeep +0 -0
  11. data/lib/seatbelt.rb +37 -0
  12. data/lib/seatbelt/collections/collection.rb +56 -0
  13. data/lib/seatbelt/core.rb +15 -0
  14. data/lib/seatbelt/core/callee.rb +35 -0
  15. data/lib/seatbelt/core/eigenmethod.rb +150 -0
  16. data/lib/seatbelt/core/eigenmethod_proxy.rb +45 -0
  17. data/lib/seatbelt/core/ext/core_ext.rb +0 -0
  18. data/lib/seatbelt/core/gate.rb +198 -0
  19. data/lib/seatbelt/core/ghost_tunnel.rb +81 -0
  20. data/lib/seatbelt/core/implementation.rb +158 -0
  21. data/lib/seatbelt/core/interface.rb +51 -0
  22. data/lib/seatbelt/core/iterators/method_config.rb +50 -0
  23. data/lib/seatbelt/core/lookup_table.rb +101 -0
  24. data/lib/seatbelt/core/pool.rb +90 -0
  25. data/lib/seatbelt/core/property.rb +161 -0
  26. data/lib/seatbelt/core/proxy.rb +135 -0
  27. data/lib/seatbelt/core/synthesizeable.rb +50 -0
  28. data/lib/seatbelt/core/terminal.rb +59 -0
  29. data/lib/seatbelt/dependencies.rb +5 -0
  30. data/lib/seatbelt/document.rb +175 -0
  31. data/lib/seatbelt/errors.rb +1 -0
  32. data/lib/seatbelt/errors/errors.rb +150 -0
  33. data/lib/seatbelt/gate_config.rb +59 -0
  34. data/lib/seatbelt/ghost.rb +140 -0
  35. data/lib/seatbelt/models.rb +9 -0
  36. data/lib/seatbelt/seatbelt.rb +10 -0
  37. data/lib/seatbelt/synthesizer.rb +3 -0
  38. data/lib/seatbelt/synthesizers/document.rb +16 -0
  39. data/lib/seatbelt/synthesizers/mongoid.rb +16 -0
  40. data/lib/seatbelt/synthesizers/synthesizer.rb +146 -0
  41. data/lib/seatbelt/tape.rb +2 -0
  42. data/lib/seatbelt/tape_deck.rb +71 -0
  43. data/lib/seatbelt/tapes/tape.rb +105 -0
  44. data/lib/seatbelt/tapes/util/delegate.rb +56 -0
  45. data/lib/seatbelt/translator.rb +66 -0
  46. data/lib/seatbelt/version.rb +3 -0
  47. data/seatbelt.gemspec +27 -0
  48. data/spec/lib/seatbelt/core/eigenmethod_spec.rb +102 -0
  49. data/spec/lib/seatbelt/core/gate_spec.rb +521 -0
  50. data/spec/lib/seatbelt/core/ghost_tunnel_spec.rb +21 -0
  51. data/spec/lib/seatbelt/core/lookup_table_spec.rb +234 -0
  52. data/spec/lib/seatbelt/core/pool_spec.rb +270 -0
  53. data/spec/lib/seatbelt/core/proxy_spec.rb +108 -0
  54. data/spec/lib/seatbelt/core/terminal_spec.rb +184 -0
  55. data/spec/lib/seatbelt/document_spec.rb +287 -0
  56. data/spec/lib/seatbelt/gate_config_spec.rb +98 -0
  57. data/spec/lib/seatbelt/ghost_spec.rb +568 -0
  58. data/spec/lib/seatbelt/synthesizers/document_spec.rb +47 -0
  59. data/spec/lib/seatbelt/synthesizers/mongoid_spec.rb +134 -0
  60. data/spec/lib/seatbelt/synthesizers/synthesizer_spec.rb +112 -0
  61. data/spec/lib/seatbelt/tape_deck_spec.rb +180 -0
  62. data/spec/lib/seatbelt/tapes/tape_spec.rb +115 -0
  63. data/spec/lib/seatbelt/translator_spec.rb +108 -0
  64. data/spec/spec_helper.rb +16 -0
  65. data/spec/support/implementations/seatbelt_environment.rb +19 -0
  66. data/spec/support/shared_examples/shared_api_class.rb +7 -0
  67. data/spec/support/shared_examples/shared_collection_child.rb +7 -0
  68. data/spec/support/worlds/eigenmethod_world.rb +7 -0
  69. metadata +205 -0
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/ext/.gitkeep ADDED
File without changes
data/lib/seatbelt.rb ADDED
@@ -0,0 +1,37 @@
1
+ require "seatbelt/dependencies"
2
+ require "seatbelt/version"
3
+ require "seatbelt/seatbelt"
4
+
5
+ module Seatbelt
6
+
7
+ METHOD_EXCLUDE_DIRECTIVES = %w{ :: }
8
+
9
+ # Internal: Checks a directive if allowed. Raises a DirectiveNotAllowedError
10
+ # if directive is not allowed.
11
+ #
12
+ # directive - the directive String to check.
13
+ #
14
+ # Returns nothing
15
+ def self.check_directive(directive)
16
+ if directive.in?(METHOD_EXCLUDE_DIRECTIVES)
17
+ raise Seatbelt::Errors::DirectiveNotAllowedError
18
+ end
19
+ end
20
+
21
+
22
+ # Public: Configures the Gate. Needs a block.
23
+ #
24
+ # Example:
25
+ #
26
+ # Seatbelt.configure_gate do |config|
27
+ # config.method_directive_instance = "|"
28
+ # config.method_directive_class = "~"
29
+ # end
30
+ #
31
+ # Returns nothing.
32
+ def self.configure_gate
33
+ yield GateConfig if block_given?
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,56 @@
1
+ module Seatbelt
2
+ module Collections
3
+
4
+ # Public: The datastructure used as primitive in
5
+ # Seatbelt::Collections::Collection
6
+ #
7
+ class Array < ::Array; end
8
+
9
+ # Public: The base collection attribute type that is used in 'has_many'
10
+ # associations.
11
+ #
12
+ # Needs a corrosponding model.
13
+ # See Seatbelt::Dcoument::Associations for further details.
14
+ class Collection < Array#Virtus::Attribute::Collection
15
+
16
+ # Public: Initializes the collections primitive to allow later type
17
+ # checking.Adds a method 'acceptable_item_name' to the primitive, that
18
+ # is later called at the time of type checking to match the allowed class.
19
+ #
20
+ # klass - The associatzed klass that is insertable into the collection.
21
+ #
22
+ def self.initialize_primitive(klass)
23
+ class_eval <<-RUBY, __FILE__, __LINE__
24
+ def acceptable_item_name
25
+ return "#{klass.name}"
26
+ end
27
+ RUBY
28
+ end
29
+
30
+ # Public: Adds an object to the 'has_many' association.
31
+ #
32
+ # item - An instance of the model class used within the collection
33
+ # or an Hash with attribute key/value pairs.
34
+ #
35
+ # Example
36
+ # region.hotels << {:name => "Radisson London Stansted"}
37
+ # region.hotels << super_hotel_in_dubai
38
+ #
39
+ # Raises Seatbelt::Errors::TypeMissmatchError if unexpected object
40
+ # is passed.
41
+ def <<(item)
42
+ case item.class.name
43
+ when acceptable_item_name then super(item)
44
+ when "Hash" then
45
+ begin
46
+ super(Module.const_get(acceptable_item_name).new(item))
47
+ end
48
+ else
49
+ raise Seatbelt::Errors::TypeMissmatchError.
50
+ new(acceptable_item_name,
51
+ item.class.name)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ require 'seatbelt/core/lookup_table'
2
+ require 'seatbelt/core/pool'
3
+ require 'seatbelt/core/property'
4
+ require 'seatbelt/core/interface'
5
+ require 'seatbelt/core/ghost_tunnel'
6
+ require 'seatbelt/core/synthesizeable'
7
+ require 'seatbelt/core/implementation'
8
+ require 'seatbelt/core/gate'
9
+ require 'seatbelt/core/eigenmethod'
10
+ require 'seatbelt/core/eigenmethod_proxy'
11
+ require 'seatbelt/core/terminal'
12
+ require 'seatbelt/core/callee'
13
+ require 'seatbelt/core/proxy'
14
+ require 'seatbelt/core/ext/core_ext'
15
+ require 'seatbelt/core/iterators/method_config'
@@ -0,0 +1,35 @@
1
+ module Seatbelt
2
+
3
+ # Public: Handles passing the call of API Methods to the Terminal.
4
+ module Callee
5
+ extend self
6
+
7
+ # Public: Handles the calling of a API method (instance and class scope).
8
+ #
9
+ # klass - The class or instance of the class containing the API method
10
+ # options - A options Hash to refine required values
11
+ # :lookup_tbl - The class lookup table instance.
12
+ # :scope - The scope the API method should be called on.
13
+ # :method_name - The API method's name
14
+ # *args - argument list of the API method
15
+ # &block - An optional block passed to the API method
16
+ #
17
+ # Returns the return value of the API methods implementation method if
18
+ # the method configuration was found at the lookup table.
19
+ def handle(klass, options, *args, &block)
20
+ lookup_tbl = options[:lookup_tbl]
21
+ scope = options[:scope]
22
+ method_name = options[:method_name]
23
+
24
+ api_method_config = lookup_tbl.
25
+ find_method(method_name, scope: scope)
26
+ arity = api_method_config[method_name][:arity]
27
+ if api_method_config[method_name][:block_required]
28
+ if block.nil?
29
+ raise Seatbelt::Errors::ApiMethodBlockRequiredError
30
+ end
31
+ end
32
+ Seatbelt::Terminal.call(method_name, klass, arity, *args, &block)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,150 @@
1
+ module Seatbelt
2
+
3
+ # Public: A configuration class that contains the implementation method
4
+ # directives and attributes.
5
+ class Eigenmethod
6
+
7
+ attr_accessor :scope_level,
8
+ :namespace,
9
+ :implemented_as,
10
+ :method,
11
+ :method_implementation_type,
12
+ :arity,
13
+ :delegated
14
+
15
+ attr_writer :receiver
16
+
17
+
18
+ # Public: The receiver of the implementation method. This is always an
19
+ # instance whether it defines a class method or instance method
20
+ # implementation.
21
+ #
22
+ # Returns receivers instance.
23
+ def receiver
24
+ @receiver
25
+ end
26
+
27
+
28
+ # Implementation type at remote class (API class) side.
29
+ #
30
+ # Returns true if an instance method is implemented, otherwise false.
31
+ def instance_level?
32
+ self.scope_level.eql?(:instance)
33
+ end
34
+
35
+ # Implementation type at remote class (API class) side.
36
+ #
37
+ # Returns true if a class method is implemented, otherwise false.
38
+ def class_level?
39
+ not self.instance_level?
40
+ end
41
+
42
+ # Implementation type at the implementation class side.
43
+ #
44
+ # Returns true if a class method is implemented, otherwise false.
45
+ def class_method_implementation?
46
+ self.method_implementation_type.eql?(:class)
47
+ end
48
+
49
+ # Implementation type at the implementation class side.
50
+ #
51
+ # Returns true if an instance method is implemented, otherwise false.
52
+ def instance_method_implementation?
53
+ not class_method_implementation?
54
+ end
55
+
56
+
57
+ # Public: Calls the implementation method of an API method call.
58
+ #
59
+ # *args - argument list for the implementation method.
60
+ # &block - A block if needed.
61
+ #
62
+ # Returns the evaluated value.
63
+ def call(*args, &block)
64
+ return __send_class_level(*args, &block) if class_level?
65
+ return __send_instance_level(*args, &block) if instance_level?
66
+ end
67
+
68
+ def [](attr)
69
+ self.send(attr)
70
+ end
71
+
72
+ alias_method :has_key?, :respond_to?
73
+ alias_method :scope, :scope_level
74
+ alias_method :scope=, :scope_level=
75
+
76
+
77
+ # Creates the corrosponding class on the receiver and defines the proxy
78
+ # tunnel on the receivers proxy object.
79
+ #
80
+ # klass_object - The API Class or an instance of the API class.
81
+ #
82
+ def init_klass_on_receiver(klass_object)
83
+ if instance_level?
84
+ __generate_proxy_object(@callee, klass_object)
85
+ end
86
+ if class_level?
87
+ if class_method_implementation?
88
+ __generate_proxy_object(receiver, klass_object)
89
+ else
90
+ if method_implementation_type.eql?(:instance)
91
+ @callee = callee.new if @callee.respond_to?(:new)
92
+ @callee.instance_variable_set(:@proxy,Seatbelt::Proxy.new)
93
+ __generate_proxy_object(callee, klass_object)
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ # Accessor of the api method implementation call object.
100
+ def callee
101
+ @callee
102
+ end
103
+
104
+ def __generate_proxy_object(receiver_, klass_object)
105
+ runner = lambda do |receiver_scope|
106
+ receiver_scope.send(:proxy_object).instance_variable_set(:@klass, klass_object)
107
+ receiver_scope.send(:proxy_object).class_eval code
108
+ receiver_scope.class.send(:define_method, :proxy) do
109
+ return self.proxy_object
110
+ end
111
+ #p receiver_scope.send(:proxy_object)
112
+ return receiver_scope
113
+ end
114
+ runner.call(receiver_)
115
+ end
116
+
117
+ def code
118
+ <<-RUBY
119
+ def klass
120
+ return instance_variable_get(:@klass)
121
+ end
122
+ private :klass
123
+ RUBY
124
+ end
125
+
126
+ def __send_class_level(*args, &block)
127
+ if class_method_implementation?
128
+ object = receiver
129
+ else
130
+ object = callee
131
+ end
132
+ object.send(@method, *args, &block)
133
+ end
134
+
135
+ def __send_instance_level(*args, &block)
136
+ if method.respond_to?(:bind)
137
+ value = method.bind(callee).call(*args, &block)
138
+ else
139
+ value = callee.send(method, *args, &block)
140
+ end
141
+ end
142
+
143
+ private :__send_instance_level,
144
+ :__send_class_level,
145
+ :__generate_proxy_object,
146
+ :code,
147
+ :callee
148
+
149
+ end
150
+ end
@@ -0,0 +1,45 @@
1
+ module Seatbelt
2
+ module EigenmethodProxy
3
+ extend self
4
+
5
+ # Public: Creates or duplicates a new eigenmethod object. Creates the proxy
6
+ # object on the Eigenmethod and adds this eigenmethod object to
7
+ # Terminal#luggage or just returns the duplicated or thenewly created object
8
+ #
9
+ # *args - An argument list consisting of:
10
+ # proxy - A Seatbelt::Proxy instance
11
+ # options - An options hash
12
+ # :eigenmethod - the eigenmethod to duplicate
13
+ # :object - the object the eigenmethod should be bind to
14
+ # :receiver - An instance or class representation of the
15
+ # implementation class
16
+ # :add_to - Eigenmethod should added to Terminal#luggage
17
+ # (defaults to true)
18
+ # :return method - Eigenmethod should be returned
19
+ #
20
+ def set(*args)
21
+ options = args.extract_options!
22
+ proxy = args.pop
23
+ eigenmethod = options[:eigenmethod]
24
+ obj = options[:object]
25
+ receiver = options[:receiver]
26
+ index = Terminal.luggage.index(eigenmethod)
27
+ add_to_luggage = options.fetch(:add_to, true)
28
+ return_method = options.fetch(:return_method, false)
29
+
30
+ new_eigenmethod = eigenmethod.dup
31
+
32
+ ivar_callee = receiver
33
+ ivar_callee.instance_variable_set(:@proxy, proxy)
34
+ new_eigenmethod.instance_variable_set(:@callee,ivar_callee)
35
+ new_eigenmethod.init_klass_on_receiver(obj) if obj
36
+
37
+ if add_to_luggage
38
+ Terminal.luggage.delete(eigenmethod)
39
+ Terminal.luggage << new_eigenmethod
40
+ end
41
+
42
+ return new_eigenmethod if return_method
43
+ end
44
+ end
45
+ end
File without changes
@@ -0,0 +1,198 @@
1
+ module Seatbelt
2
+
3
+ # Public: Module that provides method forward declaration named :implement.
4
+ #
5
+ module Gate
6
+
7
+ def self.included(base)
8
+ base.class_eval do
9
+ include Proxy
10
+ extend ImplementationCallee
11
+ extend Synthesizeable
12
+ extend ClassMethods
13
+ private_class_method :implementation_methods,
14
+ :eigenmethods_class_level,
15
+ :initialize_implementation_method,
16
+ :implementation_from_superclass
17
+ end
18
+ end
19
+
20
+ module Proxy
21
+
22
+ # Public: Access the implementation class Proxy class instance.
23
+ # If there is no Proxy class instance defined, a new instance will
24
+ # be initialized.
25
+ #
26
+ # Returns the proxy class instance.
27
+ def proxy_object
28
+ @proxy
29
+ end
30
+
31
+ end
32
+
33
+ module ImplementationCallee
34
+ def mark_as_instance_implementation
35
+ lambda do |implementation_method, name|
36
+ config = implementation_method.values.pop
37
+ method = self.instance_method(name).bind(self.new)
38
+ config[:method] = method
39
+ implement(name, config)
40
+ end
41
+ end
42
+
43
+ def mark_as_class_implementation
44
+ lambda do |implementation_method, name|
45
+ config = implementation_method.values.pop
46
+ method = name.to_sym#bind(self)
47
+ config[:method] = method
48
+ config[:type] = :class
49
+ implement(name, config)
50
+ end
51
+ end
52
+ end
53
+
54
+ module ClassMethods
55
+ include Gate::Proxy
56
+ include Gate::Implementation
57
+ # Internal: Collection of implementation methods configurations defined
58
+ # with the #implement_class directive.
59
+ def implementation_methods
60
+ @impl_methods ||= []
61
+ end
62
+
63
+ def implementation_class_methods
64
+ @implementation_class_methods ||= []
65
+ end
66
+
67
+ def singleton_method_added(name)
68
+ implementation_method = implementation_class_methods.detect do |method_config|
69
+ name.in?(method_config.keys)
70
+ end
71
+ if implementation_method
72
+ mark_as_class_implementation.call(implementation_method, name)
73
+ end
74
+ end
75
+
76
+ # Public: Ruby hook to check if a method that is added to the class was
77
+ # defined with the #implement_class directive. If so, it calls the
78
+ # #implement method. For further information see their method documention.
79
+ #
80
+ # name - the method name as Symbol
81
+ def method_added(name)
82
+ initialize_implementation_method(name)
83
+ end
84
+
85
+ def initialize_implementation_method(name)
86
+ implementation_method = implementation_methods.detect do |method_config|
87
+ name.in?(method_config.keys)
88
+ end
89
+ if implementation_method
90
+ mark_as_instance_implementation.call(implementation_method, name)
91
+ end
92
+ end
93
+
94
+ def implementation_from_superclass(superclass_method, config)
95
+ method = self.superclass.instance_method(superclass_method).bind(self.new)
96
+ config[:method] = method
97
+ implement(superclass_method,config)
98
+ end
99
+
100
+ # Public: Adds a method forward declaration object to a method config
101
+ # stack in Seatbelt::Terminal#luggage.
102
+ #
103
+ # *args - An argument list containing:
104
+ # * method - The method name that should be forwarded (or marked
105
+ # as implementation of a remote method)
106
+ # * options - A options Hash to configure to which method the
107
+ # implementation should be forwarded:
108
+ # :as - A String containing the remote class'
109
+ # namespace followed by a method directive
110
+ # identifier and the remote method name
111
+ #
112
+ # Example:
113
+ #
114
+ # implement :support_agility, :as => "Rails::Conference#workshop"
115
+ #
116
+ # Forwards the implementation method support_agility to the instance
117
+ # method #workshop of the class Conference within the module Rails.
118
+ #
119
+ # implement :check_speakers, :as => "Rails::Conference.invite_speakers"
120
+ #
121
+ # Forwards the implementation method :check_speakers to the class method
122
+ # #invite_speakers of the class Conference within the module Rails.
123
+ #
124
+ # This will be private in Seatbelt 1.0.
125
+ def implement(*args)
126
+ options = args.extract_options!
127
+ method = args.pop
128
+ remote_method = options[:as]
129
+ delegated_method = options.fetch(:delegated,false)
130
+
131
+ method_scope = :class if remote_method.include?(".")
132
+ method_scope = :instance if remote_method.include?("#")
133
+ directive = Seatbelt::GateConfig.method_directives[method_scope]
134
+ scope_chain = remote_method.split(directive)
135
+ remote_method = scope_chain.pop.to_sym
136
+
137
+ namespace = scope_chain.shift
138
+ type = options.fetch(:type, :instance)
139
+
140
+ receiver = self
141
+
142
+ method_proxy = Seatbelt::Eigenmethod.new
143
+ method_proxy.method = method
144
+ method_proxy.scope_level = method_scope
145
+ method_proxy.namespace = namespace
146
+ method_proxy.implemented_as = remote_method
147
+ method_proxy.receiver = receiver
148
+ method_proxy.method_implementation_type = type
149
+ method_proxy.delegated = delegated_method
150
+ #method_proxy.arity = method.arity
151
+
152
+
153
+
154
+ if method_scope.eql?(:instance)
155
+ method = instance_method(method)
156
+ method_proxy.arity = method.arity
157
+ else
158
+ method_proxy = eigenmethods_class_level(namespace,method_proxy)
159
+ end
160
+ Terminal.luggage << method_proxy
161
+ end
162
+
163
+ # Internal: Defines the Eigenclass method proxy object.
164
+ #
165
+ # This is only called if an implementation class implements a class
166
+ # method of its API class. Adds the method proxy to the eigenclass
167
+ # eigenmethods bucket.
168
+ #
169
+ # namespace - The API class name
170
+ # method_proxy - The MethodProxy skeleton created by #implement
171
+ #
172
+ # Returns the method proxy.
173
+ def eigenmethods_class_level(namespace,method_proxy)
174
+ receiver = self
175
+ options = { :eigenmethod => method_proxy,
176
+ :receiver => receiver,
177
+ :add_to => false,
178
+ :return_method => true
179
+ }
180
+ proxy = Seatbelt::Proxy.new
181
+ method_proxy = Seatbelt::EigenmethodProxy.set(proxy, options)
182
+ klass = Module.const_get(method_proxy.namespace)
183
+
184
+ callee = method_proxy.instance_variable_get(:@callee)
185
+ method_proxy.init_klass_on_receiver(klass)
186
+ implementation_type = method_proxy.instance_variable_get(:@method_implementation_type)
187
+ if implementation_type.eql?(:instance)
188
+ method_proxy.arity = callee.instance_method(method_proxy.method).arity
189
+ else
190
+ method_proxy.arity = callee.method(method_proxy.method).arity
191
+ end
192
+ klass.eigenmethods << method_proxy
193
+ return method_proxy
194
+ end
195
+
196
+ end
197
+ end
198
+ end