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 @@
1
+ require 'seatbelt/errors/errors'
@@ -0,0 +1,150 @@
1
+ module Seatbelt
2
+
3
+ # Public: Various errors used with Seatbelt.
4
+ #
5
+ module Errors
6
+
7
+ # Public: Will be raised if there are too much or too few arguments passed
8
+ # to a method.
9
+ # Only declared for design purposes.
10
+ #
11
+ class ArgumentsMissmatchError < ::ArgumentError; end
12
+
13
+
14
+ # Public: Will be raised within Seatbelt::Pool::Api#api_method if no meta
15
+ # method name is passed.
16
+ #
17
+ class MissingMetaMethodName < ::StandardError
18
+ def message
19
+ "You have to specifiy a meta-method name!"
20
+ end
21
+ end
22
+
23
+
24
+ # Public: Will be raised if a meta method definition will be inserted into
25
+ # the class lookuptable but already exists.
26
+ #
27
+ class MetaMethodDuplicateError < ::StandardError
28
+ def message
29
+ "The meta-method you want to define is ambigious."
30
+ end
31
+ end
32
+
33
+
34
+ # Public: Will be raised if a meta method is defined but not implemented in
35
+ # the remote class.
36
+ #
37
+ class MethodNotImplementedError < ::NoMethodError; end
38
+
39
+
40
+ # Public: Will be raised if a method directive for configurating the Gate is
41
+ # used but the directive is not allowed for usage.
42
+ #
43
+ class DirectiveNotAllowedError < ::StandardError
44
+ def message
45
+ "The directive you want to use is not allowed."
46
+ end
47
+ end
48
+
49
+
50
+ # Public: Will be raised if a method isn't implemented at a class or class
51
+ # instance.
52
+ #
53
+ class NoMethodError < MethodNotImplementedError; end
54
+
55
+
56
+ # Public: Will be raised if a method requires a block but isn't passed to
57
+ # the method.
58
+ #
59
+ class ApiMethodBlockRequiredError < ::StandardError
60
+ def message
61
+ "The method you want to call requires a block."
62
+ end
63
+ end
64
+
65
+ # Public: Will be raised if a model is assigned to a 'has_many' association
66
+ # and the models class isn't of required type.
67
+ class TypeMissmatchError < ::StandardError
68
+ attr_accessor :awaited, :got
69
+
70
+ # Public: Initialize a TypeMissmatchError.
71
+ #
72
+ # awaited - The awaited objects class name
73
+ # got - The actual assigned objects class name.
74
+ def initialize(awaited, got)
75
+ @awaited = awaited
76
+ @got = got
77
+ end
78
+
79
+ # The exception message in an understandable form.
80
+ #
81
+ # Returns the error message.
82
+ def to_s
83
+ msg = "An instance of #{awaited} awaited but "
84
+ msg += "get an instance of #{got}."
85
+ return msg
86
+ end
87
+ end
88
+
89
+ # Public: Will be raised if a question/query is called and no tape with
90
+ # implementation is found.
91
+ #
92
+ class NoTapeFoundForQueryError < ::StandardError
93
+ def message
94
+ "There is no tape implemented for answering this question."
95
+ end
96
+ end
97
+
98
+ # Public: Will be raised if a tape should be used in multiple tape decks.
99
+ #
100
+ class MultipleTapeUsageDetectedError < ::StandardError
101
+ def message
102
+ "A tape can't be used in multiple tape decks."
103
+ end
104
+ end
105
+
106
+ # Public: Will be raised if a synthseizer did not implement the
107
+ # synthesizable_attributes method.
108
+ class SynthesizeableAttributesNotImplementedError < ::StandardError
109
+ def message
110
+ "Your synthesizer has to implement #synthesizable_attributes."
111
+ end
112
+ end
113
+
114
+ # Public: Will be raised if an object is tried to call that does not exist
115
+ # at runtime.
116
+ class ObjectDoesNotExistError < ::StandardError
117
+ def message
118
+ "The object you called does not exist."
119
+ end
120
+ end
121
+
122
+ class ArgumentMissmatchError < ArgumentError; end
123
+
124
+ # Public: Will be raised if a property is tried to define on a class level
125
+ # interface.
126
+ class PropertyOnClassLevelDefinedError < ::StandardError
127
+ def message
128
+ <<-MESSAGE.gsub(/^\s+/, "")
129
+ You try to define a property at class level interface. That is not
130
+ supported by now but maybe in future version.
131
+ MESSAGE
132
+ end
133
+ end
134
+
135
+ class PropertyNotDefinedYetError < ::StandardError
136
+
137
+ def initialize(property_name)
138
+ @property_name = property_name
139
+ end
140
+
141
+ def to_s
142
+ <<-MESSAGE.gsub(/^\s+/, "")
143
+ You try to define a property as accessible that is not defined yet.
144
+ Use define_property :#{@property_name} to define your property first.
145
+ MESSAGE
146
+ end
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,59 @@
1
+ module Seatbelt
2
+
3
+ # Public: A configuration class to configure the Gate.
4
+ #
5
+ class GateConfig
6
+
7
+ # Public: Setter to set the instance method directive. This is optional
8
+ # and defaults to #
9
+ #
10
+ # :: is not allowed as directive.
11
+ #
12
+ # directive - A String representing the directive.
13
+ #
14
+ def self.method_directive_instance=(directive)
15
+ Seatbelt.check_directive(directive)
16
+ @method_directive_instance = directive
17
+ end
18
+
19
+ # Public: Setter to set the class method directive. This is optional
20
+ # and defaults to .
21
+ #
22
+ # :: is not allowed as directive.
23
+ #
24
+ # directive - A String representing the directive.
25
+ #
26
+ def self.method_directive_class=(directive)
27
+ Seatbelt.check_directive(directive)
28
+ @method_directive_class = directive
29
+ end
30
+
31
+ # Public: Getter to retrieve te instance method directive.
32
+ #
33
+ # Returns the instance method directive if set otherwise '#'
34
+ def self.method_directive_instance
35
+ @method_directive_instance || "#"
36
+ end
37
+
38
+ # Public: Getter to retrieve te class method directive.
39
+ #
40
+ # Returns the class method directive if set otherwise '.'
41
+ def self.method_directive_class
42
+ @method_directive_class || "."
43
+ end
44
+
45
+ # Public: Hash of method directives attached to its scope.
46
+ #
47
+ # Contains :class and :instance keys and their corrosponding method
48
+ # directives
49
+ #
50
+ # Returns a Hash.
51
+ def self.method_directives
52
+ {
53
+ :class => self.method_directive_class,
54
+ :instance => self.method_directive_instance
55
+ }
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,140 @@
1
+ module Seatbelt
2
+
3
+ # Public: Handles calling and defining of API methods.
4
+ #
5
+ # Any class that should acts like a API Class have to include this module.
6
+ #
7
+ # class Flight
8
+ # include Seatbelt::Ghost
9
+ #
10
+ # api_method :estimated_flight_time
11
+ #
12
+ # api_method :find_flights,
13
+ # :scope => :class
14
+ # end
15
+ #
16
+ # flight = Flight.new
17
+ # flight.estimated_flight_time(:to => "London")
18
+ # Flight.find_flights(:to => "London", :from => "Frankfurt")
19
+ module Ghost
20
+
21
+ def self.included(base)
22
+ base.class_eval do
23
+ [Pool::Api, EigenmethodStore, ClassMethods,
24
+ GhostTunnel,Interface].each do |mod|
25
+ self.extend mod
26
+ end
27
+ include EigenmethodStore
28
+ include Seatbelt::Property::InstanceMethods
29
+
30
+ class << self
31
+
32
+ alias_method :_new, :new
33
+ # Public: Overrides the Class#new method to create the class instance
34
+ # eigenmethods.
35
+ #
36
+ # *args - An argumentlist passed to #initialize
37
+ #
38
+ # Returns the instance.
39
+ def new(*args)
40
+ obj = _new(*args)
41
+ namespace = obj.class.name
42
+ eigenmethods_for_scope = Terminal.
43
+ for_scope_and_namespace(:instance,
44
+ namespace)
45
+ unless eigenmethods_for_scope.empty?
46
+ proxy = Seatbelt::Proxy.new
47
+ receiver = eigenmethods_for_scope.first.receiver.new
48
+ eigenmethods_for_scope.each do |eigenmethod|
49
+ options = {:eigenmethod => eigenmethod,
50
+ :object => obj,
51
+ :receiver => receiver,
52
+ :return_method => true,
53
+ :add_to => false
54
+ }
55
+ obj.eigenmethods << Seatbelt::EigenmethodProxy.set(proxy, options)
56
+ if obj.class.respond_to?(:synthesizers)
57
+ synthesizers = obj.class.synthesizers.select do |synthesizer|
58
+ synthesizer[:klass].eql?(obj.class.name)
59
+ end
60
+ unless synthesizers.empty?
61
+ synthesizers.each do |synthesizer|
62
+ synthesizer[:adapter].new(obj.class, receiver).synthesize
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ return obj
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ module EigenmethodStore
76
+
77
+ def eigenmethods
78
+ @eigenmethods ||= []
79
+ end
80
+
81
+ end
82
+
83
+
84
+ module ClassMethods
85
+ # Public: Calls a API class method. If the method isn't defined or
86
+ # found in the class lookup table a Seatbelt::Errors::NoMethodError is
87
+ # raised.
88
+ #
89
+ # If method is defined it passes the calling responsibility to the core
90
+ # Callee module.
91
+ #
92
+ # method_name - the called methods name
93
+ # *args - the methods argument list
94
+ # &block - the methods block (this is optional)
95
+ #
96
+ # Returns the evaluted method value.
97
+ def method_missing(method_name, *args, &block)
98
+ unless self.lookup_tbl.has?(method_name, scope: :class)
99
+ raise Seatbelt::Errors::NoMethodError
100
+ end
101
+ Seatbelt::Callee.handle(self,
102
+ { :lookup_tbl => self.lookup_tbl,
103
+ :scope => :class,
104
+ :method_name => method_name
105
+ },
106
+ *args,
107
+ &block)
108
+ end
109
+
110
+ end
111
+
112
+ # Public: Calls a API instance method. If the method isn't defined or
113
+ # found in the class lookup table a Seatbelt::Errors::NoMethodError is
114
+ # raised.
115
+ #
116
+ # If method is defined it passes the calling responsibility to the core
117
+ # Callee module.
118
+ #
119
+ # method_name - the called methods name
120
+ # *args - the methods argument list
121
+ # &block - the methods block (this is optional)
122
+ #
123
+ # Returns the evaluted method value.
124
+ def method_missing(method_name, *args, &block)
125
+ unless self.class.lookup_tbl.has?(method_name)
126
+ unless self.respond_to?(method_name)
127
+ raise Seatbelt::Errors::NoMethodError
128
+ end
129
+ end
130
+ Seatbelt::Callee.handle(self,
131
+ { :lookup_tbl => self.class.lookup_tbl,
132
+ :scope => :instance,
133
+ :method_name => method_name
134
+ },
135
+ *args,
136
+ &block)
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,9 @@
1
+ require 'seatbelt/models/hotel'
2
+ require 'seatbelt/models/flight'
3
+ require 'seatbelt/models/airport'
4
+ require 'seatbelt/models/offer'
5
+ require 'seatbelt/models/region_offer'
6
+ require 'seatbelt/models/picture'
7
+ require 'seatbelt/models/travel_request'
8
+ require 'seatbelt/models/travel_response'
9
+ require 'seatbelt/models/region_response'
@@ -0,0 +1,10 @@
1
+ require 'seatbelt/core'
2
+ require 'seatbelt/synthesizer'
3
+ require 'seatbelt/gate_config'
4
+ require 'seatbelt/ghost'
5
+ require 'seatbelt/tape'
6
+ require 'seatbelt/tape_deck'
7
+ require 'seatbelt/document'
8
+ require 'seatbelt/errors'
9
+ require 'seatbelt/collections/collection'
10
+ require 'seatbelt/translator'
@@ -0,0 +1,3 @@
1
+ require 'seatbelt/synthesizers/synthesizer'
2
+ require 'seatbelt/synthesizers/document'
3
+ require 'seatbelt/synthesizers/mongoid'
@@ -0,0 +1,16 @@
1
+ module Seatbelt
2
+ module Synthesizers
3
+
4
+ # Public: A Synthesizer that syncs a Seatbelt::Document based implementation
5
+ # class with a Seatbelt::Document proxy object
6
+ class Document
7
+ include Seatbelt::Synthesizer
8
+
9
+ # The attributes to synthesize as Array.
10
+ def synthesizable_attributes
11
+ synthesizable_object.attributes.keys
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Seatbelt
2
+ module Synthesizers
3
+
4
+ # Public: A Synthesizer that syncs a Mongoid::Document based implementation
5
+ # class with a Seatbelt::Document proxy object
6
+ class Mongoid
7
+ include Seatbelt::Synthesizer
8
+
9
+ # The attributes to synthesize as Array.
10
+ def synthesizable_attributes
11
+ synthesizable_object.fields.keys.map(&:to_sym)
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,146 @@
1
+ module Seatbelt
2
+
3
+ # Public: The Synthesizer base module which provides attribute based
4
+ # synthesizing between a proxy and an implementation object.
5
+ #
6
+ # To create a Synthesizer include this module in a plain Ruby class and
7
+ # implement the #synthesizable_attributes method.
8
+ #
9
+ # Example
10
+ #
11
+ # class BookSynthesizer
12
+ # include Seatbelt::Synthesizer
13
+ #
14
+ # def synthesizable_attributes
15
+ # [:title, :publisher]
16
+ # end
17
+ # end
18
+ #
19
+ #
20
+ # class Book
21
+ # include Seatbelt:Document
22
+ # include Seatbelt::Ghost
23
+ #
24
+ # attribute :title, String
25
+ # attribute :publisher, String
26
+ # attribute :author, String
27
+ #
28
+ # api_method :sell
29
+ # end
30
+ #
31
+ # class ImplementationBook < ActiveRecord::Base
32
+ # include Seatbelt::Gate
33
+ #
34
+ # synthesize :from => "Book",
35
+ # :adapter => "BookSynthesizer"
36
+ # end
37
+ module Synthesizer
38
+
39
+ attr_reader :synthesizable_object
40
+
41
+ # Public: Initializes the Synthesizer.
42
+ #
43
+ # from_klass - The API Class
44
+ # synthesizable_object - The implementation class instance
45
+ def initialize(from_klass, synthesizable_object)
46
+ @klass = from_klass
47
+ @synthesizable_object = synthesizable_object
48
+ end
49
+
50
+ # Public: Defines the attribute based synthesize mechanism by redefining the
51
+ # getter and setter methods of the implementation class instance.
52
+ #
53
+ def synthesize
54
+ unless self.respond_to?(:synthesizable_attributes)
55
+ raise Seatbelt::Errors::SynthesizeableAttributesNotImplementedError
56
+ end
57
+ if @synthesizable_object.class.respond_to?(:synthesize_map)
58
+ if @synthesizable_object.class.synthesize_map.empty?
59
+ __synthesize_without_map
60
+ else
61
+ __synthesize_with_map
62
+ end
63
+ else
64
+ __synthesize_without_map
65
+ end
66
+ end
67
+
68
+ # Defines synthesizing with the same proxy object attributes and
69
+ # implementation class instance attributes.
70
+ #
71
+ def __synthesize_without_map
72
+ @klass.attribute_set.each do |attr|
73
+ attribute_name = attr.name
74
+ if attribute_name.in?(__synthesizable_attribute_names)
75
+ __redefine_setter(@synthesizable_object, attribute_name)
76
+ __redefine_getter(@synthesizable_object, attribute_name)
77
+ end
78
+ end
79
+ end
80
+
81
+ # Defines synthesizing by considering the presence of a synthesize map of
82
+ # proxy and implementation attributes.
83
+ def __synthesize_with_map
84
+ map = @synthesizable_object.class.synthesize_map
85
+ @klass.attribute_set.each do |attr|
86
+ attribute_name = attr.name
87
+ if attribute_name.in?(map.keys)
88
+ object_attribute_name = map[attribute_name]
89
+ __redefine_setter(@synthesizable_object, object_attribute_name,
90
+ attribute_name)
91
+ __redefine_getter(@synthesizable_object, object_attribute_name,
92
+ attribute_name)
93
+ end
94
+ end
95
+ end
96
+
97
+ def __synthesizable_attribute_names
98
+ if synthesizable_attributes.is_a?(Hash)
99
+ return synthesizable_attributes.keys
100
+ else
101
+ return synthesizable_attributes
102
+ end
103
+ end
104
+
105
+ def __redefine_getter(on_object, attribute, klass_attribute_name=nil)
106
+ if klass_attribute_name.nil?
107
+ klass_attribute_name = attribute
108
+ end
109
+ getter_code = <<-RUBY
110
+ class << self
111
+ alias_method :_#{attribute}, :#{attribute}
112
+ #alias_method "_#{attribute}=", "#{attribute}="
113
+ end
114
+ def #{attribute}
115
+ self.send("#{attribute}=", proxy.send("#{klass_attribute_name}"))
116
+ return self.send("_#{attribute}")
117
+ end
118
+ RUBY
119
+ on_object.instance_eval getter_code, __FILE__, __LINE__
120
+ end
121
+
122
+ def __redefine_setter(on_object, attribute, klass_attribute_name=nil)
123
+ if klass_attribute_name.nil?
124
+ klass_attribute_name = attribute
125
+ end
126
+ setter_code = <<-RUBY
127
+ class << self
128
+ #alias_method :_#{attribute}, :#{attribute}
129
+ alias_method "_#{attribute}=", "#{attribute}="
130
+ end
131
+
132
+ def #{attribute}=(value)
133
+ proxy.send("#{klass_attribute_name}=",value)
134
+ self.send("_#{attribute}=",value)
135
+ end
136
+ RUBY
137
+ on_object.instance_eval setter_code, __FILE__, __LINE__
138
+ end
139
+
140
+ private :__synthesizable_attribute_names,
141
+ :__redefine_getter,
142
+ :__redefine_setter,
143
+ :__synthesize_without_map,
144
+ :__synthesize_with_map
145
+ end
146
+ end