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