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
@@ -0,0 +1,81 @@
1
+ module Seatbelt
2
+
3
+ # Public: Provides switches to enable accessing the implementation instance
4
+ # of an API class instances.
5
+ #
6
+ # Any API class that implements Seatbelt::Ghost can have access to its
7
+ # implementation class instance. This behaviour has to be enabled before using
8
+ # because its a violation of the Public/Private API approach.
9
+ #
10
+ # (And yes - in Ruby private methods are not really private methods.)
11
+ #
12
+ # Accessing the implementation instance is only available after the API Class
13
+ # was instantiated.
14
+ #
15
+ # Example:
16
+ #
17
+ # class Hotel
18
+ # include Seatbelt::Ghost
19
+ #
20
+ # enable_tunneling! # access to the implementation instance is not
21
+ # # possible.
22
+ #
23
+ # end
24
+ #
25
+ # class ImplementationHotel
26
+ # include Seatbelt::Document
27
+ # include Seatbelt::Gate
28
+ #
29
+ # attribute :ignore_dirty_rooms, Boolean
30
+ #
31
+ # end
32
+ #
33
+ # hotel = new Hotel
34
+ # hotel.tunnel(:ignore_dirty_rooms=,false)
35
+ #
36
+ # Passing blocks is also available if the accessed method supports blocks
37
+ #
38
+ # class ImplementationHotel
39
+ # include Seatbelt::Document
40
+ # include Seatbelt::Gate
41
+ #
42
+ # attribute :ignore_dirty_rooms, Boolean
43
+ #
44
+ # def filter_rooms(sections)
45
+ # rooms = self.rooms.map{|room| sections.include?(room_type)}
46
+ # yield(rooms)
47
+ # end
48
+ # end
49
+ #
50
+ # hotel.tunnel(:filter_rooms, ["shower, kitchen"]) do |rooms|
51
+ # rooms.select do |room|
52
+ # # do something
53
+ # end
54
+ # end
55
+ module GhostTunnel
56
+ extend self
57
+
58
+ # Public: Enables tunnel support for an instance of API class.
59
+ #
60
+ # Defines the #tunnel method.
61
+ def enable_tunneling!
62
+ define_method :tunnel do |name, *args, &block|
63
+ callees = self.eigenmethods.map{|n| n.instance_variable_get(:@callee)}
64
+ unless callees.empty?
65
+ callee = callees.first
66
+ callee.send(name, *args, &block)
67
+ end
68
+ end
69
+ end
70
+
71
+ # Public: Disables tunnel support for an instance of API class.
72
+ #
73
+ # Removes the #tunnel method defined in #enable_tunneling! .
74
+ def disable_tunneling!
75
+ if self.instance_methods.include?(:tunnel)
76
+ remove_method(:tunnel)
77
+ end
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,158 @@
1
+ module Seatbelt
2
+ module Gate
3
+ module Implementation
4
+
5
+ # Internal: Helper array to store method iterationable values.
6
+ def bulk_methods
7
+ @bulk_methods ||= []
8
+ end
9
+ private :bulk_methods
10
+ # Public: Provides definition for which class and object level (instance,
11
+ # class) the implementation methods match the API class methods.
12
+ #
13
+ # namespace - The API class (with complete namespace)
14
+ # scope - The object level of 'namespace' [:instance, :class]
15
+ # &block - The block that contains the implementation match
16
+ # definitions.
17
+ #
18
+ # Example:
19
+ #
20
+ # class ImplementHotel
21
+ # include Seatbelt::Gate
22
+ #
23
+ # implementation "Hotel", :class do
24
+ # match 'implement_find_region' => 'find_nearby'
25
+ # end
26
+ #
27
+ # def implement_find_region(options)
28
+ # #....
29
+ # end
30
+ # end
31
+ #
32
+ def implementation(namespace, scope, &block)
33
+ methods = []
34
+ @_namespace = namespace
35
+ @_scope = Seatbelt::GateConfig.method_directives[scope]
36
+ yield(self)
37
+ iterator = Core::Iterators::MethodConfig.
38
+ send("array_method_iterator",@_namespace,
39
+ self,scope)
40
+ bulk_methods.each(&iterator)
41
+ end
42
+
43
+ # Public: Builds an Hash representation that is used by the method_added
44
+ # hook (Seatbelt::Gate::Classmethods) to find the implementation methods
45
+ # of a specific object level.
46
+ #
47
+ # If the argument includes a :superclass key with a truthy value,
48
+ # #implementation_from_superclass is called to simulate similiar behaviour
49
+ # to the method added hook.
50
+ #
51
+ # Examples:
52
+ #
53
+ # implementation "Book", :instance do
54
+ # match 'implementation_publishing' => 'publishing'
55
+ # match 'implementation_paper_type' => 'paper_type',
56
+ # :superclass => true
57
+ # end
58
+ #
59
+ # hsh - An Hash containing the implementation method name as key and
60
+ # the API method as value.
61
+ # :superclass - defines that there will be an extra lookup for an
62
+ # implementation method in the class' superclass.
63
+ # (defaults to false)
64
+ def match(hsh)
65
+ hsh.stringify_keys!
66
+ superclass_definition = hsh.fetch("superclass", false)
67
+ delegate = hsh.fetch("delegated", false)
68
+ hsh.delete("superclass")
69
+ implementation_method = hsh.keys.first.to_sym
70
+ remote_method = hsh.values.first
71
+ if superclass_definition
72
+ config = {:as => "#{@_namespace}#{@_scope}#{remote_method}"}
73
+ self.send(:implementation_from_superclass,
74
+ implementation_method, config)
75
+ end
76
+ imp_hsh = {
77
+ implementation_method => {:as => "#{@_scope}#{remote_method}"}
78
+ }
79
+ bulk_methods << imp_hsh
80
+ if delegate && @_scope.eql?("#")
81
+ notify_delgated_method(implementation_method, imp_hsh, remote_method)
82
+ end
83
+ end
84
+
85
+ # Public: Builds an Hash representation for property that is passed to
86
+ # #match(hsh)
87
+ #
88
+ # A property is an ivar that is accessible through a getter and setter.
89
+ #
90
+ # #match_property is only useable within the :instance scope,
91
+ #
92
+ # Examples:
93
+ #
94
+ # # Having an identical property wthin the interface.
95
+ # implementation "Book", :instance do
96
+ # match_property 'author'
97
+ # end
98
+ #
99
+ # # Property names differ
100
+ # implementation "Book", :instance do
101
+ # match_property 'implementation_title' => 'title'
102
+ # end
103
+ #
104
+ # # Property is defined in the superclass
105
+ # implementation "Novel", :instance do
106
+ # match_property :publisher, :superclass => true
107
+ # end
108
+ #
109
+ # args - An argumentlist containing one of the following
110
+ # - property name as String or Symbol
111
+ # - config Hash similiar to #match
112
+ # - an Hash containing :superclass key (optional)
113
+ #
114
+ def match_property(*args)
115
+ options = {}
116
+ if args.size.eql?(1)
117
+ property = args.pop
118
+ else
119
+ options = args.pop
120
+ property = args.shift
121
+ options.stringify_keys!
122
+ end
123
+ if property.is_a?(String) || property.is_a?(Symbol)
124
+ [property, "#{property}="].each do |method|
125
+ match_options = {}
126
+ match_options[method] = method
127
+ match_options["superclass"] = options.fetch("superclass", false)
128
+ match(match_options)
129
+ end
130
+ elsif property.is_a?(Hash)
131
+ property.stringify_keys!
132
+ implement_property = property.keys.first.to_sym
133
+ remote_property = property.values.first
134
+ [{implement_property => remote_property},
135
+ {"#{implement_property}=" => "#{remote_property}="}].each do |opt|
136
+ if property.has_key?("superclass")
137
+ opt["superclass"] = property.fetch("superclass")
138
+ end
139
+ match(opt)
140
+ end
141
+ else
142
+ raise Seatbelt::Errors::TypeMissmatchError.
143
+ new("String, Symbol or Hash",
144
+ property.class.name)
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def notify_delgated_method(implementation_method, imp_hsh, remote_method)
151
+ namespaced_scope = "#{@_namespace}#{@_scope}#{remote_method}"
152
+ bulk_methods.last[implementation_method][:as] = namespaced_scope
153
+ bulk_methods.last[implementation_method][:delegated] = true
154
+ mark_as_instance_implementation.call(imp_hsh, implementation_method)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,51 @@
1
+ module Seatbelt
2
+ module Interface
3
+ extend self
4
+ include Seatbelt::Property::ClassMethods
5
+ # Public: Provides definitions of API Meta methods. For more informations
6
+ # of API meta methods see Seatbelt::Pool::Api#api_method
7
+ #
8
+ # scope - the type the methods should be defined for [:instance, :class]
9
+ #
10
+ # &block - The block that contains the API method definitions.
11
+ #
12
+ # Example
13
+ #
14
+ # class Hotel
15
+ # include Seatbelt::Ghost
16
+ #
17
+ # interface :class do
18
+ # define :find_nearby,
19
+ # :block_required => false,
20
+ # :args => [:options]
21
+ # end
22
+ # end
23
+ #
24
+ def interface(scope, &block)
25
+ @scope = scope
26
+ yield(self) if block
27
+ end
28
+
29
+ # Public: Defines an API method. This is only working if its called within
30
+ # the #interface method block.
31
+ #
32
+ # Wraps Seatbelt::Pool::Api#api_method
33
+ #
34
+ # name - Name of the API method
35
+ # hsh - an options Hash that refines the methods usage:
36
+ # :scope - the method type
37
+ # [:instance, :class]
38
+ # defaults to :instance
39
+ # :block_required - defines if the method
40
+ # require a Ruby Block for
41
+ # usage.
42
+ # :args - An array of expected
43
+ # arguments as Symbols or
44
+ # Strings. If omitted the
45
+ # method expects no arguments.
46
+ def define(name, hsh={})
47
+ hsh[:scope] = @scope
48
+ self.send(:api_method, name, hsh)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ module Seatbelt
2
+ module Core
3
+ module Iterators
4
+
5
+ # Public: Various methods useful for performing mathematical operations.
6
+ # All methods are module methods and should be called on the Math module.
7
+ #
8
+ class MethodConfig
9
+
10
+
11
+ # Public: Method config iterator block used by Gate#implement_class.
12
+ #
13
+ # klass - The API class name
14
+ # gate_klass - The class that includes the Gate module
15
+ #
16
+ # Returns a Proc that is useable for [].each
17
+ def self.array_method_iterator(klass, gate_klass, scope)
18
+ return lambda do |method_config|
19
+ method_config.send(:each_pair,
20
+ &hash_method_iterator(klass,gate_klass, scope))
21
+ end
22
+ end
23
+
24
+
25
+ # Public: Method config iterator block used by Gate#implement_class.
26
+ #
27
+ # klass - The API class name
28
+ # gate_klass - The class that includes the Gate module
29
+ #
30
+ # Returns a Proc that is useable for {}.each_pair
31
+ def self.hash_method_iterator(klass, gate_klass, scope)
32
+ methods_bucket = :implementation_methods if scope.eql?(:instance)
33
+ methods_bucket = :implementation_class_methods if scope.eql?(:class)
34
+ return lambda do |(key,value)|
35
+ method_name = key
36
+ implementation_config = {}
37
+ implementation_config[method_name] = {
38
+ :as => "#{klass}#{value[:as]}",
39
+ }
40
+ if value[:delegated]
41
+ implementation_config[method_name][:delegated] = value[:delegated]
42
+ end
43
+ gate_klass.send(methods_bucket) << implementation_config
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,101 @@
1
+ module Seatbelt
2
+
3
+ # Public: Lookup table implementation that holds all meta methods for a
4
+ # Seatbelt API Class.
5
+ #
6
+ class LookupTable < Array
7
+
8
+ alias_method :add_method, :<<
9
+ alias_method :set, :<<
10
+
11
+ # Public: Removes a method configuration from the lookup table.
12
+ #
13
+ # method_name - The method name identifier as Symbol.
14
+ # scope - The scope in for removing the method, defaults to :instance
15
+ #
16
+ # Returns the removed method configuration if found otherwise nil.
17
+ def remove_method(method_name, scope: :instance)
18
+ method = find_method(method_name, scope: scope)
19
+ delete(method) if method
20
+ end
21
+ alias_method :unset, :remove_method
22
+
23
+
24
+ # Public: Finds a method configuration by method name.
25
+ #
26
+ # method_name - The method configuration identifier.
27
+ # scope - The scope to search for the method, defaults to :instance
28
+ #
29
+ # Example:
30
+ #
31
+ # table = Seatbelt::LookupTable.new
32
+ # table.set({:my_method => {:scope => :klass, :block_required => true } })
33
+ #
34
+ # method = table.find_method(:my_method)
35
+ # #=> {:my_method => {:scope => :klass, :block_required => true } }
36
+ #
37
+ # Returns the method configuration if found, otherwise nil.
38
+ def find_method(method_name, scope: :instance)
39
+ detect do |method_config|
40
+ name = method_config.keys.first
41
+ method_scope = method_config[name][:scope]
42
+ name.eql?(method_name.to_sym) && method_scope.eql?(scope)
43
+ end
44
+ end
45
+
46
+
47
+ # Public: Gets a method configuration by name or configuration.
48
+ #
49
+ # method_c - The method identifier or its whole configurations Hash
50
+ # scope - The scope to search for the method, defaults to :instance
51
+ #
52
+ # Example:
53
+ #
54
+ # table = Seatbelt::LookupTable.new
55
+ # config = {
56
+ # :my_method => {
57
+ # :scope => :klass,
58
+ # :block_required => true
59
+ # }
60
+ # }
61
+ # table.set(config)
62
+ #
63
+ # method = table.get(:my_method)
64
+ # #=> {:my_method => {:scope => :klass, :block_required => true } }
65
+ #
66
+ # method = table.get(config)
67
+ # #=> {:my_method => {:scope => :klass, :block_required => true } }
68
+ #
69
+ # Returns the method configuration if found, otherwise nil.
70
+ def get(method_c, scope: :instance)
71
+ eqlCheck = false
72
+ identifier = if method_c.is_a?(Symbol) or method_c.is_a?(String) then
73
+ method_c
74
+ elsif method_c.is_a?(Hash)
75
+ scope = method_c.values.first[:scope]
76
+ eqlCheck = true
77
+ method_c.keys.first
78
+ end
79
+ method = find_method(identifier, scope: scope)
80
+
81
+ return method unless eqlCheck
82
+ return method if method.eql?(method_c)
83
+ end
84
+
85
+
86
+ # Public: Check if the lookup table contains a method configuration.
87
+ #
88
+ # identifier - The method configuration identifier or the configuration
89
+ # scope - The scope to search for the method, defaults to :instance
90
+ #
91
+ # Returns true if table has method otherwise false.
92
+ def has?(identifier, scope: :instance)
93
+ if identifier.is_a?(Symbol) or identifier.is_a?(String)
94
+ return not(find_method(identifier, scope: scope).nil?)
95
+ elsif identifier.is_a?(Hash)
96
+ return not(get(identifier, scope: scope).nil?)
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,90 @@
1
+ module Seatbelt
2
+ module Pool
3
+
4
+ # Public: Provides storage of meta-method definitions.
5
+ # All classes that declare (API) meta-methods have to include this module
6
+ # by using the Seatbelt::Terminal wrapper.
7
+ #
8
+ # For API design matters: https://gist.github.com/dsci/a96048884fdff81aca68
9
+ module Api
10
+ extend self
11
+
12
+ # Public: Registeres a meta-method at the method pool by adding the
13
+ # method configuration to a lookup table.
14
+ #
15
+ # Each class that includes Seatbelt::Terminal has its own lookup table.
16
+ #
17
+ # *args - An argument list consisting of
18
+ # * method_name - that has to be the first argument (required)
19
+ # * options - an options Hash that refines the methods usage:
20
+ # :scope - the method type
21
+ # [:instance, :class]
22
+ # defaults to :instance
23
+ # :block_required - defines if the method
24
+ # require a Ruby Block for
25
+ # usage.
26
+ # :args - An array of expected
27
+ # arguments as Symbols or
28
+ # Strings. If omitted the
29
+ # method expects no arguments.
30
+ #
31
+ #
32
+ # If no arguments are passed, a Seatbelt::Errors::ArgumentsMissmatchError
33
+ # is raised.
34
+ # Raises a Seatbelt::Errors::MissingMetaMethodName, if no meta-method
35
+ # name was given.
36
+ # If a meta-method name is passed that already exists in the lookup table,
37
+ # a Seatbelt::Errors::MetaMethodDuplicateError is raised.
38
+ #
39
+ # This will be a private method with Seatbelt 1.0.
40
+ def api_method(*args)
41
+ raise Errors::ArgumentsMissmatchError if args.empty?
42
+ raise Errors::MissingMetaMethodName if args.first.is_a?(Hash)
43
+ if lookup_tbl.map{|n| n.keys}.flatten.include?(args.first)
44
+ raise Errors::MetaMethodDuplicateError
45
+ end
46
+ default_options = { :scope => :instance,
47
+ :block_required => false,
48
+ :arity => 0
49
+ }
50
+ options = args.extract_options!
51
+ if options.has_key?(:args)
52
+ arity = options.delete(:args)
53
+ size = arity.size
54
+ block_size = lambda do
55
+ block_match = arity.join(",").scan(/\&{1,}/)
56
+ size = size - block_match.size if block_match
57
+ return size
58
+ end
59
+ if arity.join(",").match(/\*{1,}/)
60
+ size = -block_size.call
61
+ eqls = arity.join(",").scan(/[\=]+/).size
62
+ size = size + eqls
63
+ default_options[:arity] = size
64
+ else
65
+ size = block_size.call
66
+ eqls = arity.join(",").scan(/[\=]+/).size
67
+ size = eqls > 0 ? -size : size
68
+ default_options[:arity] = size
69
+ end
70
+ end
71
+
72
+ meta_definitions = { args.first => default_options.merge(options) }
73
+
74
+ lookup_tbl.set(meta_definitions)
75
+ end
76
+
77
+
78
+ # Public: An accessor to the lookup table array.
79
+ #
80
+ # Creates the table if it doesn't exists.
81
+ #
82
+ # Returns an instance of Lookup Table
83
+ def lookup_tbl
84
+ @lookup_tbl = LookupTable.new if @lookup_tbl.nil?
85
+ return @lookup_tbl
86
+ end
87
+
88
+ end
89
+ end
90
+ end