activefacts-compositions 1.9.10 → 1.9.12
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/activefacts-compositions.gemspec +6 -5
- data/bin/afcomp +192 -0
- data/bin/schema_compositor +26 -11
- data/lib/activefacts/compositions/binary.rb +4 -0
- data/lib/activefacts/compositions/compositor.rb +79 -7
- data/lib/activefacts/compositions/datavault.rb +308 -110
- data/lib/activefacts/compositions/docgraph.rb +798 -0
- data/lib/activefacts/compositions/relational.rb +8 -8
- data/lib/activefacts/compositions/staging.rb +3 -2
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/cwm.rb +11 -17
- data/lib/activefacts/generator/oo.rb +2 -3
- data/lib/activefacts/generator/rails/models.rb +2 -3
- data/lib/activefacts/generator/rails/schema.rb +2 -3
- data/lib/activefacts/generator/ruby.rb +1 -2
- data/lib/activefacts/generator/sql.rb +29 -15
- data/lib/activefacts/generator/summary.rb +22 -17
- data/lib/activefacts/generator/transgen.rb +144 -0
- data/lib/activefacts/generator/validate.rb +3 -3
- metadata +59 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ca6ad3d9944b1f64ffde699ef36bd7b26764b0e
|
4
|
+
data.tar.gz: 352761a4484292f70eeb90763d7518c892d9a562
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee1a8f1fe902ab61728c16adadedb68fe4bc2b40339d478c9b1d59e27d73c14a57c4d760a6cd61787b3eb0fe86cd9dc24da0c03bcb3609893b62967e4a6bc1bb
|
7
|
+
data.tar.gz: f979de0913251692a2be5c7f115ce9d81fb58147c04af65746fd1e4574723513bd99f877fa0409df316fa8aa94ecf28c37498f6477432ca862d4a912129df761
|
data/.gitignore
CHANGED
@@ -23,11 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
24
|
spec.add_development_dependency "rspec", "~> 3.3"
|
25
25
|
|
26
|
-
spec.
|
27
|
-
|
28
|
-
spec.add_runtime_dependency("activefacts-api", "~> 1", ">= 1.9.11")
|
29
|
-
spec.add_runtime_dependency("activefacts-metamodel", "~> 1", ">= 1.9.12")
|
26
|
+
spec.add_runtime_dependency "activesupport", "~> 4", ">= 4.2.7"
|
27
|
+
spec.add_runtime_dependency "json", "~> 1.8"
|
30
28
|
spec.add_runtime_dependency "tracing", "~> 2", ">= 2.0.6"
|
31
29
|
|
32
|
-
spec.
|
30
|
+
spec.add_development_dependency "activefacts", "~> 1", ">= 1.8"
|
31
|
+
spec.add_runtime_dependency "activefacts-api", "~> 1", ">= 1.9.11"
|
32
|
+
spec.add_runtime_dependency "activefacts-metamodel", "~> 1", ">= 1.9.15"
|
33
|
+
spec.add_runtime_dependency "activefacts-cql", "~> 1", ">= 1.9"
|
33
34
|
end
|
data/bin/afcomp
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# ActiveFacts: Read a model (CQL, ORM, etc), run a compositor, then a generator
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
8
|
+
$:.unshift File.dirname(File.expand_path(__FILE__))+"/../lib"
|
9
|
+
|
10
|
+
require 'pathname'
|
11
|
+
require 'activefacts/loadable'
|
12
|
+
require 'activefacts/metamodel'
|
13
|
+
require 'activefacts/compositions'
|
14
|
+
require 'activefacts/generator'
|
15
|
+
|
16
|
+
class SchemaCompositor
|
17
|
+
EXTENSIONS = ['fiml', 'fidl', 'fiql', 'cql']
|
18
|
+
|
19
|
+
attr_reader :options
|
20
|
+
attr_reader :compositors
|
21
|
+
attr_reader :generators
|
22
|
+
|
23
|
+
# Parse options into a hash, and values for each option into a hash
|
24
|
+
def initialize argv
|
25
|
+
@options = {}
|
26
|
+
while argv[0] =~ /^-/
|
27
|
+
option, value = argv.shift.split(/:/, 2)
|
28
|
+
csv = (value =~ /,/ ? value.split(',') : Array(value))
|
29
|
+
modes = csv.inject({}){|h,s| k, v = s.split(/=/, 2); h[k] = v || true; h }
|
30
|
+
@options[option.sub(/^-*/,'')] = modes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Load and enumerate all available modules in this path
|
35
|
+
def enumerate_available path
|
36
|
+
trace :loading, "Enumerating under #{path.inspect}" do
|
37
|
+
Loadable.new(path).
|
38
|
+
enumerate.
|
39
|
+
select do |filename|
|
40
|
+
begin
|
41
|
+
require(pathname = path+"/"+filename)
|
42
|
+
trace :loading, "Loaded #{pathname}"
|
43
|
+
# rescue LoadError => e
|
44
|
+
# trace :loading, "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
|
45
|
+
# rescue Exception => e
|
46
|
+
# $stderr.puts "Can't load #{pathname}: #{e.class}: #{e.message} #{e.backtrace[0]}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def arrange_actions
|
53
|
+
# Arrange the requested compositors and generators:
|
54
|
+
@compositors = []
|
55
|
+
@generators = []
|
56
|
+
@options.clone.each do |option, modes|
|
57
|
+
|
58
|
+
# Flip option and first mode if option is source or target
|
59
|
+
if option == 'source' || option == 'target'
|
60
|
+
compositor, flag = modes.shift
|
61
|
+
modes[option] = true
|
62
|
+
option = compositor
|
63
|
+
end
|
64
|
+
|
65
|
+
if action = ActiveFacts::Compositions.compositors[option]
|
66
|
+
options.delete(option)
|
67
|
+
check_options(action, modes)
|
68
|
+
@compositors << [action, modes, option]
|
69
|
+
elsif action = ActiveFacts::Generators.generators[option]
|
70
|
+
options.delete(option)
|
71
|
+
check_options(action, modes)
|
72
|
+
@generators << [action, modes, option]
|
73
|
+
else
|
74
|
+
$stderr.puts "Action --#{option} is not recognised"
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
if modes['help']
|
78
|
+
puts "Help for #{option} is not yet available"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_files argv
|
84
|
+
# Process each input file:
|
85
|
+
argv.each do |arg|
|
86
|
+
filename, input_options = *arg.split(/=/, 2)
|
87
|
+
|
88
|
+
# Load the correct file type input method
|
89
|
+
pathname, basename, extension = * /(?:(.*)[\/\\])?(.*)\.([^.]*)$/.match(filename).captures
|
90
|
+
if EXTENSIONS.detect { |e| extension == e }
|
91
|
+
extension = "cql"
|
92
|
+
end
|
93
|
+
input_handler = "activefacts/input/#{extension}"
|
94
|
+
require input_handler
|
95
|
+
|
96
|
+
input_class = extension.upcase
|
97
|
+
input_klass = ActiveFacts::Input.const_get(input_class.to_sym)
|
98
|
+
raise "Expected #{input_handler} to define #{input_class}" unless input_klass
|
99
|
+
|
100
|
+
# Read the input file:
|
101
|
+
vocabulary =
|
102
|
+
if input_klass
|
103
|
+
begin
|
104
|
+
input_klass.readfile(filename, *input_options)
|
105
|
+
rescue => e
|
106
|
+
$stderr.puts "#{e.message}"
|
107
|
+
if trace :exception
|
108
|
+
$stderr.puts "\t#{e.backtrace*"\n\t"}"
|
109
|
+
else
|
110
|
+
$stderr.puts "\t#{e.backtrace[0]}"
|
111
|
+
end
|
112
|
+
exit 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
exit 0 unless vocabulary
|
116
|
+
vocabulary.finalise unless vocabulary == true
|
117
|
+
|
118
|
+
# Run one compositor
|
119
|
+
if @compositors.size != 1
|
120
|
+
raise "Expected one compositor"
|
121
|
+
end
|
122
|
+
|
123
|
+
compositor_klass, modes, option = @compositors[0]
|
124
|
+
compositor = compositor_klass.new(vocabulary.constellation, basename, modes)
|
125
|
+
compositor.generate
|
126
|
+
|
127
|
+
# Run each generator
|
128
|
+
@generators.each do |generator_klass, modes|
|
129
|
+
output = generator_klass.new(compositor.composition, modes).generate
|
130
|
+
puts output if output
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def action_name action
|
136
|
+
action.name.sub(/ActiveFacts::[^:]+::/,'').gsub(/::/,'/').downcase
|
137
|
+
end
|
138
|
+
|
139
|
+
def display_options action, stream = $stdout
|
140
|
+
options = action.options
|
141
|
+
name = action.name.sub(/ActiveFacts::[^:]+::/,'').gsub(/::/,'/').downcase
|
142
|
+
if options.empty?
|
143
|
+
stream.puts "There are no options for --#{action_name action}"
|
144
|
+
else
|
145
|
+
stream.puts "Options for --#{name} (say e.g. --#{action_name action}=option1=value,option2)"
|
146
|
+
options.keys.sort.each do |key|
|
147
|
+
type, description = *options[key]
|
148
|
+
tag =
|
149
|
+
key.to_s +
|
150
|
+
case type
|
151
|
+
when NilClass,'Boolean', TrueClass
|
152
|
+
''
|
153
|
+
when Numeric
|
154
|
+
' num'
|
155
|
+
when Pathname
|
156
|
+
' file'
|
157
|
+
else
|
158
|
+
' str'
|
159
|
+
end
|
160
|
+
|
161
|
+
stream.puts "\t#{tag}#{' '*(24-tag.size)}#{description}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Ensure that the options provided are supported by the action
|
167
|
+
def check_options action, modes
|
168
|
+
if modes['help']
|
169
|
+
display_options(action)
|
170
|
+
exit
|
171
|
+
end
|
172
|
+
options = action.options
|
173
|
+
unsupported = modes.keys.select{|k| !options.has_key?(k.to_sym)}
|
174
|
+
return if unsupported.empty?
|
175
|
+
$stderr.puts "Action --#{action_name action} does not support #{unsupported.size >1 ? 'these options' : 'this option'}: #{unsupported*', '}"
|
176
|
+
display_options(action, $stderr)
|
177
|
+
exit 1
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
sc = SchemaCompositor.new(ARGV)
|
182
|
+
sc.enumerate_available('activefacts/compositions')
|
183
|
+
sc.enumerate_available('activefacts/generator')
|
184
|
+
if sc.options['help']
|
185
|
+
puts "Available compositors:\n\t#{ActiveFacts::Compositions.compositors.keys.sort*"\n\t"}\n\n"
|
186
|
+
puts "Available generators:\n\t#{ActiveFacts::Generators.generators.keys.sort*"\n\t"}\n\n"
|
187
|
+
puts "To get help for a particular action, follow it by =help, e.g. --relational=help"
|
188
|
+
exit
|
189
|
+
end
|
190
|
+
|
191
|
+
sc.arrange_actions
|
192
|
+
sc.process_files ARGV
|
data/bin/schema_compositor
CHANGED
@@ -14,6 +14,8 @@ require 'activefacts/compositions'
|
|
14
14
|
require 'activefacts/generator'
|
15
15
|
|
16
16
|
class SchemaCompositor
|
17
|
+
EXTENSIONS = ['fiml', 'fidl', 'fiql', 'cql']
|
18
|
+
|
17
19
|
attr_reader :options
|
18
20
|
attr_reader :compositors
|
19
21
|
attr_reader :generators
|
@@ -22,7 +24,7 @@ class SchemaCompositor
|
|
22
24
|
def initialize argv
|
23
25
|
@options = {}
|
24
26
|
while argv[0] =~ /^-/
|
25
|
-
option, value = argv.shift.split(
|
27
|
+
option, value = argv.shift.split(/[=:]/, 2)
|
26
28
|
csv = (value =~ /,/ ? value.split(',') : Array(value))
|
27
29
|
modes = csv.inject({}){|h,s| k, v = s.split(/=/, 2); h[k] = v || true; h }
|
28
30
|
@options[option.sub(/^-*/,'')] = modes
|
@@ -52,14 +54,22 @@ class SchemaCompositor
|
|
52
54
|
@compositors = []
|
53
55
|
@generators = []
|
54
56
|
@options.clone.each do |option, modes|
|
57
|
+
|
58
|
+
# Flip option and first mode if option is source or target
|
59
|
+
if option == 'source' || option == 'target'
|
60
|
+
compositor, flag = modes.shift
|
61
|
+
modes[option] = true
|
62
|
+
option = compositor
|
63
|
+
end
|
64
|
+
|
55
65
|
if action = ActiveFacts::Compositions.compositors[option]
|
56
66
|
options.delete(option)
|
57
67
|
check_options(action, modes)
|
58
|
-
@compositors << [action, modes]
|
68
|
+
@compositors << [action, modes, option]
|
59
69
|
elsif action = ActiveFacts::Generators.generators[option]
|
60
70
|
options.delete(option)
|
61
71
|
check_options(action, modes)
|
62
|
-
@generators << [action, modes]
|
72
|
+
@generators << [action, modes, option]
|
63
73
|
else
|
64
74
|
$stderr.puts "Action --#{option} is not recognised"
|
65
75
|
exit 1
|
@@ -77,6 +87,9 @@ class SchemaCompositor
|
|
77
87
|
|
78
88
|
# Load the correct file type input method
|
79
89
|
pathname, basename, extension = * /(?:(.*)[\/\\])?(.*)\.([^.]*)$/.match(filename).captures
|
90
|
+
if EXTENSIONS.detect { |e| extension == e }
|
91
|
+
extension = "cql"
|
92
|
+
end
|
80
93
|
input_handler = "activefacts/input/#{extension}"
|
81
94
|
require input_handler
|
82
95
|
|
@@ -102,16 +115,18 @@ class SchemaCompositor
|
|
102
115
|
exit 0 unless vocabulary
|
103
116
|
vocabulary.finalise unless vocabulary == true
|
104
117
|
|
105
|
-
# Run one
|
106
|
-
|
107
|
-
|
108
|
-
compositor.generate
|
109
|
-
compositor.composition
|
118
|
+
# Run one compositor
|
119
|
+
if @compositors.size != 1
|
120
|
+
raise "Expected one compositor, use --help for a list"
|
110
121
|
end
|
111
122
|
|
123
|
+
compositor_klass, modes, option = @compositors[0]
|
124
|
+
compositor = compositor_klass.new(vocabulary.constellation, basename, modes)
|
125
|
+
compositor.generate
|
126
|
+
|
112
127
|
# Run each generator
|
113
128
|
@generators.each do |generator_klass, modes|
|
114
|
-
output = generator_klass.new(
|
129
|
+
output = generator_klass.new(compositor.composition, modes).generate
|
115
130
|
puts output if output
|
116
131
|
end
|
117
132
|
end
|
@@ -127,7 +142,7 @@ class SchemaCompositor
|
|
127
142
|
if options.empty?
|
128
143
|
stream.puts "There are no options for --#{action_name action}"
|
129
144
|
else
|
130
|
-
stream.puts "Options for --#{name} (say e.g. --#{action_name action}
|
145
|
+
stream.puts "Options for --#{name} (say e.g. --#{action_name action}:option1=value,option2)"
|
131
146
|
options.keys.sort.each do |key|
|
132
147
|
type, description = *options[key]
|
133
148
|
tag =
|
@@ -169,7 +184,7 @@ sc.enumerate_available('activefacts/generator')
|
|
169
184
|
if sc.options['help']
|
170
185
|
puts "Available compositors:\n\t#{ActiveFacts::Compositions.compositors.keys.sort*"\n\t"}\n\n"
|
171
186
|
puts "Available generators:\n\t#{ActiveFacts::Generators.generators.keys.sort*"\n\t"}\n\n"
|
172
|
-
puts "To get help for a particular action, follow it by =help, e.g. --relational
|
187
|
+
puts "To get help for a particular action, follow it by =help, e.g. --relational:help"
|
173
188
|
exit
|
174
189
|
end
|
175
190
|
|
@@ -10,6 +10,10 @@ require "activefacts/compositions"
|
|
10
10
|
module ActiveFacts
|
11
11
|
module Compositions
|
12
12
|
class Binary < Compositor
|
13
|
+
def initialize constellation, name, options = {}, compositor_name = 'Binary'
|
14
|
+
super constellation, name, options, compositor_name
|
15
|
+
end
|
16
|
+
|
13
17
|
def self.options
|
14
18
|
{}
|
15
19
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Compositions, Fundamental Compositor
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# All Compositors derive from this one, which can calculate the basic binary bi-directional mapping
|
5
5
|
#
|
6
6
|
# The term "reference" used here means either an Absorption
|
@@ -19,13 +19,21 @@ module ActiveFacts
|
|
19
19
|
attr_reader :options, :name, :composition
|
20
20
|
|
21
21
|
def self.options
|
22
|
-
{
|
22
|
+
{
|
23
|
+
# source: ['Boolean', "Generate composition for source schema"],
|
24
|
+
# target: ['Boolean', "Generate composition for target schema"],
|
25
|
+
# transform: ['Boolean', "Generate composition for transform schema"]
|
26
|
+
}
|
23
27
|
end
|
24
28
|
|
25
|
-
def initialize constellation, name, options = {}
|
29
|
+
def initialize constellation, name, options = {}, compositor_name
|
26
30
|
@constellation = constellation
|
27
31
|
@name = name
|
32
|
+
@compositor_name = compositor_name
|
28
33
|
@options = options
|
34
|
+
# @option_source = options.delete('source')
|
35
|
+
# @option_target = options.delete('target')
|
36
|
+
# @option_transform = options.delete('transform')
|
29
37
|
end
|
30
38
|
|
31
39
|
# Generate all Mappings into @binary_mappings for a binary composition of all ObjectTypes in this constellation
|
@@ -37,7 +45,7 @@ module ActiveFacts
|
|
37
45
|
@composition.retract
|
38
46
|
end
|
39
47
|
|
40
|
-
@composition = @constellation.Composition(:new, :name => @name)
|
48
|
+
@composition = @constellation.Composition(:new, :name => @name, :compositor_name => @compositor_name)
|
41
49
|
preload_preferred_identifiers
|
42
50
|
populate_references
|
43
51
|
end
|
@@ -92,6 +100,35 @@ module ActiveFacts
|
|
92
100
|
trace :binarize, "Populating #{a.inspect}"
|
93
101
|
end
|
94
102
|
|
103
|
+
def find_topic import_role
|
104
|
+
if import = @constellation.Import.values.select{ |import| import.import_role == import_role }.first
|
105
|
+
import.precursor_topic
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_binary_mappings schema_topic
|
112
|
+
trace :binarize, "Build_binary_mappings for #{schema_topic.topic_name}"
|
113
|
+
if @schema_topics.key?(schema_topic)
|
114
|
+
trace :binarize, "already built, skip"
|
115
|
+
return
|
116
|
+
end
|
117
|
+
@schema_topics[schema_topic] = true
|
118
|
+
|
119
|
+
# add binary mapping for all object types in this schema
|
120
|
+
schema_topic.all_concept.each do |concept|
|
121
|
+
if concept.object_type
|
122
|
+
@binary_mappings[concept.object_type]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# recurse through precursor schemas
|
127
|
+
schema_topic.all_import.each do |import|
|
128
|
+
build_binary_mappings(import.precursor_topic)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
95
132
|
def populate_references
|
96
133
|
# A table of Mappings by object type, with a default Mapping for each:
|
97
134
|
@binary_mappings = Hash.new do |h, object_type|
|
@@ -103,11 +140,46 @@ module ActiveFacts
|
|
103
140
|
end
|
104
141
|
@component_by_fact = {}
|
105
142
|
|
106
|
-
@
|
143
|
+
@schema_topics = {}
|
144
|
+
=begin
|
145
|
+
if @option_source || @option_target
|
146
|
+
import_role = @option_source ? 'source' : 'target'
|
147
|
+
trace :binarize, "Build binary bindings for #{import_role} schema" do
|
148
|
+
if schema_topic = find_topic(import_role)
|
149
|
+
build_binary_mappings(schema_topic)
|
150
|
+
else
|
151
|
+
raise "Could not find #{import_role} schema"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
elsif @option_transform
|
155
|
+
trace :binarize, "Build binary bindings for transform schema" do
|
156
|
+
transform_topic = @constellation.Topic.values.select do |topic|
|
157
|
+
nr_importers = @constellation.Import.values.select{|i| i.precursor_topic == topic}.size
|
158
|
+
nr_importers == 0 # This topic is not imported by anything, it's the one
|
159
|
+
end.first
|
160
|
+
|
161
|
+
# add binary mapping for all object types in the transform schema
|
162
|
+
transform_topic.all_concept.each do |concept|
|
163
|
+
if concept.object_type
|
164
|
+
@binary_mappings[concept.object_type]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
else
|
169
|
+
=end
|
170
|
+
trace :binarize, "Build binary bindings for full schema" do
|
171
|
+
@constellation.ObjectType.each do |key, object_type|
|
172
|
+
@binary_mappings[object_type] # Ensure we create the top Mapping even if it has no references
|
173
|
+
end
|
174
|
+
end
|
175
|
+
# end
|
176
|
+
|
177
|
+
@binary_mappings.each do |object_type, mapping|
|
107
178
|
trace :binarize, "Populating possible absorptions for #{object_type.name}" do
|
108
|
-
@binary_mappings[object_type] # Ensure we create the top Mapping even if it has no references
|
109
179
|
|
110
180
|
object_type.all_role.each do |role|
|
181
|
+
# Exclude fact types not in @schema_topics
|
182
|
+
next if @schema_topics.size > 0 && !@schema_topics.key?(role.fact_type.concept.topic)
|
111
183
|
# Exclude base roles in objectified fact types (unless unary); just use link fact types
|
112
184
|
next if role.fact_type.entity_type && role.fact_type.all_role.size != 1
|
113
185
|
next if role.variable # REVISIT: Continue to ignore roles in derived fact types?
|
@@ -166,7 +238,7 @@ module ActiveFacts
|
|
166
238
|
detect do |c|
|
167
239
|
(rr = c.role_sequence.all_role_ref.single) and
|
168
240
|
rr.role == role
|
169
|
-
end
|
241
|
+
end
|
170
242
|
|
171
243
|
if from_1 || fact_type.entity_type
|
172
244
|
# This is a role in an objectified fact type
|