activefacts 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|