activefacts 0.8.9 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -1,44 +0,0 @@
1
- #
2
- # ActiveFacts Runtime API.
3
- #
4
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
- #
6
- # The ActiveFacts API is heavily metaprogrammed, so difficult to document.
7
- #
8
- # It operates on the principle that a Ruby module is used to encapsulate
9
- # a Vocabulary (the methods of the class Vocabulary are extend()ed into
10
- # the module). A Vocabulary contains classes that either derive from a
11
- # builtin Value type class (see standard_types.rb), or that use the method
12
- # Class#_identified_by_ to become an Entity (their classes are extend()ed
13
- # by the class Entity::ClassMethods). Each Value and Entity class also
14
- # contains the methods of the class Concept.
15
- #
16
- # A module becomes a Vocabulary when the first Concept class is defined within it.
17
- # A Constellation is a unique collection of Concept instances; no two instances may
18
- # exist of the same value of a ValueType, or having the same identifying roles for
19
- # an Entity type.
20
- #
21
- # Both kinds of Concepts play Roles, which are either binary or unary. Each Role
22
- # corresponds to an accessor method on Instances which are used to access the
23
- # counterpart. Roles are created by the class methods *has_one*, *one_to_one*,
24
- # and *maybe*. The former two create *two* roles, since the role has a counterpart
25
- # concept that also plays a role. In the case of a has_one role, the counterpart
26
- # role is a set, implemented by the RoleValues class, and the accessor method is
27
- # named beginning with *all_*.
28
- #
29
- # The roles of any Instance of any Concept may only be played by another Instance
30
- # of the counterpart Concept. There are no raw values, only instances of ValueType
31
- # classes.
32
-
33
- require 'activefacts/api/support' # General support code and core patches
34
- require 'activefacts/api/vocabulary' # A Ruby module may become a Vocabulary
35
- require 'activefacts/api/role_proxy' # Experimental proxy for has_one/one_to_one role accessors
36
- require 'activefacts/api/instance_index' # The index used by a constellation to record every instance
37
- require 'activefacts/api/constellation' # A Constellation is a query result or fact population
38
- require 'activefacts/api/concept' # A Ruby class may become a Concept in a Vocabulary
39
- require 'activefacts/api/role' # A Concept has a collection of Roles
40
- require 'activefacts/api/role_values' # The container used for sets of role players in many_one's
41
- require 'activefacts/api/instance' # An Instance is an instance of a Concept class
42
- require 'activefacts/api/value' # A Value is an Instance of a value class (String, Numeric, etc)
43
- require 'activefacts/api/entity' # An Entity class is an Instance not of a value class
44
- require 'activefacts/api/standard_types' # Value classes are augmented so their subclasses may become Value Types
@@ -1,410 +0,0 @@
1
- #
2
- # ActiveFacts Runtime API
3
- # Concept (a mixin module for the class Class)
4
- #
5
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
- #
7
-
8
- module ActiveFacts
9
- module API
10
- module Vocabulary; end
11
-
12
- # Concept contains methods that are added as class methods to all Value and Entity classes.
13
- module Concept
14
- # What vocabulary (Ruby module) does this concept belong to?
15
- def vocabulary
16
- modspace # The module that contains this concept.
17
- end
18
-
19
- # Each Concept maintains a list of the Roles it plays:
20
- def roles(name = nil)
21
- unless instance_variable_defined? "@roles"
22
- @roles = RoleCollection.new # Initialize and extend without warnings.
23
- end
24
- case name
25
- when nil
26
- @roles
27
- when Symbol, String
28
- # Search this class then all supertypes:
29
- unless role = @roles[name.to_sym]
30
- role = nil
31
- supertypes.each do |supertype|
32
- r = supertype.roles(name) rescue nil
33
- next unless r
34
- role = r
35
- break
36
- end
37
- end
38
- raise "Role #{basename}.#{name} is not defined" unless role
39
- # Bind the role if possible, but don't require it:
40
- role.resolve_counterpart(vocabulary) rescue nil unless role.counterpart_concept.is_a?(Class)
41
- role
42
- else
43
- nil
44
- end
45
- end
46
-
47
- # Define a unary fact type attached to this concept; in essence, a boolean attribute.
48
- #
49
- # Example: maybe :is_ceo
50
- def maybe(role_name)
51
- realise_role(roles[role_name] = Role.new(self, TrueClass, nil, role_name))
52
- end
53
-
54
- # Define a binary fact type relating this concept to another,
55
- # with a uniqueness constraint only on this concept's role.
56
- # This method creates two accessor methods, one in this concept and one in the other concept.
57
- # * role_name is a Symbol for the name of the role (this end of the relationship)
58
- # Options contain optional keys:
59
- # * :class - A class name, Symbol or String naming a class, required if it doesn't match the role_name. Use a symbol or string if the class isn't defined yet, and the methods will be created later, when the class is first defined.
60
- # * :mandatory - if this role may not be NULL in a valid fact population, say :mandatory => true. Mandatory constraints are only enforced during validation (e.g. before saving).
61
- # * :counterpart - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>\_as_<role_name>
62
- # * :reading - for verbalisation. Not used yet.
63
- # * :restrict - a list of values or ranges which this role may take. Not used yet.
64
- def has_one(role_name, options = {})
65
- role_name, related, mandatory, related_role_name = extract_binary_params(false, role_name, options)
66
- define_binary_fact_type(false, role_name, related, mandatory, related_role_name)
67
- end
68
-
69
- # Define a binary fact type joining this concept to another,
70
- # with uniqueness constraints in both directions, i.e. a one-to-one relationship
71
- # This method creates two accessor methods, one in this concept and one in the other concept.
72
- # * role_name is a Symbol for the name of the role (this end of the relationship)
73
- # Options contain optional keys:
74
- # * :class - A class name, Symbol or String naming a class, required if it doesn't match the role_name. Use a symbol or string if the class isn't defined yet, and the methods will be created later, when the class is first defined.
75
- # * :mandatory - if this role may not be NULL in a valid fact population, say :mandatory => true. Mandatory constraints are only enforced during validation (e.g. before saving).
76
- # * :counterpart - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>\_as_<role_name>
77
- # * :reading - for verbalisation. Not used yet.
78
- # * :restrict - a list of values or ranges which this role may take. Not used yet.
79
- def one_to_one(role_name, options = {})
80
- role_name, related, mandatory, related_role_name =
81
- extract_binary_params(true, role_name, options)
82
- define_binary_fact_type(true, role_name, related, mandatory, related_role_name)
83
- end
84
-
85
- # Access supertypes or add new supertypes; multiple inheritance.
86
- # With parameters (Class objects), it adds new supertypes to this class. Instances of this class will then have role methods for any new superclasses (transitively). Superclasses must be Ruby classes which are existing Concepts.
87
- # Without parameters, it returns the array of Concept supertypes (one by Ruby inheritance, any others as defined using this method)
88
- def supertypes(*concepts)
89
- class_eval do
90
- @supertypes ||= []
91
- all_supertypes = supertypes_transitive
92
- concepts.each do |concept|
93
- next if all_supertypes.include? concept
94
- case concept
95
- when Class
96
- @supertypes << concept
97
- when Symbol
98
- # No late binding here:
99
- @supertypes << (concept = vocabulary.const_get(concept.to_s.camelcase))
100
- else
101
- raise "Illegal supertype #{concept.inspect} for #{self.class.basename}"
102
- end
103
-
104
- # Realise the roles (create accessors) of this supertype.
105
- # REVISIT: The existing accessors at the other end will need to allow this class as role counterpart
106
- # REVISIT: Need to check all superclass roles recursively, unless we hit a common supertype
107
- #puts "Realising concept #{concept.name} in #{basename}"
108
- realise_supertypes(concept, all_supertypes)
109
- end
110
- [(superclass.vocabulary && superclass rescue nil), *@supertypes].compact
111
- end
112
- end
113
-
114
- # Return the array of all Concept supertypes, transitively.
115
- def supertypes_transitive
116
- class_eval do
117
- supertypes = []
118
- supertypes << superclass if Module === (superclass.vocabulary rescue nil)
119
- supertypes += (@supertypes ||= [])
120
- supertypes.inject([]) {|a, t|
121
- next if a.include?(t)
122
- a += [t]
123
- a += t.supertypes_transitive rescue []
124
- }.uniq
125
- end
126
- end
127
-
128
- def subtypes
129
- @subtypes ||= []
130
- end
131
-
132
- # Every new role added or inherited comes through here:
133
- def realise_role(role) #:nodoc:
134
- #puts "Realising role #{role.counterpart_concept.basename rescue role.counterpart_concept}.#{role.name} in #{basename}"
135
-
136
- if (!role.counterpart)
137
- # Unary role
138
- define_unary_role_accessor(role)
139
- elsif (role.unique)
140
- define_single_role_accessor(role, role.counterpart.unique)
141
- else
142
- define_array_role_accessor(role)
143
- end
144
- end
145
-
146
- # REVISIT: Use method_missing to catch all_some_role_as_other_role_and_third_role, to sort_by those roles?
147
-
148
- def is_a? klass
149
- super || supertypes_transitive.include?(klass)
150
- end
151
-
152
- private
153
-
154
- def realise_supertypes(concept, all_supertypes = nil)
155
- all_supertypes ||= supertypes_transitive
156
- s = concept.supertypes
157
- #puts "realising #{concept.basename} supertypes #{s.inspect} of #{basename}"
158
- s.each {|t|
159
- next if all_supertypes.include? t
160
- realise_supertypes(t, all_supertypes)
161
- t.subtypes << self
162
- all_supertypes << t
163
- }
164
- #puts "Realising roles of #{concept.basename} in #{basename}"
165
- realise_roles(concept)
166
- end
167
-
168
- # Realise all the roles of a concept on this concept, used when a supertype is added:
169
- def realise_roles(concept)
170
- concept.roles.each do |role_name, role|
171
- realise_role(role)
172
- end
173
- end
174
-
175
- # Shared code for both kinds of binary fact type (has_one and one_to_one)
176
- def define_binary_fact_type(one_to_one, role_name, related, mandatory, related_role_name)
177
- # puts "#{self}.#{role_name} is to #{related.inspect}, #{mandatory ? :mandatory : :optional}, related role is #{related_role_name}"
178
-
179
- raise "#{name} cannot have more than one role named #{role_name}" if roles[role_name]
180
- roles[role_name] = role = Role.new(self, related, nil, role_name, mandatory)
181
-
182
- # There may be a forward reference here where role_name is a Symbol,
183
- # and the block runs later when that Symbol is bound to the concept.
184
- when_bound(related, self, role_name, related_role_name) do |target, definer, role_name, related_role_name|
185
- if (one_to_one)
186
- target.roles[related_role_name] = role.counterpart = Role.new(target, definer, role, related_role_name, false)
187
- else
188
- target.roles[related_role_name] = role.counterpart = Role.new(target, definer, role, related_role_name, false, false)
189
- end
190
- role.counterpart_concept = target
191
- #puts "Realising role pair #{definer.basename}.#{role_name} <-> #{target.basename}.#{related_role_name}"
192
- realise_role(role)
193
- target.realise_role(role.counterpart)
194
- end
195
- end
196
-
197
- def define_unary_role_accessor(role)
198
- # puts "Defining #{basename}.#{role_name} as unary"
199
- class_eval do
200
- define_method "#{role.name}=" do |value|
201
- #puts "Setting #{self.class.name} #{object_id}.@#{role.name} to #{(value ? true : nil).inspect}"
202
- instance_variable_set("@#{role.name}", value ? true : nil)
203
- # REVISIT: Provide a way to find all instances playing/not playing this role
204
- # Analogous to true.all_thing_as_role_name...
205
- end
206
- end
207
- define_single_role_getter(role)
208
- end
209
-
210
- def define_single_role_getter(role)
211
- class_eval do
212
- define_method role.name do
213
- i = instance_variable_get("@#{role.name}") rescue nil
214
- i ? RoleProxy.new(role, i) : i
215
- i
216
- end
217
- end
218
- end
219
-
220
- # REVISIT: Add __add_to(constellation) and __remove(constellation) here?
221
- def define_single_role_accessor(role, one_to_one)
222
- # puts "Defining #{basename}.#{role.name} to #{role.counterpart_concept.basename} (#{one_to_one ? "assigning" : "populating"} #{role.counterpart.name})"
223
- define_single_role_getter(role)
224
-
225
- if (one_to_one)
226
- # This gets called to assign nil to the related role in the old correspondent:
227
- # value is included here so we can check that the correct value is being nullified, if necessary
228
- nullify_reference = lambda{|from, role_name, value| from.send("#{role_name}=".to_sym, nil) }
229
-
230
- # This gets called to replace an old single value for a new one in the related role of a new correspondent
231
- assign_reference = lambda{|from, role_name, old_value, value| from.send("#{role_name}=".to_sym, value) }
232
-
233
- define_single_role_setter(role, nullify_reference, assign_reference)
234
- else
235
- # This gets called to delete this object from the role value array in the old correspondent
236
- delete_reference = lambda{|from, role_name, value| from.send(role_name).update(value, nil) }
237
-
238
- # This gets called to replace an old value by a new one in the related role value array of a new correspondent
239
- replace_reference = lambda{|from, role_name, old_value, value|
240
- from.send(role_name).update(old_value, value)
241
- }
242
-
243
- define_single_role_setter(role, delete_reference, replace_reference)
244
- end
245
- end
246
-
247
- def define_single_role_setter(role, deassign_old, assign_new)
248
- class_eval do
249
- define_method "#{role.name}=" do |value|
250
- role_var = "@#{role.name}"
251
-
252
- # If role.counterpart_concept isn't bound to a class yet, bind it.
253
- role.resolve_counterpart(self.class.vocabulary) unless role.counterpart_concept.is_a?(Class)
254
-
255
- # Get old value, and jump out early if it's unchanged:
256
- old = instance_variable_get(role_var) rescue nil
257
- return if old == value # Occurs during one_to_one assignment, for example
258
-
259
- value = role.adapt(constellation, value) if value
260
- return if old == value # Occurs when same value is assigned
261
-
262
- # DEBUG: puts "assign #{self.class.basename}.#{role.name} <-> #{value.inspect}.#{role.counterpart.name}#{old ? " (was #{old.inspect})" : ""}"
263
-
264
- # REVISIT: A frozen-key solution could be used to allow changing identifying roles.
265
- # The key would be frozen, allowing indices and counterparts to de-assign,
266
- # but delay re-assignment until defrosted.
267
- # That would also allow caching the identifying_role_values, a performance win.
268
-
269
- # This allows setting and clearing identifying roles, but not changing them.
270
- raise "#{self.class.basename}: illegal attempt to modify identifying role #{role.name}" if role.is_identifying && value != nil && old != nil
271
-
272
- # puts "Setting binary #{role_var} to #{value.verbalise}"
273
- instance_variable_set(role_var, value)
274
-
275
- # De-assign/remove "self" at the old other end too:
276
- deassign_old.call(old, role.counterpart.name, self) if old
277
-
278
- # Assign/add "self" at the other end too:
279
- assign_new.call(value, role.counterpart.name, old, self) if value
280
- end
281
- end
282
- end
283
-
284
- def define_array_role_accessor(role)
285
- class_eval do
286
- define_method "#{role.name}" do
287
- unless (r = instance_variable_get(role_var = "@#{role.name}") rescue nil)
288
- r = instance_variable_set(role_var, RoleValues.new)
289
- end
290
- # puts "fetching #{self.class.basename}.#{role.name} array, got #{r.class}, first is #{r[0] ? r[0].verbalise : "nil"}"
291
- r
292
- end
293
- end
294
- end
295
-
296
- # Extract the parameters to a role definition and massage them into the right shape.
297
- #
298
- # The first parameter, role_name, is mandatory. It may be a Symbol, a String or a Class.
299
- # New proposed input options:
300
- # :class => the related class (Class object or Symbol). Not allowed if role_name was a class.
301
- # :mandatory => true. There must be a related object for this object to be valid.
302
- # :counterpart => Symbol/String. The name of the counterpart role. Will be to_s.snakecase'd and maybe augmented with "all_" and/or "_as_<role_name>"
303
- # :reading => "forward/reverse". Forward and reverse readings. Must include MARKERS for the player names. May include adjectives. REVISIT: define MARKERS!
304
- # LATER:
305
- # :order => :local_role OR lambda{} (for sort_by)
306
- # :restrict => Range or Array of Range/value or respond_to?(include?)
307
- #
308
- # This function returns an array:
309
- # [ role_name,
310
- # related,
311
- # mandatory,
312
- # related_role_name ]
313
- #
314
- # Role naming rule:
315
- # "all_" if there may be more than one (only ever on related end)
316
- # Role Name:
317
- # If a role name is defined at this end:
318
- # Role Name
319
- # else:
320
- # Leading Adjective
321
- # Role counterpart_concept name (not role name)
322
- # Trailing Adjective
323
- # "_as_<other_role_name>" if other_role_name != this role counterpart_concept's name, and not other_player_this_player
324
- def extract_binary_params(one_to_one, role_name, options)
325
- # Options:
326
- # other counterpart_concept (Symbol or Class)
327
- # mandatory (:mandatory)
328
- # other end role name if any (Symbol),
329
- related = nil
330
- mandatory = false
331
- related_role_name = nil
332
- role_player = self.basename.snakecase
333
-
334
- role_name = a.name.snakecase.to_sym if Class === role_name
335
- role_name = role_name.to_sym
336
-
337
- # The related class might be forward-referenced, so handle a Symbol/String instead of a Class.
338
- related_name = options.delete(:class)
339
- case related_name
340
- when nil
341
- related = role_name # No :class provided, assume it matches the role_name
342
- related_name ||= role_name.to_s
343
- when Class
344
- related = related_name
345
- related_name = related_name.basename.to_s.snakecase
346
- when Symbol, String
347
- related = related_name
348
- related_name = related_name.to_s.snakecase
349
- else
350
- raise "Invalid type for :class option on :#{role_name}"
351
- end
352
-
353
- # resolve the Symbol to a Class now if possible:
354
- resolved = vocabulary.concept(related) rescue nil
355
- #puts "#{related} resolves to #{resolved}"
356
- related = resolved if resolved
357
- # puts "related = #{related.inspect}"
358
-
359
- if options.delete(:mandatory) == true
360
- mandatory = true
361
- end
362
-
363
- related_role_name = related_role_name.to_s if related_role_name = options.delete(:counterpart)
364
-
365
- reading = options.delete(:reading) # REVISIT: Implement verbalisation
366
- role_value_constraint = options.delete(:restrict) # REVISIT: Implement role value constraints
367
-
368
- raise "Unrecognised options on #{role_name}: #{options.keys.inspect}" unless options.empty?
369
-
370
- # Avoid a confusing mismatch:
371
- # Note that if you have a role "supervisor" and a sub-class "Supervisor", this'll bitch.
372
- if (Class === related && (indicated = vocabulary.concept(role_name)) && indicated != related)
373
- raise "Role name #{role_name} indicates a different counterpart concept #{indicated} than specified"
374
- end
375
-
376
- # This code probably isn't as quick or simple as it could be, but it does work right,
377
- # and that was pretty hard, because the variable naming is all over the shop. Should fix
378
- # the naming first (here and in generate/oo.rb) then figure out how to speed it up.
379
- # Note that oo.rb names things from the opposite end, so you wind up in a maze of mirrors.
380
- other_role_method =
381
- (one_to_one ? "" : "all_") +
382
- (related_role_name || role_player)
383
- if role_name.to_s != related_name and
384
- (!related_role_name || related_role_name == role_player)
385
- other_role_method += "_as_#{role_name}"
386
- end
387
- #puts "On #{basename}: have related_role_name=#{related_role_name.inspect}, role_player=#{role_player}, role_name=#{role_name}, related_name=#{related_name.inspect} -> #{related_name}.#{other_role_method}"
388
-
389
- [ role_name,
390
- related,
391
- mandatory,
392
- other_role_method.to_sym
393
- ]
394
- end
395
-
396
- def when_bound(concept, *args, &block)
397
- case concept
398
- when Class
399
- block.call(concept, *args) # Execute block in the context of the concept
400
- when Symbol
401
- vocabulary.__delay(concept.to_s.camelcase(true), args, &block)
402
- when String # Arrange for this to happen later
403
- vocabulary.__delay(concept, args, &block)
404
- else
405
- raise "Delayed binding not possible for #{concept.class.name} #{concept.inspect}"
406
- end
407
- end
408
- end
409
- end
410
- end