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,2 @@
1
+ require 'seatbelt/tapes/util/delegate'
2
+ require 'seatbelt/tapes/tape'
@@ -0,0 +1,71 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Seatbelt
3
+
4
+ # A tape deck holds several tapes and 'plays' the section for a query.
5
+ # (See #respond for details.)
6
+ module TapeDeck
7
+
8
+ def self.included(base)
9
+ base.class_eval do
10
+ extend ClassMethods
11
+
12
+ private_class_method :delegate_answer,
13
+ :get_answer_from_tape
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ include Seatbelt::Tapes::Util::Delegate
19
+
20
+ # A store to hold tapes for the class that implements the module.
21
+ #
22
+ # Returns store.
23
+ def tapes
24
+ @tapes = [] if @tapes.nil?
25
+ @tapes
26
+ end
27
+
28
+ # Public: Adds a tape to the tape store. If a passed tape is already
29
+ # included in the store, it will not included a second time.
30
+ #
31
+ # tape_name - (class) name of the tape.
32
+ #
33
+ def use_tape(tape_name)
34
+ unless tape_name.in?(tapes)
35
+ unless tape_name.tape_deck.nil?
36
+ raise Seatbelt::Errors::MultipleTapeUsageDetectedError
37
+ end
38
+ tape_name.tape_deck.nil?
39
+ tape_name.tape_deck = Module.const_get(self.name)
40
+ tapes << tape_name
41
+ end
42
+ end
43
+ alias_method :add_tape, :use_tape
44
+
45
+ # Public: Adds a bunch of tapes to the store. See #use_tape.
46
+ #
47
+ # *tapes - A list of tapes.
48
+ #
49
+ def use_tapes(*tapes)
50
+ tapes.flatten.each { |tape| self.use_tape(tape) }
51
+ end
52
+
53
+ # Public: Detects an answer that matches the question and calls the
54
+ # translation block.
55
+ #
56
+ # question - The question or query as String
57
+ #
58
+ def respond(question)
59
+ found_answer = nil
60
+ tapes.each do |tape|
61
+ found_answer = get_answer_from_tape(tape, :question => question)
62
+ break if found_answer
63
+ end
64
+ raise Seatbelt::Errors::NoTapeFoundForQueryError unless found_answer
65
+ delegate_answer(found_answer, :question => question) if found_answer
66
+ end
67
+ alias_method :answer, :respond
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,105 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Seatbelt
3
+ # Public: A Tape is a collection of translations to common queries.
4
+ # It's the base class of all tapes that provides handling the querys
5
+ # and delegating translations to other tapes.
6
+ #
7
+ # Example:
8
+ #
9
+ # class HotelSampleTape < Seatbelt::Tape
10
+ # translate /Show me (\d+) of the (\w+) in (\w+)/ do |sentence,
11
+ # count,
12
+ # price_type,
13
+ # location|
14
+ # # do some code here ....
15
+ # end
16
+ # end
17
+ #
18
+ # A translation or answer to a query is defined with #translate following
19
+ # by a regular expression and a block.
20
+ #
21
+ # The block takes at least one argument. If the block argument list contains
22
+ # only one item then the item is the first machted data from the regular
23
+ # expression.
24
+ #
25
+ # If you want to have the original query and you expect matched data (at
26
+ # least one) than define your block argument list with at least two items
27
+ # where the first one is the query and the second, third ... are the
28
+ # matched data entries.
29
+ #
30
+ # Note that the extracted data from any query is passed to your translation
31
+ # block as a String, so you have to handle the type casts by yourself.
32
+ #
33
+ # To call other translation or delegate some values to a different
34
+ # translation at another tape use the #play_tape method within your
35
+ # #translation block.
36
+ #
37
+ # Example
38
+ #
39
+ # translate /Send me (\d+) postcards from (\w+)/ do |sentence,
40
+ # post_card_count,
41
+ # location|
42
+ # country = Region.find(:location => location)
43
+ # amount = play_tape(PostcardTape, :section => "send #{post_card_count}")
44
+ #
45
+ # CardDispatcher.send_cards(:num => 2, :rate => amount,
46
+ # :lc => country.iso_code)
47
+ # end
48
+ #
49
+ # Calls the /send (\d+)/ answer of the PostCardTape. The Tape has not to be
50
+ # attached with the own tape deck.
51
+ # If the first argument of #play_tape is omitted, any section of the
52
+ # current tape is called. Note that the section hash is needed.
53
+ class Tape
54
+ extend Seatbelt::Tapes::Util::Delegate
55
+
56
+ private_class_method :delegate_answer,
57
+ :get_answer_from_tape
58
+
59
+ class << self
60
+ # Public: Gets/Sets the tape deck of the tape.
61
+ attr_accessor :tape_deck
62
+ end
63
+
64
+ # A store to hold answers for the tape.
65
+ #
66
+ # Returns the store.
67
+ def self.answers
68
+ @answers = [] if @answers.nil?
69
+ @answers
70
+ end
71
+
72
+ # Public: Defines a translation implementation for a query/question.
73
+ #
74
+ # query - A regular expression for identifying the question.
75
+ # &block - An implementation block.
76
+ #
77
+ def self.translate(query, &block)
78
+ answers << { :regex => query,
79
+ :action => block }
80
+ end
81
+
82
+ # Public: Delegates or call another tape translation section.
83
+ #
84
+ # *args - An argument list containing:
85
+ # tape (class) - The tape to call (optional)
86
+ # options - A Hash to refine the :section (required)
87
+ #
88
+ def self.play_tape(*args)
89
+ options = args.extract_options!
90
+ tape = args.pop
91
+ section = options[:section]
92
+
93
+ tape = self if tape.nil?
94
+
95
+ answer = get_answer_from_tape(tape, :question => section)
96
+ raise Seatbelt::Errors::NoTapeFoundForQueryError unless answer
97
+ delegate_answer(answer, :question => section) if answer
98
+ end
99
+
100
+ class << self
101
+ alias_method :listen_to, :translate
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,56 @@
1
+ module Seatbelt
2
+ module Tapes
3
+ module Util
4
+
5
+ # Public: Various methods to delegate answers or sections of a tape
6
+ # to corrosponding implementation.
7
+ # All methods are module methods and should be included as a class
8
+ # extension in a tape related class as private methods.
9
+ module Delegate
10
+
11
+ # Public: Calls the implementation block for a query.
12
+ #
13
+ # *args - An argument list containing
14
+ # answer - the answer implementation config
15
+ # (see Tape.translate for details)
16
+ # options - A hash containing
17
+ # :question - The query question as String
18
+ #
19
+ # Returns the evaluated result of answer[:action].
20
+ def delegate_answer(*args)
21
+ options = args.extract_options!
22
+ question = options[:question]
23
+ answer = args.pop
24
+ argument_length = answer[:action].arity - 1
25
+ data = question.match(answer[:regex]).captures
26
+ first_data_item = if data.empty?
27
+ question
28
+ else
29
+ data.first
30
+ end
31
+ case argument_length
32
+ when 0 then answer[:action].call(first_data_item)
33
+ when 1 then answer[:action].call(question,data.first)
34
+ else answer[:action].call(question,*data)
35
+ end
36
+ end
37
+
38
+ # Public: Extracts an answer from a tape where the answer's regex
39
+ # matches the question string.
40
+ #
41
+ # tape - A Tape class
42
+ # options={} - A Hash containing
43
+ # :question - The query question as String
44
+ #
45
+ # Returns a found answer or nil.
46
+ def get_answer_from_tape(tape, options={})
47
+ question = options[:question]
48
+ tape.answers.detect do |answer|
49
+ question.match(answer[:regex])
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ module Seatbelt
2
+
3
+ # Public: Interface to the implementation sections of TQL.
4
+ # (TranslationQuery Language)
5
+ #
6
+ class Translator
7
+
8
+ class Config
9
+
10
+ def self.namespace
11
+ @namespace ||= ""
12
+ end
13
+
14
+ def self.namespace=(namespace)
15
+ @namespace = namespace
16
+ end
17
+
18
+ def self.name_regex
19
+ @name_regex ||= /^\s*\w*[:,]/
20
+ end
21
+
22
+ def self.name_regex=(regexp)
23
+ @name_regex = regexp
24
+ end
25
+
26
+ def self.default_model_class
27
+ @default_model_class
28
+ end
29
+
30
+ def self.default_model_class=(klass)
31
+ @default_model_class = klass
32
+ end
33
+
34
+ end
35
+
36
+
37
+ def self.setup(&block)
38
+ yield(config)
39
+ end
40
+
41
+ def self.config
42
+ Translator::Config
43
+ end
44
+
45
+ # Public: Takes sentence an delegates it to the responding class.
46
+ #
47
+ # query - The natural language sentence as String.
48
+ #
49
+ # Example
50
+ # Translator.tell_me "Hotel: Find the 2 cheapest near London"
51
+ # Translator.tell_me "Offer: Find all for three weeks in Finnland"
52
+ #
53
+ # A query starts with the class name the query should pointed to following
54
+ # by ':'. If this is omitted the class defaults to Offer
55
+ def self.tell_me(query)
56
+ model_prefix = config.namespace
57
+ name_regex = config.name_regex
58
+ result = query.scan(name_regex).first
59
+ klass = result.gsub(":", "") if result.respond_to?(:gsub)
60
+ klass = config.default_model_class unless klass
61
+ pattern = query.gsub(name_regex, "").lstrip
62
+ Module.const_get("#{model_prefix}#{klass}").send(:respond,pattern)
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module Seatbelt
2
+ VERSION = "0.10.0"
3
+ end
data/seatbelt.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'seatbelt/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "seat-belt"
8
+ spec.version = Seatbelt::VERSION
9
+ spec.authors = ["Daniel Schmidt"]
10
+ spec.email = ["dsci@code79.net"]
11
+ spec.description = %q{Tool to define implementation interfaces that should be decoupled from their
12
+ implementations. (A Ruby header file approach.)}
13
+ spec.summary = %q{Define headers in your Ruby (we try to)}
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_runtime_dependency "activesupport"
25
+ spec.add_runtime_dependency "virtus", "~> 1.0.0"
26
+ spec.add_runtime_dependency "activemodel"
27
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe Seatbelt::Eigenmethod do
4
+
5
+ it "provides #scope_level" do
6
+ expect(subject).to respond_to(:scope_level)
7
+ expect(subject).to respond_to(:scope_level=)
8
+ end
9
+
10
+ it "provides #receiver" do
11
+ expect(subject).to respond_to(:receiver)
12
+ expect(subject).to respond_to(:receiver=)
13
+ end
14
+
15
+ it "provides #namespace" do
16
+ expect(subject).to respond_to(:namespace)
17
+ expect(subject).to respond_to(:namespace=)
18
+ end
19
+
20
+ it "provides #implemented_as" do
21
+ expect(subject).to respond_to(:implemented_as)
22
+ expect(subject).to respond_to(:implemented_as=)
23
+ end
24
+
25
+ it "provides #method" do
26
+ expect(subject).to respond_to(:method=)
27
+ end
28
+
29
+ it "provides #method_implementation_type" do
30
+ expect(subject).to respond_to(:method_implementation_type=)
31
+ expect(subject).to respond_to(:method_implementation_type)
32
+ end
33
+
34
+ it "provides #call" do
35
+ expect(subject).to respond_to(:call)
36
+ end
37
+
38
+ it "provides #instance_level?" do
39
+ expect(subject).to respond_to(:instance_level?)
40
+ end
41
+
42
+ it "provides #class_level?" do
43
+ expect(subject).to respond_to(:class_level?)
44
+ end
45
+
46
+ it "provides #arity" do
47
+ expect(subject).to respond_to(:arity)
48
+ end
49
+
50
+ describe "#call" do
51
+ let(:proxy){ Seatbelt::Eigenmethod.new }
52
+ context "on instance level" do
53
+
54
+ before do
55
+ class Sample
56
+ def foo(a,b)
57
+ a*b
58
+ end
59
+ end
60
+ proxy.method = Sample.instance_method(:foo)
61
+ proxy.receiver = Sample
62
+ proxy.scope_level = :instance
63
+ proxy.implemented_as = :superfoo
64
+ proxy.instance_variable_set(:@callee, Sample.new)
65
+ end
66
+
67
+ it "calls the implemented method" do
68
+ expect(proxy.call(2,4)).to eq 8
69
+ end
70
+
71
+ end
72
+
73
+ context "on class level" do
74
+
75
+ before do
76
+ class NextSample
77
+ def self.count
78
+ return 12
79
+ end
80
+ end
81
+ proxy.method = :count
82
+ proxy.receiver = NextSample
83
+ proxy.scope_level = :class
84
+ proxy.implemented_as = :superfoo
85
+ proxy.method_implementation_type = :class
86
+ end
87
+
88
+ it "calls the implemented method" do
89
+ expect(proxy.call).to eq 12
90
+ end
91
+
92
+ context "calling instance method on class level" do
93
+ before do
94
+ class Sample
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,521 @@
1
+ require 'spec_helper'
2
+
3
+ describe Seatbelt::Gate do
4
+ class ImplementsA
5
+ include Seatbelt::Gate
6
+ end
7
+
8
+ def stub_eigenmethods(*args)
9
+ opts = args.last.is_a?(Hash) ? args.pop : {}
10
+ return_value = opts.fetch(:return_value, [])
11
+ args.each do |klass|
12
+ klass.class.send(:define_method, :eigenmethods) do
13
+ return return_value
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "class methods" do
19
+
20
+ it "provides #implement" do
21
+ expect(ImplementsA).to respond_to(:implement)
22
+ end
23
+
24
+ it "provides #synthesize" do
25
+ expect(ImplementsA).to respond_to(:synthesize)
26
+ end
27
+
28
+ it "provides #synthesize_map" do
29
+ expect(ImplementsA).to respond_to(:synthesize_map)
30
+ end
31
+
32
+ describe "#implement" do
33
+
34
+ before(:all) do
35
+ class A; end
36
+ module Vagalo; class Airport; end; end
37
+ stub_eigenmethods(A, Vagalo::Airport)
38
+ end
39
+
40
+ it "registers a logic method the the terminal" do
41
+ expect do
42
+ ImplementsA.class_eval do
43
+ def fetch_flight_time; end
44
+ implement :fetch_flight_time, :as => "A.flight_time"
45
+ end
46
+ end.to change{ Seatbelt::Terminal.luggage.size }.by(1)
47
+
48
+ config = Seatbelt::Terminal.luggage.last
49
+
50
+ expect(config).to have_key(:method)
51
+ expect(config).to have_key(:implemented_as)
52
+ expect(config).to have_key(:namespace)
53
+ expect(config).to have_key(:scope)
54
+
55
+ expect(config[:implemented_as]).to eq :flight_time
56
+ expect(config[:namespace]).to eq "A"
57
+ expect(config[:scope]).to eq :class
58
+ end
59
+
60
+ it "registers a logic method with module namespace and instance method" do
61
+ expect do
62
+ ImplementsA.class_eval do
63
+ def check_flight_to(destination); end
64
+ implement :check_flight_to, :as => "Vagalo::Airport#book_flight_to"
65
+ end
66
+ end.to change { Seatbelt::Terminal.luggage.size }.by(1)
67
+
68
+ config = Seatbelt::Terminal.luggage.last
69
+
70
+ expect(config[:namespace]).to eq "Vagalo::Airport"
71
+ expect(config[:scope]).to eq :instance
72
+ expect(config[:implemented_as]).to eq :book_flight_to
73
+
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+
80
+ describe "instance methods" do
81
+
82
+ it "provides #proxy_object" do
83
+ expect(ImplementsA.new).to respond_to(:proxy_object)
84
+ end
85
+
86
+ describe "#proxy" do
87
+
88
+ before(:all) do
89
+
90
+ class Sky
91
+ def sky_color
92
+ 'blue'
93
+ end
94
+ end
95
+
96
+ class ProxySample
97
+ include Seatbelt::Document
98
+ include Seatbelt::Ghost
99
+
100
+ attribute :name, String
101
+
102
+ interface :instance do
103
+ define :bar, :args => [:name]
104
+ define :foo
105
+ define :codecs, :args => [:num]
106
+ define :sky, :args => [:ff]
107
+ end
108
+
109
+ end
110
+
111
+ class ProxyImplementationSample
112
+ include Seatbelt::Gate
113
+ attr_accessor :airport_codes
114
+
115
+ delegate :sky_color, to: :@sky
116
+
117
+ implementation "ProxySample", :instance do
118
+ match 'implement_bar' => 'bar'
119
+ match 'implement_foo' => 'foo'
120
+ match 'implement_increase_airport_codes' => "codecs"
121
+ match 'sky_color' => 'sky', delegated: true
122
+ match 'shhh' => 'foo'
123
+ end
124
+
125
+ def initialize
126
+ @sky = Sky.new
127
+ end
128
+
129
+ def implement_bar(name)
130
+ @airport_codes = 12
131
+ proxy.name = name
132
+ return [proxy,self]
133
+ end
134
+
135
+ def implement_foo
136
+ return [proxy,self]
137
+ end
138
+
139
+ def implement_increase_airport_codes(num)
140
+ @airport_codes = num
141
+ return self
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ it "will not be the same object for multiple API class instances" do
149
+ first = ProxySample.new(:name => "Foo")
150
+ second = ProxySample.new(:name => "Bar")
151
+
152
+ first_proxy, first_imp = first.bar("walter")
153
+ second_proxy, second_imp = second.bar("hannes")
154
+
155
+ expect(first.codecs(99).airport_codes).to eq 99
156
+ expect(second.codecs(200).airport_codes).to eq 200
157
+ #
158
+ expect(first_proxy).not_to be second_proxy
159
+ expect(first_proxy.name).to eq "walter"
160
+ expect(second_proxy.name).to eq "hannes"
161
+
162
+ expect(first_proxy.sky).to eq Sky.new.sky_color
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+
169
+ describe "defining implementation classes" do
170
+
171
+ context "api methods on instance level" do
172
+
173
+ before(:all) do
174
+ class ApiFooSample
175
+ include Seatbelt::Document
176
+ include Seatbelt::Ghost
177
+
178
+ attribute :code, Integer
179
+
180
+ interface :instance do
181
+ define :sample_method1
182
+ define :sample_method2
183
+ define :sample_method3
184
+ end
185
+
186
+ end
187
+
188
+ class ImplementationApiFooSample
189
+ include Seatbelt::Gate
190
+
191
+ attr_accessor :codec
192
+
193
+ implementation "ApiFooSample", :instance do
194
+ match 'implementation_sample_method1' => 'sample_method1'
195
+ match 'implementation_sample_method2' => 'sample_method2'
196
+ match 'implementation_sample_method3' => 'sample_method3'
197
+ end
198
+
199
+ def initialize
200
+ @codec = "mp4"
201
+ end
202
+
203
+ def implementation_sample_method1
204
+ #p self.object_id
205
+ #p proxy
206
+ #puts caller
207
+ #p "in implementation method"
208
+ #p proxy.object_id
209
+ return proxy,self, codec
210
+ end
211
+
212
+ def implementation_sample_method2
213
+ #p self.object_id
214
+ @codec = "divx"
215
+ return proxy,self, codec
216
+ end
217
+
218
+ def implementation_sample_method3
219
+ return proxy,self, codec
220
+ end
221
+
222
+ end
223
+ end
224
+
225
+ context "implementing multiple methods within the same namespace" do
226
+
227
+ it "uses the same proxy object" do
228
+ api = ApiFooSample.new(:code => 911)
229
+ api2 = ApiFooSample.new(:code => 112)
230
+
231
+ api2_proxy1,api2_object1, api_code2 = api2.sample_method1
232
+ api1_proxy1,api1_object1, api_code1 = api.sample_method1
233
+
234
+ sample_method1_proxy, sample_method1_object,code1 = api.sample_method1
235
+ sample_method2_proxy, sample_method2_object,code2 = api.sample_method2
236
+ sample_method3_proxy, sample_method3_object,code3 = api.sample_method3
237
+
238
+ expect(sample_method1_object.object_id).to eq sample_method2_object.object_id
239
+ expect(sample_method1_object).to eq sample_method3_object
240
+ expect(sample_method2_object).to eq sample_method3_object
241
+
242
+ expect(sample_method1_proxy).to eq sample_method2_proxy
243
+ expect(sample_method1_proxy).to eq sample_method3_proxy
244
+ expect(sample_method2_proxy).to eq sample_method3_proxy
245
+
246
+ expect(sample_method1_proxy.code).to eq 911
247
+
248
+ expect(code1).to eq "mp4"
249
+ expect(code2).to eq "divx"
250
+ expect(code3).to eq "divx"
251
+
252
+ end
253
+
254
+ end
255
+
256
+ context "implementing multiple methods within several namespaces" do
257
+
258
+ before(:all) do
259
+
260
+ class ApiBarSample
261
+ include Seatbelt::Ghost
262
+
263
+ interface :instance do
264
+ define :method1
265
+ end
266
+
267
+ end
268
+
269
+ ImplementationApiFooSample.class_eval do
270
+ implementation "ApiBarSample", :instance do
271
+ match 'implement_with_other_namespace' => 'method1'
272
+ end
273
+
274
+ def implement_with_other_namespace
275
+ return proxy, self
276
+ end
277
+ end
278
+
279
+ end
280
+
281
+ it "every method has its own proxy scope" do
282
+ apibar = ApiBarSample.new
283
+ api = ApiFooSample.new(:code => 911)
284
+
285
+ apibar_proxy = apibar.method1.first
286
+ api_proxy = api.sample_method1.first
287
+
288
+ expect(apibar_proxy).not_to be api_proxy
289
+ end
290
+
291
+ it "every method namespace has its own instance" do
292
+ apibar = ApiBarSample.new
293
+ api = ApiFooSample.new(:code => 911)
294
+
295
+ apibar_object = apibar.method1[1]
296
+ api_object = api.sample_method1[1]
297
+
298
+ expect(apibar_object).not_to be api_object
299
+ end
300
+ end
301
+
302
+ context "methods of superclass of the implementation class" do
303
+
304
+ before(:all) do
305
+ class ApiInheritanceSample
306
+ include Seatbelt::Ghost
307
+
308
+ interface :instance do
309
+ define :hello_world, :args => [:name]
310
+ end
311
+ end
312
+
313
+ class ImplementationSuperClass
314
+
315
+ def implementation_world(name)
316
+ "Hello #{name}!"
317
+ end
318
+
319
+ end
320
+
321
+ class ImplementationChildClass < ImplementationSuperClass
322
+ include Seatbelt::Gate
323
+
324
+ implementation "ApiInheritanceSample", :instance do
325
+ match 'implementation_world' => 'hello_world', :superclass=>true
326
+ end
327
+
328
+ end
329
+ end
330
+
331
+ it "evaluates the method defined in the superclass" do
332
+ inheritance_sample = ApiInheritanceSample.new
333
+ expected = "Hello Max!"
334
+ expect(inheritance_sample.hello_world("Max")).to eq expected
335
+ end
336
+
337
+ end
338
+ end
339
+
340
+ context "api methods on class level" do
341
+ before(:all) do
342
+ class ClassLevel
343
+ include Seatbelt::Ghost
344
+
345
+ interface :class do
346
+ define :all
347
+ end
348
+ end
349
+ class ImplementationClassLevel
350
+
351
+ include Seatbelt::Gate
352
+
353
+ implementation "ClassLevel", :class do
354
+ match 'all' => 'all'
355
+ end
356
+
357
+ def self.all
358
+ return "12"
359
+ end
360
+ #implement :all, :as => "ClassLevel.all"
361
+ end
362
+ end
363
+
364
+ it "evaluates the class method" do
365
+ expect(ClassLevel.all).to eq "12"
366
+ end
367
+ end
368
+
369
+ context "accessing implemenation attributes" do
370
+
371
+ context "with using proxy#tunnel" do
372
+
373
+ before(:all) do
374
+ class ApiB
375
+ include Seatbelt::Document
376
+ include Seatbelt::Ghost
377
+
378
+ api_method :foobar
379
+ api_method :bar
380
+ end
381
+ class ApiA
382
+ include Seatbelt::Document
383
+ include Seatbelt::Ghost
384
+
385
+ has :b, ApiB
386
+ api_method :my_delegating
387
+ api_method :init_b
388
+ end
389
+
390
+ class ImplementationA
391
+ include Seatbelt::Gate
392
+
393
+ def init_b
394
+ proxy.b = ApiB.new
395
+ end
396
+ implement :init_b,
397
+ :as => "ApiA#init_b"
398
+
399
+ def implement_my_delegating
400
+ proxy.tunnel("b.nice_looking")
401
+ end
402
+ implement :implement_my_delegating,
403
+ :as => "ApiA#my_delegating"
404
+ end
405
+
406
+ class ImplementationB
407
+ include Seatbelt::Document
408
+ include Seatbelt::Gate
409
+
410
+ attribute :nice_looking, String, :default => "Black"
411
+
412
+ def implement_foobar
413
+
414
+ end
415
+ implement :implement_foobar,
416
+ :as => "ApiB#foobar"
417
+
418
+ def implement_bar
419
+
420
+ end
421
+ implement :implement_bar,
422
+ :as => "ApiB#bar"
423
+ end
424
+ end
425
+
426
+ it "calls another implementation attribute or method" do
427
+ api = ApiA.new
428
+ api.init_b
429
+ expect(api.my_delegating).to eq "Black"
430
+ end
431
+
432
+ end
433
+
434
+ end
435
+
436
+ context "property matching" do
437
+
438
+ context "using #match_property" do
439
+
440
+ context "with string as argument" do
441
+
442
+ class PropertyFoo
443
+ include Seatbelt::Ghost
444
+ interface :instance do
445
+ define_property :foo
446
+ define_property :foobar
447
+ end
448
+ end
449
+
450
+ class ImplementationPropertyFooParent
451
+
452
+ attr_accessor :foobar
453
+
454
+ end
455
+
456
+ class ImplementationPropertyFoo < ImplementationPropertyFooParent
457
+ include Seatbelt::Gate
458
+ include Seatbelt::Document
459
+ implementation "PropertyFoo", :instance do
460
+ match_property :foobar, :superclass => true
461
+ match_property :foo
462
+ end
463
+
464
+ attr_accessor :foo
465
+
466
+ end
467
+
468
+ it "matches identical named getter and setters" do
469
+ property_foo = PropertyFoo.new
470
+ property_foo.foo = 100
471
+ property_foo.foobar = 10
472
+ expect(property_foo.foo).to eq 100
473
+ expect(property_foo.foobar).to eq 10
474
+ end
475
+
476
+ end
477
+
478
+ context "with hash as argument" do
479
+
480
+ class PropertyBar
481
+ include Seatbelt::Ghost
482
+ interface :instance do
483
+ define_property :bar
484
+ define_property :real_world
485
+ end
486
+ end
487
+
488
+ class ImplementationPropertyParent
489
+
490
+ attr_accessor :super_foo
491
+
492
+ end
493
+
494
+ class ImplementationPropertyBar < ImplementationPropertyParent
495
+ include Seatbelt::Gate
496
+ include Seatbelt::Document
497
+ implementation "PropertyBar", :instance do
498
+ match_property 'implement_bar_accessor' => 'bar'
499
+ match_property 'super_foo' => 'real_world', :superclass => true
500
+ end
501
+
502
+ attr_accessor :implement_bar_accessor
503
+
504
+ end
505
+
506
+ it "matches the explicitly named getter and setter" do
507
+ property_foo = PropertyBar.new
508
+ property_foo.bar = 100
509
+ property_foo.real_world = "England"
510
+ expect(property_foo.bar).to eq 100
511
+ expect(property_foo.real_world).to eq "England"
512
+ end
513
+
514
+ end
515
+
516
+ end
517
+
518
+ end
519
+
520
+ end
521
+ end