seat-belt 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/.travis.yml +6 -0
- data/Changelog.md +55 -0
- data/Gemfile +15 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +705 -0
- data/Rakefile +1 -0
- data/ext/.gitkeep +0 -0
- data/lib/seatbelt.rb +37 -0
- data/lib/seatbelt/collections/collection.rb +56 -0
- data/lib/seatbelt/core.rb +15 -0
- data/lib/seatbelt/core/callee.rb +35 -0
- data/lib/seatbelt/core/eigenmethod.rb +150 -0
- data/lib/seatbelt/core/eigenmethod_proxy.rb +45 -0
- data/lib/seatbelt/core/ext/core_ext.rb +0 -0
- data/lib/seatbelt/core/gate.rb +198 -0
- data/lib/seatbelt/core/ghost_tunnel.rb +81 -0
- data/lib/seatbelt/core/implementation.rb +158 -0
- data/lib/seatbelt/core/interface.rb +51 -0
- data/lib/seatbelt/core/iterators/method_config.rb +50 -0
- data/lib/seatbelt/core/lookup_table.rb +101 -0
- data/lib/seatbelt/core/pool.rb +90 -0
- data/lib/seatbelt/core/property.rb +161 -0
- data/lib/seatbelt/core/proxy.rb +135 -0
- data/lib/seatbelt/core/synthesizeable.rb +50 -0
- data/lib/seatbelt/core/terminal.rb +59 -0
- data/lib/seatbelt/dependencies.rb +5 -0
- data/lib/seatbelt/document.rb +175 -0
- data/lib/seatbelt/errors.rb +1 -0
- data/lib/seatbelt/errors/errors.rb +150 -0
- data/lib/seatbelt/gate_config.rb +59 -0
- data/lib/seatbelt/ghost.rb +140 -0
- data/lib/seatbelt/models.rb +9 -0
- data/lib/seatbelt/seatbelt.rb +10 -0
- data/lib/seatbelt/synthesizer.rb +3 -0
- data/lib/seatbelt/synthesizers/document.rb +16 -0
- data/lib/seatbelt/synthesizers/mongoid.rb +16 -0
- data/lib/seatbelt/synthesizers/synthesizer.rb +146 -0
- data/lib/seatbelt/tape.rb +2 -0
- data/lib/seatbelt/tape_deck.rb +71 -0
- data/lib/seatbelt/tapes/tape.rb +105 -0
- data/lib/seatbelt/tapes/util/delegate.rb +56 -0
- data/lib/seatbelt/translator.rb +66 -0
- data/lib/seatbelt/version.rb +3 -0
- data/seatbelt.gemspec +27 -0
- data/spec/lib/seatbelt/core/eigenmethod_spec.rb +102 -0
- data/spec/lib/seatbelt/core/gate_spec.rb +521 -0
- data/spec/lib/seatbelt/core/ghost_tunnel_spec.rb +21 -0
- data/spec/lib/seatbelt/core/lookup_table_spec.rb +234 -0
- data/spec/lib/seatbelt/core/pool_spec.rb +270 -0
- data/spec/lib/seatbelt/core/proxy_spec.rb +108 -0
- data/spec/lib/seatbelt/core/terminal_spec.rb +184 -0
- data/spec/lib/seatbelt/document_spec.rb +287 -0
- data/spec/lib/seatbelt/gate_config_spec.rb +98 -0
- data/spec/lib/seatbelt/ghost_spec.rb +568 -0
- data/spec/lib/seatbelt/synthesizers/document_spec.rb +47 -0
- data/spec/lib/seatbelt/synthesizers/mongoid_spec.rb +134 -0
- data/spec/lib/seatbelt/synthesizers/synthesizer_spec.rb +112 -0
- data/spec/lib/seatbelt/tape_deck_spec.rb +180 -0
- data/spec/lib/seatbelt/tapes/tape_spec.rb +115 -0
- data/spec/lib/seatbelt/translator_spec.rb +108 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/implementations/seatbelt_environment.rb +19 -0
- data/spec/support/shared_examples/shared_api_class.rb +7 -0
- data/spec/support/shared_examples/shared_collection_child.rb +7 -0
- data/spec/support/worlds/eigenmethod_world.rb +7 -0
- metadata +205 -0
@@ -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
|
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
|