activefacts 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +0 -3
- data/Rakefile +7 -5
- data/bin/afgen +5 -2
- data/bin/cql +3 -2
- data/examples/CQL/Genealogy.cql +3 -3
- data/examples/CQL/Metamodel.cql +10 -7
- data/examples/CQL/MultiInheritance.cql +2 -1
- data/examples/CQL/OilSupply.cql +4 -4
- data/examples/CQL/Orienteering.cql +2 -2
- data/lib/activefacts.rb +2 -1
- data/lib/activefacts/api.rb +21 -2
- data/lib/activefacts/api/concept.rb +52 -39
- data/lib/activefacts/api/constellation.rb +8 -6
- data/lib/activefacts/api/entity.rb +41 -37
- data/lib/activefacts/api/instance.rb +5 -3
- data/lib/activefacts/api/numeric.rb +28 -21
- data/lib/activefacts/api/role.rb +29 -43
- data/lib/activefacts/api/standard_types.rb +8 -3
- data/lib/activefacts/api/support.rb +4 -4
- data/lib/activefacts/api/value.rb +9 -3
- data/lib/activefacts/api/vocabulary.rb +17 -7
- data/lib/activefacts/cql.rb +10 -7
- data/lib/activefacts/cql/CQLParser.treetop +6 -0
- data/lib/activefacts/cql/Concepts.treetop +32 -26
- data/lib/activefacts/cql/DataTypes.treetop +6 -0
- data/lib/activefacts/cql/Expressions.treetop +6 -0
- data/lib/activefacts/cql/FactTypes.treetop +6 -0
- data/lib/activefacts/cql/Language/English.treetop +9 -3
- data/lib/activefacts/cql/LexicalRules.treetop +6 -0
- data/lib/activefacts/cql/Rakefile +8 -0
- data/lib/activefacts/cql/parser.rb +4 -2
- data/lib/activefacts/generate/absorption.rb +20 -28
- data/lib/activefacts/generate/cql.rb +28 -16
- data/lib/activefacts/generate/cql/html.rb +327 -321
- data/lib/activefacts/generate/null.rb +7 -3
- data/lib/activefacts/generate/oo.rb +19 -15
- data/lib/activefacts/generate/ordered.rb +457 -461
- data/lib/activefacts/generate/ruby.rb +12 -4
- data/lib/activefacts/generate/sql/server.rb +42 -10
- data/lib/activefacts/generate/text.rb +7 -3
- data/lib/activefacts/input/cql.rb +55 -28
- data/lib/activefacts/input/orm.rb +32 -22
- data/lib/activefacts/persistence.rb +5 -0
- data/lib/activefacts/persistence/columns.rb +66 -32
- data/lib/activefacts/persistence/foreignkey.rb +29 -5
- data/lib/activefacts/persistence/index.rb +57 -25
- data/lib/activefacts/persistence/reference.rb +65 -30
- data/lib/activefacts/persistence/tables.rb +28 -17
- data/lib/activefacts/support.rb +8 -0
- data/lib/activefacts/version.rb +7 -1
- data/lib/activefacts/vocabulary.rb +4 -2
- data/lib/activefacts/vocabulary/extensions.rb +12 -10
- data/lib/activefacts/vocabulary/metamodel.rb +24 -23
- data/spec/api/autocounter.rb +2 -2
- data/spec/api/entity_type.rb +2 -2
- data/spec/api/instance.rb +61 -30
- data/spec/api/roles.rb +9 -9
- data/spec/cql_parse_spec.rb +1 -0
- data/spec/norma_tables_spec.rb +3 -3
- metadata +8 -4
data/README.rdoc
CHANGED
@@ -26,9 +26,6 @@ SQL is.
|
|
26
26
|
|
27
27
|
* Queries are parsed, but not yet generated to SQL. DDL is complete.
|
28
28
|
|
29
|
-
* SQL generation handles some one-to-one relationships wrongly,
|
30
|
-
including subtypes when independent of the supertype's table.
|
31
|
-
|
32
29
|
* The Constellation API lacks SQL persistence (but is already useful;
|
33
30
|
the CQL compiler uses the generated Ruby code extensively)
|
34
31
|
|
data/Rakefile
CHANGED
@@ -16,11 +16,13 @@ $hoe = Hoe.new('activefacts', ActiveFacts::VERSION) do |p|
|
|
16
16
|
['newgem', ">= #{::Newgem::VERSION}"]
|
17
17
|
]
|
18
18
|
p.spec_extras[:extensions] = 'lib/activefacts/cql/Rakefile'
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
# Magic Hoe hook to prevent the generation of diagrams:
|
20
|
+
ENV['NODOT'] = 'yes'
|
21
|
+
p.spec_extras[:rdoc_options] = %w{
|
22
|
+
-A has_one -A one_to_one -A maybe
|
23
|
+
-x lib/activefacts/cql/.*.rb
|
24
|
+
-x lib/activefacts/vocabulary/.*.rb
|
25
|
+
}
|
24
26
|
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
25
27
|
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
26
28
|
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
data/bin/afgen
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#! env ruby
|
2
2
|
#
|
3
|
-
# Read
|
3
|
+
# ActiveFacts: Read a Vocabulary (from a NORMA, CQL or other file) and run a generator
|
4
4
|
#
|
5
|
-
# Copyright (c)
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
7
|
$:.unshift File.dirname(File.expand_path(__FILE__))+"/../lib"
|
8
8
|
|
@@ -11,6 +11,9 @@ require 'ruby-debug'
|
|
11
11
|
require 'activefacts'
|
12
12
|
require 'activefacts/vocabulary'
|
13
13
|
|
14
|
+
debugger if debug :exception
|
15
|
+
true # Ok, got the stack set up, continue to the fault
|
16
|
+
|
14
17
|
arg = ARGV.shift
|
15
18
|
|
16
19
|
# Load the required generator, or the default "text" generator:
|
data/bin/cql
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#! env ruby
|
2
2
|
#
|
3
|
-
# Interactive CQL command-line.
|
4
|
-
#
|
3
|
+
# ActiveFacts: Interactive CQL command-line. Incomplete; only parses CQL and shows the parse trees
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
5
6
|
#
|
6
7
|
|
7
8
|
require 'activefacts'
|
data/examples/CQL/Genealogy.cql
CHANGED
@@ -9,10 +9,10 @@ Day is defined as UnsignedInteger(32) restricted to {1..31};
|
|
9
9
|
Email is defined as VariableLengthText(64);
|
10
10
|
EventID is defined as AutoCounter();
|
11
11
|
EventLocation is defined as VariableLengthText(128);
|
12
|
-
EventRoleName is defined as VariableLengthText() restricted to {'
|
12
|
+
EventRoleName is defined as VariableLengthText() restricted to {'Celebrant', 'Father', 'Husband', 'Mother', 'Subject', 'Wife'};
|
13
13
|
EventTypeID is defined as AutoCounter();
|
14
|
-
EventTypeName is defined as VariableLengthText(16) restricted to {'Birth', '
|
15
|
-
Gender is defined as FixedLengthText(1) restricted to {'
|
14
|
+
EventTypeName is defined as VariableLengthText(16) restricted to {'Birth', 'Burial', 'Christening', 'Death', 'Divorce', 'Marriage'};
|
15
|
+
Gender is defined as FixedLengthText(1) restricted to {'F', 'M'};
|
16
16
|
Month is defined as UnsignedInteger(32) restricted to {1..12};
|
17
17
|
Name is defined as VariableLengthText(128);
|
18
18
|
Occupation is defined as VariableLengthText(128);
|
data/examples/CQL/Metamodel.cql
CHANGED
@@ -108,10 +108,10 @@ Vocabulary contains Constraint,
|
|
108
108
|
Import is where
|
109
109
|
Vocabulary imports imported-Vocabulary [acyclic];
|
110
110
|
|
111
|
-
Feature is identified by
|
112
|
-
Feature is called one Name,
|
111
|
+
Feature is identified by Vocabulary and Name where
|
113
112
|
Feature belongs to at most one Vocabulary,
|
114
|
-
Vocabulary contains Feature
|
113
|
+
Vocabulary contains Feature,
|
114
|
+
Feature is called one Name;
|
115
115
|
Correspondence is where
|
116
116
|
in Import imported-Feature corresponds to at most one local-Feature;
|
117
117
|
|
@@ -129,7 +129,10 @@ Population includes RoleValue,
|
|
129
129
|
|
130
130
|
SetComparisonConstraint is a kind of SetConstraint;
|
131
131
|
SetComparisonRoles is where
|
132
|
-
SetComparisonConstraint
|
132
|
+
SetComparisonConstraint has in Ordinal position at most one RoleSequence,
|
133
|
+
RoleSequence is Ordinal in SetComparisonConstraint,
|
134
|
+
in Ordinal position SetComparisonConstraint has RoleSequence,
|
135
|
+
SetComparisonConstraint has RoleSequence in at most one Ordinal position;
|
133
136
|
|
134
137
|
SetEqualityConstraint is a kind of SetComparisonConstraint;
|
135
138
|
|
@@ -224,9 +227,6 @@ each combination EntityType, TypeInheritance occurs at most one time in
|
|
224
227
|
TypeInheritance provides identification;
|
225
228
|
each FactType occurs at least one time in
|
226
229
|
FactType has Ordinal role played by Concept;
|
227
|
-
each combination Name, Vocabulary occurs at most one time in
|
228
|
-
Name is of Constraint,
|
229
|
-
Vocabulary contains Constraint;
|
230
230
|
each PresenceConstraint occurs at least one time in
|
231
231
|
PresenceConstraint has min-Frequency,
|
232
232
|
PresenceConstraint has max-Frequency,
|
@@ -236,3 +236,6 @@ each RoleSequence occurs at least one time in
|
|
236
236
|
each ValueRange occurs at least one time in
|
237
237
|
ValueRange has minimum-Bound,
|
238
238
|
ValueRange has maximum-Bound;
|
239
|
+
each combination Vocabulary, Name occurs at most one time in
|
240
|
+
Vocabulary contains Constraint,
|
241
|
+
Name is of Constraint;
|
@@ -13,7 +13,8 @@ TFN is defined as FixedLengthText(9);
|
|
13
13
|
Person is identified by its Name;
|
14
14
|
|
15
15
|
Australian is a kind of Person;
|
16
|
-
Australian has at most one TFN
|
16
|
+
Australian has at most one TFN,
|
17
|
+
TFN is of one Australian;
|
17
18
|
|
18
19
|
Employee is a kind of Person identified by its ID;
|
19
20
|
|
data/examples/CQL/OilSupply.cql
CHANGED
@@ -9,7 +9,7 @@ ProductName is defined as VariableLengthText();
|
|
9
9
|
Quantity is defined as UnsignedInteger(32);
|
10
10
|
RefineryName is defined as VariableLengthText(80);
|
11
11
|
RegionName is defined as VariableLengthText();
|
12
|
-
Season is defined as VariableLengthText(6) restricted to {'
|
12
|
+
Season is defined as VariableLengthText(6) restricted to {'Autumn', 'Spring', 'Summer', 'Winter'};
|
13
13
|
TransportMethod is defined as VariableLengthText() restricted to {'Rail', 'Road', 'Sea'};
|
14
14
|
YearNr is defined as SignedInteger(32);
|
15
15
|
|
@@ -34,9 +34,9 @@ TransportRoute incurs at most one Cost per kl;
|
|
34
34
|
|
35
35
|
Year is identified by its Nr;
|
36
36
|
|
37
|
-
SupplyPeriod is identified by
|
38
|
-
SupplyPeriod is in one
|
39
|
-
SupplyPeriod is in one
|
37
|
+
SupplyPeriod is identified by Year and Month where
|
38
|
+
SupplyPeriod is in one Year,
|
39
|
+
SupplyPeriod is in one Month;
|
40
40
|
ProductionForecast is where
|
41
41
|
Refinery forecasts production of Product in SupplyPeriod;
|
42
42
|
RegionalDemand is where
|
@@ -12,7 +12,7 @@ EntryID is defined as AutoCounter();
|
|
12
12
|
EventID is defined as AutoCounter();
|
13
13
|
EventName is defined as VariableLengthText(50);
|
14
14
|
FamilyName is defined as VariableLengthText(48);
|
15
|
-
Gender is defined as FixedLengthText(1) restricted to {'
|
15
|
+
Gender is defined as FixedLengthText(1) restricted to {'F', 'M'};
|
16
16
|
GivenName is defined as VariableLengthText(48);
|
17
17
|
Location is defined as VariableLengthText(200);
|
18
18
|
MapID is defined as AutoCounter();
|
@@ -24,7 +24,7 @@ PointValue is defined as UnsignedInteger(32);
|
|
24
24
|
PostCode is defined as UnsignedInteger(32);
|
25
25
|
PunchID is defined as AutoCounter();
|
26
26
|
Score is defined as SignedInteger(32);
|
27
|
-
ScoringMethod is defined as VariableLengthText(32) restricted to {'
|
27
|
+
ScoringMethod is defined as VariableLengthText(32) restricted to {'Scatter', 'Score', 'Special'};
|
28
28
|
SeriesID is defined as AutoCounter();
|
29
29
|
SeriesName is defined as VariableLengthText(40);
|
30
30
|
StartTime is defined as DateAndTime();
|
data/lib/activefacts.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Swiss-army knife.
|
3
|
+
# Include all that's required for a non-persistent runtime application.
|
3
4
|
#
|
4
|
-
# Copyright (c)
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
5
6
|
# Author: Clifford Heath.
|
6
7
|
#
|
7
8
|
require 'rubygems'
|
data/lib/activefacts/api.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
5
|
#
|
5
6
|
# The ActiveFacts API is heavily metaprogrammed, so difficult to document.
|
6
7
|
#
|
@@ -13,12 +14,30 @@
|
|
13
14
|
# contains the methods of the class Concept.
|
14
15
|
#
|
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.
|
16
32
|
|
17
33
|
require 'activefacts/api/support' # General support code and core patches
|
18
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
|
19
37
|
require 'activefacts/api/constellation' # A Constellation is a query result or fact population
|
20
38
|
require 'activefacts/api/concept' # A Ruby class may become a Concept in a Vocabulary
|
21
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
|
22
41
|
require 'activefacts/api/instance' # An Instance is an instance of a Concept class
|
23
42
|
require 'activefacts/api/value' # A Value is an Instance of a value class (String, Numeric, etc)
|
24
43
|
require 'activefacts/api/entity' # An Entity class is an Instance not of a value class
|
@@ -1,7 +1,10 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Runtime API
|
3
|
+
# Concept (a mixin module for the class Class)
|
4
4
|
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
|
5
8
|
module ActiveFacts
|
6
9
|
module API
|
7
10
|
module Vocabulary; end
|
@@ -34,7 +37,7 @@ module ActiveFacts
|
|
34
37
|
end
|
35
38
|
raise "Role #{basename}.#{name} is not defined" unless role
|
36
39
|
# Bind the role if possible, but don't require it:
|
37
|
-
role.
|
40
|
+
role.resolve_counterpart(vocabulary) rescue nil unless role.counterpart_concept.is_a?(Class)
|
38
41
|
role
|
39
42
|
else
|
40
43
|
nil
|
@@ -45,7 +48,7 @@ module ActiveFacts
|
|
45
48
|
#
|
46
49
|
# Example: maybe :is_ceo
|
47
50
|
def maybe(role_name)
|
48
|
-
realise_role(roles[role_name] = Role.new(TrueClass, nil, role_name))
|
51
|
+
realise_role(roles[role_name] = Role.new(self, TrueClass, nil, role_name))
|
49
52
|
end
|
50
53
|
|
51
54
|
# Define a binary fact type relating this concept to another,
|
@@ -55,7 +58,7 @@ module ActiveFacts
|
|
55
58
|
# * role_name - a Symbol for the name of the role (this end of the relationship).
|
56
59
|
# * other_player - 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.
|
57
60
|
# * :mandatory - if this role may not be NULL in a valid fact population. Mandatory constraints are only enforced during validation (e.g. before saving).
|
58
|
-
# * :other_role_name - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept
|
61
|
+
# * :other_role_name - use if the role at the other end should have a name other than the default :all_<concept> or :all_<concept>\_as_<role_name>
|
59
62
|
def has_one(*args)
|
60
63
|
role_name, related, mandatory, related_role_name = extract_binary_params(false, args)
|
61
64
|
define_binary_fact_type(false, role_name, related, mandatory, related_role_name)
|
@@ -68,7 +71,7 @@ module ActiveFacts
|
|
68
71
|
# * role_name - a Symbol for the name of the role (this end of the relationship)
|
69
72
|
# * other_player - 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
|
70
73
|
# * :mandatory - if this role may not be NULL in a valid fact population. Mandatory constraints are only enforced during validation (e.g. before saving)
|
71
|
-
# * :other_role_name - use if the role at the other end should have a name other than the default :<concept> or :<concept>
|
74
|
+
# * :other_role_name - use if the role at the other end should have a name other than the default :<concept> or :<concept>_as_<role_name>
|
72
75
|
def one_to_one(*args)
|
73
76
|
role_name, related, mandatory, related_role_name =
|
74
77
|
extract_binary_params(true, args)
|
@@ -95,7 +98,7 @@ module ActiveFacts
|
|
95
98
|
end
|
96
99
|
|
97
100
|
# Realise the roles (create accessors) of this supertype.
|
98
|
-
# REVISIT: The existing accessors at the other end will need to allow this class as role
|
101
|
+
# REVISIT: The existing accessors at the other end will need to allow this class as role counterpart
|
99
102
|
# REVISIT: Need to check all superclass roles recursively, unless we hit a common supertype
|
100
103
|
#puts "Realising concept #{concept.name} in #{basename}"
|
101
104
|
realise_supertypes(concept, all_supertypes)
|
@@ -120,7 +123,7 @@ module ActiveFacts
|
|
120
123
|
|
121
124
|
# Every new role added or inherited comes through here:
|
122
125
|
def realise_role(role) #:nodoc:
|
123
|
-
#puts "Realising role #{role.
|
126
|
+
#puts "Realising role #{role.counterpart_concept.basename rescue role.counterpart_concept}.#{role.name} in #{basename}"
|
124
127
|
|
125
128
|
if (!role.counterpart)
|
126
129
|
# Unary role
|
@@ -132,7 +135,7 @@ module ActiveFacts
|
|
132
135
|
end
|
133
136
|
end
|
134
137
|
|
135
|
-
# REVISIT: Use method_missing to catch
|
138
|
+
# REVISIT: Use method_missing to catch all_some_role_as_other_role_and_third_role, to sort_by those roles?
|
136
139
|
|
137
140
|
private
|
138
141
|
|
@@ -160,15 +163,16 @@ module ActiveFacts
|
|
160
163
|
def define_binary_fact_type(one_to_one, role_name, related, mandatory, related_role_name)
|
161
164
|
# puts "#{self}.#{role_name} is to #{related.inspect}, #{mandatory ? :mandatory : :optional}, related role is #{related_role_name}"
|
162
165
|
|
163
|
-
|
166
|
+
raise "#{self.class.basename} cannot have more than one role named #{role_name}" if roles[role_name]
|
167
|
+
roles[role_name] = role = Role.new(self, related, nil, role_name, mandatory)
|
164
168
|
|
165
169
|
# There may be a forward reference here where role_name is a Symbol,
|
166
170
|
# and the block runs later when that Symbol is bound to the concept.
|
167
171
|
when_bound(related, self, role_name, related_role_name) do |target, definer, role_name, related_role_name|
|
168
172
|
if (one_to_one)
|
169
|
-
target.roles[related_role_name] = role.counterpart = Role.new(definer, role, related_role_name, false)
|
173
|
+
target.roles[related_role_name] = role.counterpart = Role.new(target, definer, role, related_role_name, false)
|
170
174
|
else
|
171
|
-
target.roles[related_role_name] = role.counterpart = Role.new(definer, role, related_role_name, false, false)
|
175
|
+
target.roles[related_role_name] = role.counterpart = Role.new(target, definer, role, related_role_name, false, false)
|
172
176
|
end
|
173
177
|
#puts "Realising role pair #{definer.basename}.#{role_name} <-> #{target.basename}.#{related_role_name}"
|
174
178
|
realise_role(role)
|
@@ -183,7 +187,7 @@ module ActiveFacts
|
|
183
187
|
#puts "Setting #{self.class.name} #{object_id}.@#{role.name} to #{(value ? true : nil).inspect}"
|
184
188
|
instance_variable_set("@#{role.name}", value ? true : nil)
|
185
189
|
# REVISIT: Provide a way to find all instances playing/not playing this role
|
186
|
-
# Analogous to true.
|
190
|
+
# Analogous to true.all_thing_as_role_name...
|
187
191
|
end
|
188
192
|
end
|
189
193
|
define_single_role_getter(role)
|
@@ -192,14 +196,16 @@ module ActiveFacts
|
|
192
196
|
def define_single_role_getter(role)
|
193
197
|
class_eval do
|
194
198
|
define_method role.name do
|
195
|
-
instance_variable_get("@#{role.name}") rescue nil
|
199
|
+
i = instance_variable_get("@#{role.name}") rescue nil
|
200
|
+
i ? RoleProxy.new(role, i) : i
|
201
|
+
i
|
196
202
|
end
|
197
203
|
end
|
198
204
|
end
|
199
205
|
|
200
206
|
# REVISIT: Add __add_to(constellation) and __remove(constellation) here?
|
201
207
|
def define_single_role_accessor(role, one_to_one)
|
202
|
-
# puts "Defining #{basename}.#{role.name} to #{role.
|
208
|
+
# puts "Defining #{basename}.#{role.name} to #{role.counterpart_concept.basename} (#{one_to_one ? "assigning" : "populating"} #{role.counterpart.name})"
|
203
209
|
define_single_role_getter(role)
|
204
210
|
|
205
211
|
if (one_to_one)
|
@@ -213,12 +219,11 @@ module ActiveFacts
|
|
213
219
|
define_single_role_setter(role, nullify_reference, assign_reference)
|
214
220
|
else
|
215
221
|
# This gets called to delete this object from the role value array in the old correspondent
|
216
|
-
delete_reference = lambda{|from, role_name, value| from.send(role_name).
|
222
|
+
delete_reference = lambda{|from, role_name, value| from.send(role_name).update(value, nil) }
|
217
223
|
|
218
224
|
# This gets called to replace an old value by a new one in the related role value array of a new correspondent
|
219
225
|
replace_reference = lambda{|from, role_name, old_value, value|
|
220
|
-
|
221
|
-
array.__replace(array - [old_value].compact + [value])
|
226
|
+
from.send(role_name).update(old_value, value)
|
222
227
|
}
|
223
228
|
|
224
229
|
define_single_role_setter(role, delete_reference, replace_reference)
|
@@ -230,8 +235,8 @@ module ActiveFacts
|
|
230
235
|
define_method "#{role.name}=" do |value|
|
231
236
|
role_var = "@#{role.name}"
|
232
237
|
|
233
|
-
# If role.
|
234
|
-
role.
|
238
|
+
# If role.counterpart_concept isn't bound to a class yet, bind it.
|
239
|
+
role.resolve_counterpart(self.class.vocabulary) unless role.counterpart_concept.is_a?(Class)
|
235
240
|
|
236
241
|
# Get old value, and jump out early if it's unchanged:
|
237
242
|
old = instance_variable_get(role_var) rescue nil
|
@@ -242,7 +247,13 @@ module ActiveFacts
|
|
242
247
|
|
243
248
|
# DEBUG: puts "assign #{self.class.basename}.#{role.name} <-> #{value.inspect}.#{role.counterpart.name}#{old ? " (was #{old.inspect})" : ""}"
|
244
249
|
|
245
|
-
# REVISIT:
|
250
|
+
# REVISIT: A frozen-key solution could be used to allow changing identifying roles.
|
251
|
+
# The key would be frozen, allowing indices and counterparts to de-assign,
|
252
|
+
# but delay re-assignment until defrosted.
|
253
|
+
# That would also allow caching the identifying_role_values, a performance win.
|
254
|
+
|
255
|
+
# This allows setting and clearing identifying roles, but not changing them.
|
256
|
+
raise "#{self.class.basename}: illegal attempt to modify identifying role #{role.name}" if role.is_identifying && value != nil && old != nil
|
246
257
|
|
247
258
|
# puts "Setting binary #{role_var} to #{value.verbalise}"
|
248
259
|
instance_variable_set(role_var, value)
|
@@ -260,7 +271,7 @@ module ActiveFacts
|
|
260
271
|
class_eval do
|
261
272
|
define_method "#{role.name}" do
|
262
273
|
unless (r = instance_variable_get(role_var = "@#{role.name}") rescue nil)
|
263
|
-
r = instance_variable_set(role_var,
|
274
|
+
r = instance_variable_set(role_var, RoleValues.new)
|
264
275
|
end
|
265
276
|
# puts "fetching #{self.class.basename}.#{role.name} array, got #{r.class}, first is #{r[0] ? r[0].verbalise : "nil"}"
|
266
277
|
r
|
@@ -283,13 +294,13 @@ module ActiveFacts
|
|
283
294
|
# Role Name
|
284
295
|
# else:
|
285
296
|
# Leading Adjective
|
286
|
-
# Role
|
297
|
+
# Role counterpart_concept name (not role name)
|
287
298
|
# Trailing Adjective
|
288
|
-
# "
|
299
|
+
# "_as_<other_role_name>" if other_role_name != this role counterpart_concept's name, and not other_player_this_player
|
289
300
|
def extract_binary_params(one_to_one, args)
|
290
301
|
# Params:
|
291
302
|
# role_name (Symbol)
|
292
|
-
# other
|
303
|
+
# other counterpart_concept (Symbol or Class)
|
293
304
|
# mandatory (:mandatory)
|
294
305
|
# other end role name if any (Symbol),
|
295
306
|
role_name = nil
|
@@ -304,6 +315,7 @@ module ActiveFacts
|
|
304
315
|
role_name = a.to_sym
|
305
316
|
when Class
|
306
317
|
role_name = a.name.snakecase.to_sym
|
318
|
+
puts "#{a.name.snakecase} -> #{role_name}"
|
307
319
|
else
|
308
320
|
raise "Illegal first parameter to role: #{a.inspect}"
|
309
321
|
end
|
@@ -336,8 +348,8 @@ module ActiveFacts
|
|
336
348
|
args.shift
|
337
349
|
end
|
338
350
|
|
339
|
-
if Symbol
|
340
|
-
related_role_name = args.shift
|
351
|
+
if Symbol === args[0]
|
352
|
+
related_role_name = args.shift.to_s
|
341
353
|
end
|
342
354
|
|
343
355
|
reading = args[0]
|
@@ -345,25 +357,26 @@ module ActiveFacts
|
|
345
357
|
# Avoid a confusing mismatch:
|
346
358
|
# Note that if you have a role "supervisor" and a sub-class "Supervisor", this'll bitch.
|
347
359
|
if (Class === related && (indicated = vocabulary.concept(role_name)) && indicated != related)
|
348
|
-
raise "Role name #{role_name} indicates a different
|
360
|
+
raise "Role name #{role_name} indicates a different counterpart concept #{indicated} than specified"
|
349
361
|
end
|
350
362
|
|
351
|
-
#
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
363
|
+
# This code probably isn't as quick or simple as it could be, but it does work right,
|
364
|
+
# and that was pretty hard, because the variable naming is all over the shop. Should fix
|
365
|
+
# the naming first (here and in generate/oo.rb) then figure out how to speed it up.
|
366
|
+
# Note that oo.rb names things from the opposite end, so you wind up in a maze of mirrors.
|
367
|
+
other_role_method =
|
368
|
+
(one_to_one ? "" : "all_") +
|
369
|
+
(related_role_name || role_player)
|
370
|
+
if role_name.to_s != related_name and
|
371
|
+
(!related_role_name || related_role_name == role_player)
|
372
|
+
other_role_method += "_as_#{role_name}"
|
361
373
|
end
|
374
|
+
#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}"
|
362
375
|
|
363
376
|
[ role_name,
|
364
377
|
related,
|
365
378
|
mandatory,
|
366
|
-
|
379
|
+
other_role_method.to_sym
|
367
380
|
]
|
368
381
|
end
|
369
382
|
|