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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 54679b016a9bdcf5c7c866fbeee5c5a0994f5205
4
+ data.tar.gz: adec3307b740eeb81d088ca8dddc9640164bb0f3
5
+ SHA512:
6
+ metadata.gz: a514352e359790c24860cc10d9de19179da3305188ce928c1965e2f764eaee19493691ecfbb8d97ef9790e117bd22a9ecdd0551e56b74ec5ac4254460bef0adb
7
+ data.tar.gz: 29e9cc0326deca84d5d34d7f5e3649d7d3f9ba117434d2864b5ebe51665f03b37c13dec3e909bef7aa45db2aed3e63f7e6f37d7144f200ffb793d6c45c103c0c
data/.gitignore ADDED
@@ -0,0 +1,29 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ rspec_results.html
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .tags
20
+
21
+ # RVM
22
+ .ruby-version
23
+ .ruby-gemset
24
+ .rvmrc
25
+
26
+ # OSX
27
+ .DS_Store
28
+
29
+ *.tags*
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.2.0
5
+ - 2.2.3
6
+ script: rspec spec/
data/Changelog.md ADDED
@@ -0,0 +1,55 @@
1
+ ## v0.10.0 (2014-01-17)
2
+
3
+ * Add configuration for Translator the specify custom values
4
+ * Rename TravelAgent to Translator
5
+ * Remove Seatbelt::Collections::Collection subclasses
6
+ * Remove Seatbelt::Models
7
+ * Support mass assignment of properties and (if present) attributes
8
+
9
+ ## v0.4.2 (2013-12-17)
10
+
11
+ * Include superclass method implementations to match directive
12
+ * Add property defining and matching
13
+
14
+ ## v0.4.1 (2013-11-19)
15
+
16
+ * Change internal API to Virtus 1.0.0
17
+ * Fixes an issue that prevents class methods used as implementation methods
18
+ * Add #interface and #implementation directives to have a cleaner API
19
+ * Add support for specifying arguments to a API method definition.
20
+ * Add tunneling from API class instances to implementation class instances
21
+ * Add tunneling of private API properties
22
+ * Add synthesize support on instance level between two objects.
23
+
24
+ ## v0.4.0 (2013-30-09)
25
+
26
+ * New internal API method delegating by introducing an Eigenmethod object
27
+
28
+ ## v0.3.3 (2013-09-25)
29
+
30
+ * Fixes an error that raises TypeMissmatchError if multiple has_many is used
31
+ * Fixes assigning identical proxy object to multiple implementation classes.
32
+
33
+ ## v0.3.2 (2013-09-24)
34
+
35
+ * API class methods are now delegatable to implementation class methods
36
+
37
+ ## v0.3.1 (2013-09-17)
38
+
39
+ * Add tapes support for TQL
40
+
41
+ ## v0.3.0 (2013-09-06)
42
+
43
+ * Add association support to Seatbelt::Document (has_many, has)
44
+
45
+ ## v0.2.0 (2013-08-27)
46
+
47
+ * Add Dynamic Proxy pattern to have direct access to proxy objects klass methods
48
+ * Add validations of attributes to Seatbelt::Document
49
+
50
+ ## v0.1.0 (2013-08-21)
51
+
52
+ * Add API method definition
53
+ * Add forward declaration of API methods to implementation classes
54
+ * Add proxy object to access API class or instance in implementation methods
55
+ * Add attributes to Seatbelt::Document classes
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in seatbelt.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "rspec"
8
+ gem "mongoid", "~>3.1.5"
9
+ end
10
+
11
+ group :development do
12
+ gem "step-up", "~> 0.9.1"
13
+ gem 'guard-rspec'
14
+ gem 'ruby_gntp'
15
+ end
data/Guardfile ADDED
@@ -0,0 +1,13 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Turnip features and steps
10
+ watch(%r{^spec/acceptance/(.+)\.feature$})
11
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
12
+ end
13
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Daniel Schmidt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,705 @@
1
+ ![Seatbelt](http://datenspiel.s3.amazonaws.com/vagalo/seatbelt_top.jpg)
2
+ # Seatbelt
3
+
4
+ Tool to define implementation interfaces that should be decoupled from their
5
+ implementations. (A Ruby header file approach.)
6
+
7
+ ## Development instructions
8
+
9
+ ### Install RSpec Git Pre-commit hook
10
+
11
+ To get a working testsuite before committing code to the repository you have to install a Git pre-commit hook that prevends you from committing unless your specs passed or are pending.
12
+
13
+ If you haven't <code>wget</code> installed already, install it using homebrew:
14
+
15
+ ```
16
+ brew install wget
17
+ ```
18
+
19
+ Install the hook:
20
+
21
+ ```
22
+ wget -O .git/hooks/pre-commit https://raw.github.com/markhazlett/RSpec-Pre-commit-Git-Hook/master/rspec-precommit
23
+ ```
24
+
25
+ After installing, call
26
+
27
+ ```
28
+ chmod +x .git/hooks/pre-commit
29
+ ```
30
+
31
+ to make it executable.
32
+
33
+ ### Testing
34
+
35
+ Although running the testsuite with [Guard](http://guardgem.org/) I highly recommened the
36
+ [RubyTest](https://sublime.wbond.net/packages/RubyTest) Sublime Text package.
37
+
38
+
39
+ ## Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ gem 'seatbelt'
44
+
45
+ And then execute:
46
+
47
+ $ bundle
48
+
49
+ Or install it yourself as:
50
+
51
+ $ gem install seatbelt
52
+
53
+ ## Usage
54
+
55
+ ### Defining API classes and API meta-methods
56
+
57
+ Defining classes that act like API classes is a very simple step. Define a plain
58
+ Ruby class and include the
59
+
60
+ ```ruby
61
+ Seatbelt::Ghost
62
+ ```
63
+
64
+ module.
65
+
66
+ That gives you access to the ```interface``` class method. API meta-methods are not implemented, they will only be defined. Be sure that you have a specification of these methods like passing how many arguments and if a block is required.
67
+
68
+ ```interface``` takes an argument that defines which object level the method should be
69
+ callable at (The implementation class has to define a matcher for this. See below.)
70
+
71
+ ```ruby
72
+ class Hotel
73
+ include Seatbelt::Gate
74
+
75
+ interface :class do
76
+ define :find_nearby,
77
+ :args => [:options]
78
+ end
79
+
80
+ end
81
+ ```
82
+
83
+ ```define``` expects at least one argument, it's method name. The second argument is optional but has to be a ```Hash```.
84
+
85
+ If your API meta-method expects arguments you have to specify the list of arguments.
86
+
87
+ ```ruby
88
+ define :find_nearby,
89
+ :block_required => false,
90
+ :args => [:options]
91
+ ```
92
+
93
+ With this, a class method ```find_nearby``` is defined at the ```Hotel``` class. Calling that method is straight forward:
94
+
95
+ ```ruby
96
+ Hotel.find_nearby(:city => "London") # returns all Hotels near London
97
+ ```
98
+
99
+ To define an instance API meta-method at the ```Hotel``` class, just pass ```:instance``` to ```:interface``` method:
100
+
101
+ ```ruby
102
+ interface :instance do
103
+ define :number_of_rooms_with_tv_sets
104
+ end
105
+ ```
106
+
107
+ And, like above, calling is just pure Ruby:
108
+
109
+ ```ruby
110
+ hotel = Hotel.new
111
+ hotel.number_of_rooms_with_tv_sets
112
+ ```
113
+
114
+ If your API meta-method specification says that the method requires a block, just pass truthy
115
+ to the ```:block_required``` key.
116
+
117
+ A note about method argument specification:
118
+
119
+ ```ruby
120
+ define :foo,
121
+ :args => ["name", "*args", "&block"]
122
+ ```
123
+
124
+ expects that you have an implementation body - let's say - like this:
125
+
126
+ ```
127
+ def implement_foo(name, *args, &block)
128
+ # ...
129
+ end
130
+ ```
131
+
132
+ **Defining API properties**
133
+
134
+ If the API class should provide properties use the ```define_property``` for single property or ```define_properties``` for multiple property definiton.
135
+
136
+ ```ruby
137
+ interface :instance do
138
+ define_property :foo
139
+ define_properties :bar, :foobar
140
+ end
141
+ ```
142
+
143
+ If you want to update a bunch of properties at once, you must mark them as accessible.
144
+
145
+ ```ruby
146
+ interface :instance do
147
+ define_property :foo
148
+ define_properties :bar, :foobar
149
+
150
+ property_accessible :foo, :bar
151
+ end
152
+
153
+
154
+ foo = Foo.new
155
+
156
+ foo.properties = { :foo => 12, :bar => "glasses", :foobar => "of beer." }
157
+ ```
158
+
159
+ In the example above, the ```foobar``` property will not be set, because it's not marked as accessible.
160
+
161
+ ### Implementing API meta-methods
162
+
163
+ Defining API meta-methods is simple, but how do you implement the logic of these methods? Use a plain Ruby class and include the ```Seatbelt::Gate``` module.
164
+
165
+ Then it's possible to associate the implemention method with the API meta-method definition.
166
+
167
+ ```ruby
168
+ class ImplementHotel
169
+ include Seatbelt::Gate
170
+
171
+ implementation "Hotel", :class do
172
+ match 'hotels_nearby_city' => 'find_nearby'
173
+ match 'all' => 'all'
174
+ end
175
+
176
+ def hotels_nearby_city(options={})
177
+ # ...
178
+ end
179
+
180
+ def self.all
181
+ # ....
182
+ end
183
+
184
+ end
185
+ ```
186
+
187
+ The ```implementation``` directive takes two arguments. The first one is the method name of the API class, the second one is the object level the implementation is defined on (```:instance```, ```:class```).
188
+
189
+ The ```match``` directive takes an Hash containing the implementation method name as key and the API method as value.
190
+
191
+ ```ruby
192
+ class ImplementHotel
193
+ include Seatbelt::Gate
194
+
195
+ # Class methods.
196
+ implementation "Hotel", :class do
197
+ match 'hotels_nearby_city' => 'find_nearby'
198
+ end
199
+
200
+ # Instance methods.
201
+ implementation "Hotel", :instance do
202
+ match 'rooms_with_tv' => 'number_of_rooms_with_tv_sets'
203
+ end
204
+
205
+ def hotels_nearby_city(options={})
206
+ # ...
207
+ end
208
+
209
+ def rooms_with_tv
210
+ #....
211
+ end
212
+
213
+ end
214
+ ```
215
+
216
+ With the ```match``` directive it is also possible to bind an implementation method from the superclass to the given interface class:
217
+
218
+ ```ruby
219
+
220
+ class MetaBook
221
+ # An implementation method used for all childs of MetaBook
222
+ def general_stuff_for_books
223
+ #...
224
+ end
225
+ end
226
+
227
+ class ImplementationNovel < MetaBook
228
+ include Seatbelt::Gate
229
+
230
+ implementation "Novel", :instance do
231
+ match 'general_stuff_for_books' => 'publisher', :superclass => true
232
+ end
233
+
234
+ end
235
+
236
+ ```
237
+
238
+ Note that this is only possible for ```:instance``` level implementations.
239
+
240
+ (Thanks to @LarsM for suggesting this feature.)
241
+
242
+ **Using delegated methods**
243
+
244
+ If you want to use a method as implementation method that is delegated to another object you can mark these methods as
245
+ delegated and Seatbelt will recognize them too.
246
+
247
+ ```ruby
248
+ class BookImplementation
249
+ delegate :distributor_name, to: :@distributor
250
+
251
+ implementation "Book", :instance do
252
+ match 'distributor_name' => 'distributor', delegated: true
253
+ end
254
+ end
255
+ ```
256
+
257
+ **Implementing API properties**
258
+
259
+ Similiar to ```match``` associate the properties of your implementation class and your API class with ```match_property```.
260
+
261
+ ```ruby
262
+ # Having an identical property wthin the interface.
263
+ implementation "Book", :instance do
264
+ match_property 'author'
265
+ end
266
+
267
+ # Property names differ
268
+ implementation "Book", :instance do
269
+ match_property 'implementation_title' => 'title'
270
+ end
271
+
272
+ # Property is defined in the superclass
273
+ implementation "Novel", :instance do
274
+ match_property :publisher, :superclass => true
275
+ end
276
+ ```
277
+
278
+
279
+ ### Accessing the API class in implementations of API meta-methods
280
+
281
+ You can access the API class within the implementation methods through the
282
+ ```proxy``` object.
283
+
284
+ Depending the type of method you are implementing, ```proxy``` will be a different scope:
285
+
286
+ * implementing a later class method, ```proxy``` is the class of the API class defined in ```implementation```
287
+ * implementing a later instance method, ```proxy``` is the instance of the API class defined in ```implementation```
288
+
289
+ The ```proxy``` object provides a ```call``` method to access the ```proxy``` methods. It expects the method name to call, an argument list and an optional block.
290
+
291
+ ```ruby
292
+ def rooms_with_tv
293
+ excluded_rooms = proxy.call(:second_floor)
294
+ room_criteria = proxy.call(:criteria, :not => :gallery)
295
+ # ....
296
+ end
297
+ ```
298
+
299
+ ```#call``` could be omitted and message send directly to the API class or instance receiver.
300
+
301
+ ```ruby
302
+ def rooms_with_tv
303
+ excluded_rooms = proxy.second_floor
304
+ room_criteria = proxy.criteria(:not => :gallery)
305
+ end
306
+ ```
307
+
308
+ **Hint**: Chaining API classes is possible by returning ```proxy.object``` from the implementation method.
309
+
310
+ **Note:** If you want to delegate a class method of your implementation class to a class API method by using the ```:type``` key, the proxy object acts as the class object of the API class.
311
+
312
+ ### Defining attributes in API classes
313
+
314
+ You can define attributes within an API class by including the ```Seatbelt::Document```module.
315
+
316
+ ```ruby
317
+ class Airport
318
+ include Seatbelt::Ghost
319
+ include Seatbelt::Document
320
+
321
+ attribute :name, String
322
+ attribute :lat, Float
323
+ attribute :lng, Float
324
+
325
+ interface :instance do
326
+ define :identifier
327
+ end
328
+
329
+ end
330
+ ```
331
+
332
+ To access the attributes within an implementation class use the ```proxy``` and it's ```call``` method.
333
+
334
+ For more informations about attributes see the [Virtus](https://github.com/solnic/virtus) project.
335
+
336
+ **A note about attributes and Mongoid**: If you include ```mongoid``` in your project and any ```attribute``` should be a boolean, you have use the full path to the type:
337
+
338
+ ```ruby
339
+ attribute :is_read, Virtus::Attribute::Boolean
340
+ ```
341
+
342
+ For further informations see: https://github.com/solnic/virtus/issues/132#issuecomment-11611142
343
+
344
+
345
+ ### Defining associations between objects
346
+
347
+ Associations between objects is possible in two ways if ```Seatbelt::Document``` is included in the class.
348
+
349
+ **one-to-many**
350
+
351
+ ```ruby
352
+ module Seatbelt
353
+ module Models
354
+ class Airport
355
+ include Seatbelt::Document
356
+
357
+ has_many :flights, Seatbelt::Models::Flight
358
+
359
+ end
360
+ end
361
+ end
362
+ ```
363
+
364
+ A ```has_many``` relationship definition takes two arguments:
365
+
366
+ * the association name
367
+ * the class used for a single item in the relationship collection
368
+
369
+ That creates an accessor method acts like an ```Array```.
370
+
371
+ You can add items to the collection (association) in two ways:
372
+
373
+ *instance level*
374
+
375
+ ```ruby
376
+ airport = Seatbelt::Models::Airport.where(:name => "London Stansted")
377
+ Flights.find_to(airport.name).each do |flight|
378
+ aiport.flights << flight
379
+ end
380
+ ```
381
+
382
+ *attribute level*
383
+
384
+ ```ruby
385
+ airport = Seatbelt::Models::Airport.where(:name => "London Stansted")
386
+ GetFromTheInternet.fetch_flights.each do |flight|
387
+ aiport.flights << {:number => flight["fn_num"], :return_flight_at => flight["RRUECK"]}
388
+ end
389
+ ```
390
+
391
+ **one-to-one**
392
+
393
+ ```ruby
394
+ module Seatbelt
395
+ module Models
396
+ class Hotel
397
+ include Seatbelt::Document
398
+
399
+ has :region
400
+ end
401
+ end
402
+ end
403
+ ```
404
+
405
+ A ```has``` association takes two arguments where the last one is optional.
406
+
407
+ * the association name
408
+ * the class used for the assocation
409
+
410
+ If the second argument is omitted ```has``` guessed the corrosping model class. In the example above it will use the
411
+ ```Seatbelt::Models::Region``` class.
412
+
413
+ You can assign an object to the association the same way as assigning an attribute.
414
+
415
+ ### Synthesize objects
416
+
417
+ Synthesizing objects is only available at the instance level. By now Seatbelt will only synthesize the state of an object, not its behaviour!
418
+
419
+ To synthesize an implementation class instance and the proxy object, add ```synthesize``` to the Implementation class.
420
+
421
+ ```ruby
422
+ class ImplementationAirport
423
+ include Seatbelt::Gate
424
+ include Mongoid::Document
425
+
426
+ field :name, :type => String
427
+ field :lat, :type => Float
428
+ field :lng, :type => Float
429
+
430
+ synthesize :from => "Seatbelt::Models::Airport",
431
+ :adapter => "Seatbelt::Synthesizers::Mongoid"
432
+ end
433
+ ```
434
+
435
+ Then - every time ```proxy.[attribute_name]``` is changed within the implementation class - the instance of the implementation class is changed too. And vice versa:
436
+
437
+ ```ruby
438
+ aiport = Seatbelt::Models::Airport.new(:name => "London Stansted")
439
+ # in implementation class self.name will be "London Stansted"
440
+
441
+ # in a implementation method
442
+ def something
443
+ proxy.name = "London Gatewick"
444
+ p self.name # => "London Gatewick"
445
+ end
446
+ ```
447
+
448
+ If attribute names are the same on both sides, all is fine. If not, the implementation class has to implement a ```synthesize_map``` method where the keys are the attributes from the ```API class``` and the values are attributes from the ```implementation class```.
449
+
450
+ ```ruby
451
+ class ImplementAirport
452
+ include Seatbelt::Gate
453
+ include Mongoid::Document
454
+
455
+ field :l_name, :type => String
456
+ field :gidd_lat, :type => Float
457
+ field :gidd_lng, :type => Float
458
+
459
+ synthesize :from => "Seatbelt::Models::Airport",
460
+ :adapter => "Seatbelt::Synthesizers::Mongoid"
461
+
462
+ synthesize_map :name => :l_name, :lat => :gidd_lat, :lng => :gidd_lng
463
+ end
464
+ ```
465
+
466
+ ### Defining custom synthesizers
467
+
468
+ Seatbelt provides to synthesizers:
469
+
470
+ * ```Seatbelt::Synthesizers::Document```
471
+ * ```Seatbelt::Synthesizers::Mongoid```
472
+
473
+ The first one synthesizes ```Seatbelt::Document``` or ```Virtus``` based implementation classes. The second one synthesizes ```Mongoid::Document``` based implementation classed.
474
+
475
+ Defining custom synthesizers helpful, if
476
+
477
+ * the implementation class uses a not supported backend
478
+ * only a few attributes should be synthesized that exists on both sides
479
+
480
+ A Synthesizer is a plain Ruby class which includes the ```Seatbelt::Synthesizer``` module and implements a ```synthesizable_attributes``` method.
481
+
482
+ ```ruby
483
+ class CustomSynthesizer
484
+ include Seatbelt::Synthesizer
485
+
486
+ def synthesizable_attributes
487
+ [:l_name, :gidd_lat]
488
+ end
489
+ end
490
+ ```
491
+
492
+ Which only synthesizes the ```:l_name``` and ```:gidd_lat``` properties.
493
+
494
+ ### Tunneling from API class instances to implementation class instances
495
+
496
+ Any API class that implements Seatbelt::Ghost can have access to its
497
+ implementation class instance. This behaviour has to be enabled before using
498
+ because its a violation of the Public/Private API approach.
499
+
500
+ (**And yes - in Ruby private methods are not really private methods.**)
501
+
502
+ Accessing the implementation instance is only available after the API Class
503
+ was instantiated.
504
+
505
+ Example:
506
+
507
+ ```ruby
508
+ class Hotel
509
+ include Seatbelt::Ghost
510
+
511
+ enable_tunneling! # access to the implementation instance is now
512
+ # possible.
513
+
514
+ end
515
+
516
+ class ImplementationHotel
517
+ include Seatbelt::Document
518
+ include Seatbelt::Gate
519
+
520
+ attribute :ignore_dirty_rooms, Boolean
521
+
522
+ end
523
+
524
+ hotel = new Hotel
525
+ hotel.tunnel(:ignore_dirty_rooms=,false)
526
+ ```
527
+
528
+ Passing blocks is also available if the accessed method supports blocks
529
+
530
+ ```ruby
531
+ class ImplementationHotel
532
+ include Seatbelt::Document
533
+ include Seatbelt::Gate
534
+
535
+ attribute :ignore_dirty_rooms, Boolean
536
+
537
+ def filter_rooms(sections)
538
+ rooms = self.rooms.map{|room| sections.include?(room_type)}
539
+ yield(rooms)
540
+ end
541
+ end
542
+
543
+ hotel.tunnel(:filter_rooms, ["shower, kitchen"]) do |rooms|
544
+ rooms.select do |room|
545
+ # do something
546
+ end
547
+ end
548
+ ```
549
+
550
+ **Note** that this is a dangerous approach and should be avoided. If you change the implementation layer and you are using tunneling from API classes to Implementation classes you have to make sure that the new implementation layer provides the attribute or
551
+ method you are tunneling to with your API class instance.
552
+
553
+ To disable tunneling just call the ```disable_tunneling!``` class method.
554
+
555
+ ### Man ... we need a translator here
556
+
557
+ > "The Babel fish," said The Hitchhiker's Guide to the Galaxy quietly, "is small, yellow and leech-like, and probably the oddest thing in the Universe. It feeds on brainwave energy received not from its own carrier but from those around it. It absorbs all unconscious mental frequencies from this brainwave energy to nourish itself with. It then excretes into the mind of its carrier a telepathic matrix formed by combining the conscious thought frequencies with nerve signals picked up from the speech centres of the brain which has supplied them. The practical upshot of all this is that if you stick a Babel fish in your ear you can instantly understand anything in any form of language. The speech patterns you actually hear decode the brainwave matrix which has been fed into your mind by your Babel fish.
558
+
559
+ *(The Hitchhiker's Guide to the Galaxy)*
560
+
561
+ Let's talk about basics of the TQL (Translation Query Language). TQL is basically a query formed in a natural sentence (language doesn't matter) after a defined syntax.
562
+
563
+ TQL basic support is implemented in Seatbelt v0.4. Basic support means that you are able to implement your own translations and tapes.
564
+
565
+ **Tapes?**
566
+
567
+ A tape is collection of ```translate``` blocks that will translate a query into some business logic. A tape is added to a tape deck that will play the corrosponding tape section to a given query (or better said - call the translation of this query).
568
+
569
+ Let's take a closer look at what a tape deck is and what tapes are.
570
+
571
+ Any class could act as a tape deck by including the ```Seatbelt::TapeDeck``` module. Mostly that classes are also including the ```Seatbelt::Document``` module.
572
+
573
+ Adding the ```Seatbelt::TapeDeck``` module to a class gives the class the opportunity to
574
+
575
+ * add tapes to the class
576
+ * answer a query with a translation defined within a tape
577
+
578
+ Before diving into some example code, let's record a tape. As mentioned above, a tape is just a bunch of translation blocks defining the query syntax and an associated logic implementation.
579
+
580
+ To have translation implemented or defined you should be familiar with regular expressions, the core tool of a tape.
581
+
582
+ A tape class inherits from ```Seatbelt::Tape``` and is very simple to implement.
583
+
584
+ ```ruby
585
+ class PubTape < Seatbelt::Tape
586
+
587
+ translate /Gimme (\d+) beers!/ do |sentence, count_of_beer|
588
+ #
589
+ end
590
+
591
+ end
592
+ ```
593
+
594
+ A ```translate```block takes arguments. If your argument list to the ```translate``` block has only one item, this item is the first matched value from your sentence (or if the query didn't expect any matched value then it's the original query sentence).
595
+
596
+ Anyhow - every match marked with your regular expression is passed do the ```translate``` block if there are enough arguments defined.
597
+
598
+ ```ruby
599
+ translate /Gimme (\d+) beers!/ do |sentence, count_of_beer|
600
+ # sentence is original query sentence
601
+ # count_of_beer is the value matched by (\d+)
602
+ end
603
+ ```
604
+
605
+ **Note:** Any argument passed to the block is passed as String. So you have to take care
606
+ of type casts.
607
+
608
+ To have access to the tape's translation block, a tape has to be assigned to a tape deck.
609
+
610
+ ```ruby
611
+ class Pub
612
+ include Seatbelt::Document
613
+ include Seatbelt::Tape
614
+
615
+ use_tape PubTape
616
+ end
617
+ ```
618
+
619
+ Also possible:
620
+
621
+ ```ruby
622
+ Pub.add_tape PubTape
623
+ ```
624
+
625
+ Having more than one tape:
626
+
627
+ ```ruby
628
+ class Pub
629
+ include Seatbelt::Document
630
+ include Seatbelt::Tape
631
+
632
+ use_tapes PubTape,
633
+ AnotherTape
634
+ end
635
+ ```
636
+
637
+ Calling ```Pub.answer("Gimme 4 beers!")``` will call the corrosponding translation block.
638
+
639
+ **Calling other tapes from a tape**
640
+
641
+ To call another tape or a specific translation of a tape use the ```play_tape``` method within the ```translate```block.
642
+
643
+ ```ruby
644
+ class CreditCardTape < Seatbelt::Tape
645
+
646
+ translate /Charge the credit card with (\d+) Euro./ do |amount|
647
+ # do something
648
+ end
649
+
650
+ end
651
+
652
+ class PubTape < Seatbelt::Tape
653
+
654
+ translate /Gimme (\d+) beers!/ do |sentence, count_of_beer|
655
+ overall_costs = play_tape(:section => "Want the bill for #{count_of_beer} beer")
656
+ play_tape(CreditCardTape, :section => "Charge the credit card with #{overall_costs} Euro.")
657
+ end
658
+
659
+ translate /Want the bill for (\d+) beer/ do |beer_amount|
660
+ costs_of_beer = 2
661
+ sum = 2 * beer_amount.to_i
662
+ sum
663
+ end
664
+
665
+ end
666
+ ```
667
+
668
+ Note the difference between the two ```play_tape``` calls.
669
+
670
+ With the ```tape_deck``` object within your translate block you have access to the associated tape deck class (not instance).
671
+
672
+ ### Translator
673
+
674
+ By knowing what tapes and tape decks are, it's easy to understand what the Translator is doing.
675
+
676
+ The Translator takes the query and delegates the query to the responsible model.
677
+
678
+ ```ruby
679
+ Translator.tell_me "Hotel: 3 persons want to travel for 10 days beginning at next friday to Finnland."
680
+ ```
681
+
682
+ Delegates the query ```3 persons want to travel for 10 days beginning at next friday to Finnland.``` to the ```Hotel``` model.
683
+
684
+ The model declaration can be ommitted, if this is done, the query is delegated to the
685
+ model that is defined in the ```Translator.setup```:
686
+
687
+ ```ruby
688
+ Seatbelt::Translator.setup do |c|
689
+ c.namespace = "Seatbelt::Models::"
690
+ c.default_model_class = "Offer"
691
+ end
692
+ ```
693
+
694
+ That will call ```Seatbelt::Models::Offer``` if no model declaration is given within a query.
695
+
696
+
697
+ Define your tapes in ```lib/seatbelt/tapes```!
698
+
699
+ ## Contributing
700
+
701
+ 1. Fork it
702
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
703
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
704
+ 4. Push to the branch (`git push origin my-new-feature`)
705
+ 5. Create new Pull Request