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,161 @@
|
|
1
|
+
module Seatbelt
|
2
|
+
module Property
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
# Public: Defines an API property. This is only working if its called within
|
6
|
+
# the #interface method block.
|
7
|
+
#
|
8
|
+
# Wraps Seatbelt::Pool::Api#api_method with a getter and setter method name.
|
9
|
+
#
|
10
|
+
# *args - An argument list containing:
|
11
|
+
# name - Name of the API property (required)
|
12
|
+
# options - An optional options Hash containing:
|
13
|
+
# :accessible - Property is mass assignable
|
14
|
+
# (defaults to false)
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# class Book
|
19
|
+
# interface :instance do
|
20
|
+
# define_property :author
|
21
|
+
# define_property :title
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
def define_property(*args)
|
25
|
+
if @scope.eql?(:class)
|
26
|
+
raise Seatbelt::Errors::PropertyOnClassLevelDefinedError
|
27
|
+
end
|
28
|
+
hsh = {}
|
29
|
+
options = args.extract_options!
|
30
|
+
accessible = options.fetch(:accessible, false)
|
31
|
+
name = args.pop
|
32
|
+
hsh[:scope] = @scope
|
33
|
+
|
34
|
+
self.send(:api_method, name, hsh)
|
35
|
+
hsh[:args] = [:value]
|
36
|
+
self.send(:api_method, :"#{name}=", hsh)
|
37
|
+
|
38
|
+
property_list << name
|
39
|
+
self.send(:property_accessible, name) if accessible
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Mass defining of properties.
|
44
|
+
#
|
45
|
+
# See #define_property for details
|
46
|
+
#
|
47
|
+
# - properties - A list of property names to define.
|
48
|
+
#
|
49
|
+
def define_properties(*properties)
|
50
|
+
properties.each { |property| define_property(property) }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: All properties that are marked as accessible.
|
54
|
+
#
|
55
|
+
# Returns the property list or an empty Array.
|
56
|
+
def accessible_properties
|
57
|
+
@accessible_properties ||= []
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Defines one or more properties to be mass assignable due
|
61
|
+
# the #properties= setter method.
|
62
|
+
#
|
63
|
+
# If the class implements an #attributes method (e.g. coming from
|
64
|
+
# Seatbelt::Document) these attributes are includeable in this list too.
|
65
|
+
#
|
66
|
+
# properties - A list of property names. Requires at least one property.
|
67
|
+
#
|
68
|
+
def property_accessible(*properties)
|
69
|
+
properties.each do |property|
|
70
|
+
attribute_defined = false
|
71
|
+
if self.respond_to?(:attributes)
|
72
|
+
if self.attribute_set.map(&:name).include?(property)
|
73
|
+
attribute_defined = true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
unless attribute_defined
|
77
|
+
unless property_list.include?(property) && !attribute_defined
|
78
|
+
raise Seatbelt::Errors::PropertyNotDefinedYetError.new(property)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
accessible_properties << property
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Internal: All properties of the class defined with #define_property.
|
88
|
+
#
|
89
|
+
# Returns the the properties as Array or an empty Array.
|
90
|
+
def property_list
|
91
|
+
@property_list ||= []
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
module InstanceMethods
|
97
|
+
|
98
|
+
require 'active_support/core_ext/hash/keys'
|
99
|
+
|
100
|
+
# Public: Gets a list of key-value pairs that includes the properties
|
101
|
+
# with its values.
|
102
|
+
#
|
103
|
+
# role - The scope of properties that will be selected
|
104
|
+
# Defaults to :accessibles which will only select properties that
|
105
|
+
# are defined as accessible.
|
106
|
+
#
|
107
|
+
# Returns an Hash containing the property names and its values.
|
108
|
+
def properties(role = :accessibles)
|
109
|
+
injector = lambda do |result, item|
|
110
|
+
result[item] = self.send(item)
|
111
|
+
result
|
112
|
+
end
|
113
|
+
list = case role
|
114
|
+
when :accessibles then
|
115
|
+
self.class.accessible_properties.inject({}, &injector)
|
116
|
+
when :all then
|
117
|
+
all = self.class.send(:property_list).inject({}, &injector)
|
118
|
+
all.merge!(attributes) if self.respond_to?(:attributes)
|
119
|
+
all
|
120
|
+
else
|
121
|
+
{}
|
122
|
+
end
|
123
|
+
list.with_indifferent_access
|
124
|
+
end
|
125
|
+
|
126
|
+
# Public: Sets a bunch of properties that are marked as accessible.
|
127
|
+
#
|
128
|
+
# property_hsh - The Hash containing the property name and the property
|
129
|
+
# value.
|
130
|
+
#
|
131
|
+
# Examples
|
132
|
+
#
|
133
|
+
# class Car
|
134
|
+
# include Seatbelt::Ghost
|
135
|
+
#
|
136
|
+
# interface :instance do
|
137
|
+
# define_property :wheels
|
138
|
+
# define_property :color
|
139
|
+
#
|
140
|
+
# property_accessible :color
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# car = Car.new
|
145
|
+
# car.properties = { :color => "yellow", :wheels => 3 }
|
146
|
+
#
|
147
|
+
# car.color # => 'yellow' because it's accessible
|
148
|
+
# car.wheels # => nil because it's not accessible
|
149
|
+
#
|
150
|
+
def properties=(property_hsh)
|
151
|
+
property_hsh.each do |property_key, property_value|
|
152
|
+
if self.class.accessible_properties.include?(property_key) || \
|
153
|
+
self.class.accessible_properties.map(&:to_s).include?(property_key)
|
154
|
+
self.send("#{property_key}=", property_value)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Seatbelt
|
2
|
+
|
3
|
+
# Public: A Seatbelt::Proxy class is a shadow of any class that includes
|
4
|
+
# Seatbelt::Gate.
|
5
|
+
# It provides access to the API class or instance of the API class and
|
6
|
+
# implements the DynamicProxy pattern.
|
7
|
+
#
|
8
|
+
# A Proxy class provides a private dynamic accessor #klass that changes with
|
9
|
+
# the implementation scope of the API methods implementation.
|
10
|
+
#
|
11
|
+
# Let's show this by example:
|
12
|
+
#
|
13
|
+
# class ApiClass
|
14
|
+
# include Seatbelt::Ghost
|
15
|
+
#
|
16
|
+
# api_method :an_instance_method_to_implement
|
17
|
+
# api_method :a_class_method_to_implement
|
18
|
+
# def helper(a,b,c)
|
19
|
+
# #....
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def self.c_helper(options={})
|
23
|
+
# # ...
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# class ApiClassImplementation
|
28
|
+
#
|
29
|
+
# def instance_implementation
|
30
|
+
# local = proxy.call(:helper, 1,2,4) # proxy scope is instance of ApiClass
|
31
|
+
# end
|
32
|
+
# implement :instance_implementation,
|
33
|
+
# :as => "ApiClass#an_instance_method_to_implement"
|
34
|
+
#
|
35
|
+
# def class_implementation
|
36
|
+
# local = proxy.call(:c_helper, {:foo => 19})
|
37
|
+
# # proxy scope is ApiClass
|
38
|
+
# end
|
39
|
+
# implement :class_implementation,
|
40
|
+
# :as => "ApiClass.a_class_method_to_implement"
|
41
|
+
# end
|
42
|
+
class Proxy
|
43
|
+
|
44
|
+
NOT_ALLOWABLE_CALLS_ON_OBJECT = %w{ call object klass tunnel }
|
45
|
+
|
46
|
+
# Public: Send a method message to the current #klass scope receiver.
|
47
|
+
# See class documentation section for further informations about #klass.
|
48
|
+
#
|
49
|
+
# method_name - The method to call
|
50
|
+
# *args - The methods argument list
|
51
|
+
# &block - An optional block that should be passed to the callable
|
52
|
+
# method
|
53
|
+
#
|
54
|
+
# Returns the return value of the callable method or raises a
|
55
|
+
# NoMethodError.
|
56
|
+
def call(method_name, *args, &block)
|
57
|
+
object.send(method_name,*args,&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def object
|
61
|
+
self.send(:klass)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: Calls a private API attribute or method of an object defined
|
65
|
+
# in a API class.
|
66
|
+
#
|
67
|
+
# It is only working on a second level.
|
68
|
+
#
|
69
|
+
# chain - The attribute call chain as String.
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
#
|
73
|
+
# class ApiA
|
74
|
+
# include Seatbelt::Document
|
75
|
+
# include Seatbelt::Ghost
|
76
|
+
#
|
77
|
+
# has :b, "Models::B"
|
78
|
+
#
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# class Models::B
|
82
|
+
# include Seatbelt::Document
|
83
|
+
# include Seatbelt::Ghost
|
84
|
+
#
|
85
|
+
# # definitions
|
86
|
+
#
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# class ImplementationB
|
90
|
+
# field :name, :type => String
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# In a implementation method of ApiA
|
94
|
+
#
|
95
|
+
# proxy.tunnel("b.name")
|
96
|
+
#
|
97
|
+
#
|
98
|
+
# Returns the duplicated String.
|
99
|
+
def tunnel(chain)
|
100
|
+
proxy_associated_object,object_method = chain.split(".")
|
101
|
+
unless object.respond_to?(proxy_associated_object)
|
102
|
+
raise Seatbelt::Errors::ObjectDoesNotExistError
|
103
|
+
else
|
104
|
+
tunneled_object = object.send(proxy_associated_object)
|
105
|
+
callees = tunneled_object.eigenmethods.map do |eigenmethod|
|
106
|
+
eigenmethod.send(:callee)
|
107
|
+
end
|
108
|
+
|
109
|
+
callee = callees.uniq.first
|
110
|
+
unless callee.nil?
|
111
|
+
unless object_method
|
112
|
+
warn "You called a single object. Use #call instead."
|
113
|
+
object_method = proxy_associated_object
|
114
|
+
end
|
115
|
+
return callee.send(object_method)
|
116
|
+
else
|
117
|
+
raise Seatbelt::Errors::MethodNotImplementedError
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Public: Delegates a method message to the #object receiver if the
|
123
|
+
# message is not included in NOT_ALLOWABLE_CALLS_ON_OBJECT or the
|
124
|
+
# class responds to.
|
125
|
+
def method_missing(method_name, *args, &block)
|
126
|
+
unless method_name.to_s.in?(NOT_ALLOWABLE_CALLS_ON_OBJECT) &&
|
127
|
+
(not self.respond_to?(method_name))
|
128
|
+
self.call(method_name, *args, &block)
|
129
|
+
else
|
130
|
+
super
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Seatbelt
|
2
|
+
module Synthesizeable
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Public: Combined getter and setter for a map of attributes that should
|
6
|
+
# be used for synthesizing the proxy and the implementation object.
|
7
|
+
#
|
8
|
+
# map=nil - a Hash of proxy attributes as keys and implementation object
|
9
|
+
# attributes as values.
|
10
|
+
#
|
11
|
+
# If map is nil, it returns the synthesize map (or an Empty hash if not
|
12
|
+
# previously set).
|
13
|
+
def synthesize_map(map=nil)
|
14
|
+
if map.nil?
|
15
|
+
return @synthesize_map ||={}
|
16
|
+
else
|
17
|
+
@synthesize_map=map
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Defines a synthesizer for the implementation class. It takes an
|
22
|
+
# Hash argument to define the class the object should by synthesized and
|
23
|
+
# an additional adapter.
|
24
|
+
#
|
25
|
+
# options - A Hash containing synthesize configurations
|
26
|
+
# :from - The class the implementation and proxy object has to
|
27
|
+
# synthesized (String or Constant)
|
28
|
+
# :adapter- An optional class name that points to a implemented
|
29
|
+
# Synthesizer. Defaults to
|
30
|
+
# Seatbelt::Synthesizers::Document
|
31
|
+
#
|
32
|
+
def synthesize(options)
|
33
|
+
klass_to_synthesize = options[:from]
|
34
|
+
synthesize_adapter = options.fetch(:adapter,
|
35
|
+
"Seatbelt::Synthesizers::Document")
|
36
|
+
synthesize_adapter = Module.const_get(synthesize_adapter)
|
37
|
+
unless klass_to_synthesize.respond_to?(:name)
|
38
|
+
klass_to_synthesize = Module.const_get(klass_to_synthesize)
|
39
|
+
end
|
40
|
+
synthesize_obj = {:klass => klass_to_synthesize.name,
|
41
|
+
:adapter => synthesize_adapter}
|
42
|
+
klass_to_synthesize.class.class_eval do
|
43
|
+
def synthesizers
|
44
|
+
@synthesizers ||= []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
klass_to_synthesize.synthesizers << synthesize_obj
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Seatbelt
|
2
|
+
|
3
|
+
# Public: Interface between API class and Implementation class.
|
4
|
+
# Calls the implementation of an API method with passed arguments and block.
|
5
|
+
#
|
6
|
+
class Terminal
|
7
|
+
# The implementation methods config store.
|
8
|
+
def self.luggage=(luggage_pack)
|
9
|
+
@luggage = luggage_pack
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: The implementation methods config store.
|
13
|
+
#
|
14
|
+
# Returns implementation methods config store.
|
15
|
+
def self.luggage
|
16
|
+
@luggage ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.for_scope_and_namespace(scope, namespace)
|
20
|
+
Terminal.luggage.select do |package|
|
21
|
+
package.scope_level.eql?(scope) && package.namespace.eql?(namespace)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Public: calls the implementation of an API method with passed arguments
|
27
|
+
# and block.
|
28
|
+
# Before sending the method message to the receiver, it defines the
|
29
|
+
# receivers proxy scope depending on klass (see below).
|
30
|
+
#
|
31
|
+
# action - The API method name to be called
|
32
|
+
# klass - The API class name on which the API method is declared
|
33
|
+
# arity - Number of required arguments
|
34
|
+
# *args - An argument list passed to the implementation method
|
35
|
+
# &block - An optional block passed to the implementation method.
|
36
|
+
#
|
37
|
+
# Returns the return value of the implementation method.
|
38
|
+
def self.call(action, klass, arity, *args, &block)
|
39
|
+
raise Seatbelt::Errors::MethodNotImplementedError if luggage.empty?
|
40
|
+
scope = klass.class.eql?(Class) ? :class : :instance
|
41
|
+
klass_namespace = scope.eql?(:class) ? klass.name : klass.class.name
|
42
|
+
|
43
|
+
eigenmethod = klass.eigenmethods.detect do |meth|
|
44
|
+
meth.implemented_as.eql?(action)
|
45
|
+
end
|
46
|
+
|
47
|
+
unless eigenmethod
|
48
|
+
raise Seatbelt::Errors::MethodNotImplementedError
|
49
|
+
end
|
50
|
+
|
51
|
+
if (not eigenmethod.delegated) && (not eigenmethod.arity.eql?(arity))
|
52
|
+
raise Seatbelt::Errors::ArgumentMissmatchError
|
53
|
+
end
|
54
|
+
|
55
|
+
eigenmethod.call(*args, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Seatbelt
|
2
|
+
|
3
|
+
# Public: A Document is an interface to specify attributes for a API class.
|
4
|
+
# Accessing and setting attributes for an API class within an implementation
|
5
|
+
# class is possible during the proxy object.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# class Airport
|
10
|
+
# include Seatbelt::Ghost
|
11
|
+
# include Seatbelt::Document
|
12
|
+
#
|
13
|
+
# attribute :name, String
|
14
|
+
# attribute :lat, Float
|
15
|
+
# attribute :lng, Float
|
16
|
+
#
|
17
|
+
# api_method :identifier
|
18
|
+
#
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class ImplementationKlass
|
22
|
+
# include Seatbelt::Gate
|
23
|
+
#
|
24
|
+
# def aiport_identifier
|
25
|
+
# name = proxy.call(:name) # <= calls the Aiport attribute
|
26
|
+
# return "airport_#{name}".underscore
|
27
|
+
# end
|
28
|
+
# implement :airport_identifier, :as => "Airport#identifier"
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# aiport = Airport.new(:name => "London Stansted")
|
32
|
+
# airport.identifier # => "airport_london_stansted"
|
33
|
+
#
|
34
|
+
# For more information about definining and working with attributes see
|
35
|
+
# 'Virtus' project page: https://github.com/solnic/virtus
|
36
|
+
#
|
37
|
+
# To have validations for attributes just implement ActiveModel validations.
|
38
|
+
# For more informations about that see the ActiveModel validations docs:
|
39
|
+
# http://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
40
|
+
#
|
41
|
+
# Example
|
42
|
+
#
|
43
|
+
# class Airport
|
44
|
+
# include Seatbelt::Ghost
|
45
|
+
# include Seatbelt::Document
|
46
|
+
#
|
47
|
+
# attribute :name, String
|
48
|
+
# attribute :lat, Float
|
49
|
+
# attribute :lng, Float
|
50
|
+
#
|
51
|
+
# validates_presence_of :name
|
52
|
+
#
|
53
|
+
# api_method :identifier
|
54
|
+
#
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# For associations between Documents see Association module below.
|
58
|
+
module Document
|
59
|
+
|
60
|
+
def self.included(base)
|
61
|
+
base.class_eval do
|
62
|
+
include ::Virtus.model
|
63
|
+
include ::ActiveModel::Validations
|
64
|
+
extend Document::Associations
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Associations are a set of macro-like class methods for tying Ruby objects
|
70
|
+
# together.
|
71
|
+
# Each macro adds a getter and setter method that expresses the realtionship
|
72
|
+
# depending on its type.
|
73
|
+
# A 'has_many' relation defines an Array of models that also acts just the
|
74
|
+
# same as the definition says.
|
75
|
+
# A 'has' relation defines a single relation to another Ruby object.
|
76
|
+
#
|
77
|
+
# Note that every Class used for associations has to include
|
78
|
+
# Seatbelt::Document.
|
79
|
+
#
|
80
|
+
# A little bit more about 'has_many':
|
81
|
+
#
|
82
|
+
# The resulting 'has_many' Array is an abstraction of
|
83
|
+
# Virtus::Attribute::Collection.
|
84
|
+
#
|
85
|
+
# If it's defined
|
86
|
+
#
|
87
|
+
# has_many :horses, Horse
|
88
|
+
#
|
89
|
+
# Seatbelt awaits a collection definition like:
|
90
|
+
#
|
91
|
+
# module Seatbelt
|
92
|
+
# module Collections
|
93
|
+
# class HorseCollection < Seatbelt::Collections::Collection
|
94
|
+
# # do smth
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# The most important part is the collections namespace, it should be
|
100
|
+
# prefixed with Seatbelt::Collections. The collection itself has to
|
101
|
+
# implement a Virtus::Attribute.
|
102
|
+
#
|
103
|
+
module Associations
|
104
|
+
|
105
|
+
# Public: Defines a 1:n association.
|
106
|
+
#
|
107
|
+
# That is - in fact - an Array of models. There is no "other side" like
|
108
|
+
# it's known from ActiveRecord or Mongoid.
|
109
|
+
#
|
110
|
+
# collection_name - The name of the collection. That will be also the
|
111
|
+
# name of accessor method.
|
112
|
+
# collection_model - The model class used for this collection.
|
113
|
+
#
|
114
|
+
# The resulting accessor method as like an Array, with some features:
|
115
|
+
#
|
116
|
+
# Adding a model to the relationship awaits an instance of the class
|
117
|
+
# defined in 'collection_model' (otherwise a
|
118
|
+
# Seatbelt::Errors::TypeMissmatchError is raised) or a hash of attribute
|
119
|
+
# values. Any attributes included in this Hash that are not defined
|
120
|
+
# within the model are ignored.
|
121
|
+
#
|
122
|
+
# Example
|
123
|
+
#
|
124
|
+
# module HorseFarm
|
125
|
+
# module Models
|
126
|
+
# class Barnstable
|
127
|
+
# include Seatbelt::Document
|
128
|
+
#
|
129
|
+
# has_many :horses, HorseFarm::Models::Horse
|
130
|
+
#
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
def has_many(collection_name, collection_model)
|
135
|
+
model_name = collection_model.name.to_s.demodulize
|
136
|
+
collection_module_name = "Seatbelt::Collections::#{model_name}Collection"
|
137
|
+
collection = Module.const_get(collection_module_name)
|
138
|
+
collection.initialize_primitive(collection_model)
|
139
|
+
self.send(:attribute, collection_name, collection[collection_model])
|
140
|
+
end
|
141
|
+
|
142
|
+
# Public: Defines a single reference to another Seatbelt::Document class.
|
143
|
+
#
|
144
|
+
# reference_name - The name of the reference that will be also the
|
145
|
+
# name of accessor method
|
146
|
+
# attribute_type = nil - The reference attribute type (optional).
|
147
|
+
# If the attribute type if omitted
|
148
|
+
# Seabelt::Models::[reference_name class name] is
|
149
|
+
# used.
|
150
|
+
#
|
151
|
+
# Example
|
152
|
+
#
|
153
|
+
# module HorseFarm
|
154
|
+
# module Models
|
155
|
+
# class Horse
|
156
|
+
# include Seatbelt::Document
|
157
|
+
#
|
158
|
+
# # needs Seatbelt::Models::Horse
|
159
|
+
# has :horseman
|
160
|
+
# has :barnstable, HorseFarm::Models::Barnstable
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
def has(reference_name, attribute_type = nil)
|
166
|
+
unless attribute_type
|
167
|
+
attribute_type = "Seatbelt::Models::#{reference_name.to_s.classify}".
|
168
|
+
constantize
|
169
|
+
end
|
170
|
+
self.send(:attribute, reference_name, attribute_type)
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|