activefacts-generators 1.8.2 → 1.8.3
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/activefacts-generators.gemspec +2 -2
- data/lib/activefacts/generators/helpers/rails.rb +30 -30
- data/lib/activefacts/generators/rails/models.rb +189 -189
- data/lib/activefacts/generators/rails/schema.rb +184 -184
- data/lib/activefacts/generators/transform/datavault.rb +3 -0
- metadata +2 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18414a4533151ffe192e7ae375366327595faf25
|
4
|
+
data.tar.gz: 6d31af89a93cfa0d63edf126cacb3df917770cd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 314a64ec5dcf991328ef3ffcc1fdb18b5f99673bb1c27695d0e9f796ac922c26b0acb94517c146401dbb08d3e6adbc965c6e44b43ce13e22d0bbf8cf021a2b50
|
7
|
+
data.tar.gz: 0ed72bc6312ef1eae1059d5a93f530b056f9cec1ea7b690eaa93f1e6933be4dc4376cf2175f82846e8cf6dd49972714402ce3c539d76a1b828baffd915335905
|
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "activefacts-generators"
|
7
|
-
spec.version = "1.8.
|
7
|
+
spec.version = "1.8.3"
|
8
8
|
spec.authors = ["Clifford Heath"]
|
9
9
|
spec.email = ["clifford.heath@gmail.com"]
|
10
10
|
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.add_development_dependency "bundler", ">= 1.10"
|
19
|
+
spec.add_development_dependency "bundler", ">= 1.10"
|
20
20
|
spec.add_development_dependency "rake", "~> 10.0"
|
21
21
|
spec.add_development_dependency "rspec", "~> 3.3"
|
22
22
|
|
@@ -2,29 +2,29 @@ module ActiveFacts
|
|
2
2
|
module Generators
|
3
3
|
module Rails
|
4
4
|
module Helpers
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
5
|
+
def rails_plural_name name
|
6
|
+
# Crunch spaces and pluralise the first part, all in snake_case
|
7
|
+
name.pop if name.is_a?(Array) and name.last == []
|
8
|
+
name = name[0]*'_' if name.is_a?(Array) and name.size == 1
|
9
|
+
if name.is_a?(Array)
|
10
|
+
name = ActiveSupport::Inflector.tableize((name[0]*'_').gsub(/\s+/, '_')) +
|
11
|
+
'_' +
|
12
|
+
ActiveSupport::Inflector.underscore((name[1..-1].flatten*'_').gsub(/\s+/, '_'))
|
13
|
+
else
|
14
|
+
ActiveSupport::Inflector.tableize(name.gsub(/\s+/, '_'))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def rails_singular_name name
|
19
|
+
# Crunch spaces and convert to snake_case
|
20
|
+
name = name.flatten*'_' if name.is_a?(Array)
|
21
|
+
ActiveSupport::Inflector.underscore(name.gsub(/\s+/, '_'))
|
22
|
+
end
|
23
|
+
|
24
|
+
def rails_class_name name
|
25
|
+
name = name*'_' if name.is_a?(Array)
|
26
|
+
ActiveSupport::Inflector.camelize(name.gsub(/\s+/, '_'))
|
27
|
+
end
|
28
28
|
|
29
29
|
end
|
30
30
|
end
|
@@ -40,16 +40,16 @@ module ActiveFacts
|
|
40
40
|
include Generate::Rails::Helpers
|
41
41
|
|
42
42
|
def rails_from_association_name
|
43
|
-
|
43
|
+
rails_singular_name(to_name.join('_'))
|
44
44
|
end
|
45
45
|
|
46
46
|
def rails_to_association
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
47
|
+
jump = jump_reference
|
48
|
+
if jump.is_one_to_one
|
49
|
+
[ "has_one", rails_singular_name(from_name)]
|
50
|
+
else
|
51
|
+
[ "has_many", rails_plural_name(from_name)]
|
52
|
+
end
|
53
53
|
end
|
54
54
|
|
55
55
|
end
|
@@ -19,212 +19,212 @@ module ActiveFacts
|
|
19
19
|
module Rails
|
20
20
|
# Generate Rails models for the vocabulary
|
21
21
|
# Invoke as
|
22
|
-
# afgen --rails/
|
22
|
+
# afgen --rails/models[=options] <file>.cql
|
23
23
|
class Models
|
24
24
|
|
25
|
-
|
25
|
+
HEADER = "# Auto-generated from CQL, edits will be lost"
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
29
|
+
def initialize(vocabulary, *options)
|
30
|
+
@vocabulary = vocabulary
|
31
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
32
|
+
help if options.include? "help"
|
33
|
+
options.delete_if { |option| @output = $1 if option =~ /^output=(.*)/ }
|
34
|
+
@concern = nil
|
35
|
+
options.delete_if { |option| @concern = $1 if option =~ /^concern=(.*)/ }
|
36
|
+
@validations = true
|
37
|
+
options.delete_if { |option| @validations = eval($1) if option =~ /^validation=(.*)/ }
|
38
|
+
end
|
39
|
+
|
40
|
+
def help
|
41
|
+
@helping = true
|
42
|
+
warn %Q{Options for --rails/schema:
|
43
|
+
output=dir Overwrite model files into this output directory
|
44
|
+
concern=name Namespace for the concerns
|
45
|
+
validation=false Disable generation of validations
|
46
46
|
}
|
47
|
-
|
47
|
+
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def warn *a
|
50
|
+
$stderr.puts *a
|
51
|
+
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
def puts s
|
54
|
+
@out.puts s
|
55
|
+
end
|
56
56
|
|
57
57
|
public
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
58
|
+
def generate(out = $>) #:nodoc:
|
59
|
+
return if @helping
|
60
|
+
@out = out
|
61
|
+
list_extant_files if @output
|
62
|
+
|
63
|
+
# Populate all foreignkeys first:
|
64
|
+
@vocabulary.tables.each { |table| table.foreign_keys }
|
65
|
+
ok = true
|
66
|
+
@vocabulary.tables.each do |table|
|
67
|
+
ok &= generate_table(table)
|
68
|
+
end
|
69
|
+
warn "\# #{@vocabulary.name} generated with errors" unless ok
|
70
|
+
delete_old_generated_files if @output
|
71
|
+
ok
|
72
|
+
end
|
73
|
+
|
74
|
+
def list_extant_files
|
75
|
+
@preexisting_files = Dir[@output+'/*.rb']
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete_old_generated_files
|
79
|
+
remaining = []
|
80
|
+
cleaned = 0
|
81
|
+
@preexisting_files.each do |pathname|
|
82
|
+
if generated_file_exists(pathname) == true
|
83
|
+
File.unlink(pathname)
|
84
|
+
cleaned += 1
|
85
|
+
else
|
86
|
+
remaining << pathname
|
87
|
+
end
|
88
|
+
end
|
89
|
+
$stderr.puts "Cleaned up #{cleaned} old generated files" if @preexisting_files.size > 0
|
90
|
+
$stderr.puts "Remaining non-generated files:\n\t#{remaining*"\n\t"}" if remaining.size > 0
|
91
|
+
end
|
92
|
+
|
93
|
+
def generated_file_exists pathname
|
94
|
+
File.open(pathname, 'r') do |existing|
|
95
|
+
first_lines = existing.read(1024) # Make it possible to pass over a magic charset comment
|
96
|
+
if first_lines.length == 0 or first_lines =~ %r{^#{HEADER}}
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
return false # File exists, but is not generated
|
101
|
+
rescue Errno::ENOENT
|
102
|
+
return nil # File does not exist
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_if_ok filename
|
106
|
+
# Create a file in the output directory, being careful not to overwrite carelessly
|
107
|
+
if @output
|
108
|
+
pathname = (@output+'/'+filename).gsub(%r{//+}, '/')
|
109
|
+
@preexisting_files.reject!{|f| f == pathname } # Don't clean up this file
|
110
|
+
if generated_file_exists(pathname) == false
|
111
|
+
$stderr.puts "not overwriting non-generated file #{pathname}"
|
112
|
+
@individual_file = nil
|
113
|
+
return
|
114
|
+
end
|
115
|
+
@individual_file = @out = File.open(pathname, 'w')
|
116
|
+
puts "#{HEADER}"
|
117
|
+
end
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_associations table
|
122
|
+
# belongs_to Associations
|
123
|
+
table.foreign_keys.map do |fk|
|
124
|
+
association_name = fk.rails_from_association_name
|
125
|
+
|
126
|
+
if association_name != fk.to.rails_singular_name
|
127
|
+
# A different class_name is implied, emit an explicit one:
|
128
|
+
class_name = ", :class_name => '#{fk.to.rails_class_name}'"
|
129
|
+
end
|
130
|
+
foreign_key = ", :foreign_key => :#{fk.from_columns[0].rails_name}"
|
131
|
+
if foreign_key == fk.to.rails_singular_name+'_id'
|
132
|
+
# See lib/active_record/reflection.rb, method #derive_foreign_key
|
133
|
+
foreign_key = ''
|
134
|
+
end
|
135
|
+
|
136
|
+
%Q{
|
137
137
|
\# #{fk.verbalised_path}
|
138
138
|
belongs_to :#{association_name}#{class_name}#{foreign_key}}
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def from_associations table
|
143
|
+
# has_one/has_many Associations
|
144
|
+
table.foreign_keys_to.sort_by{|fk| fk.describe}.map do |fk|
|
145
|
+
# Get the jump reference
|
146
|
+
|
147
|
+
if fk.from_columns.size > 1
|
148
|
+
raise "Can't emit Rails associations for multi-part foreign key with #{fk.references.inspect}. Did you mean to use --transform/surrogate"
|
149
|
+
end
|
150
|
+
|
151
|
+
association_type, association_name = *fk.rails_to_association
|
152
|
+
|
153
|
+
ref = fk.jump_reference
|
154
|
+
[
|
155
|
+
"\n \# #{fk.verbalised_path(true)}" +
|
156
|
+
"\n" +
|
157
|
+
%Q{ #{association_type} :#{association_name}} +
|
158
|
+
%Q{, :class_name => '#{fk.from.rails_class_name}'} +
|
159
|
+
%Q{, :foreign_key => :#{fk.from_columns[0].rails_name}} +
|
160
|
+
%Q{, :dependent => :destroy}
|
161
|
+
] +
|
162
|
+
# If ref.from is a join table, we can emit a has_many :through for each other key
|
163
|
+
# REVISIT Could alternately do this for all belongs_to's in ref.from
|
164
|
+
if ref.from.identifier_columns.length > 1
|
165
|
+
ref.from.identifier_columns.map do |ic|
|
166
|
+
next nil if ic.references[0] == ref or # Skip the back-reference
|
167
|
+
ic.references[0].is_unary # or use rails_plural_name(ic.references[0].to_names) ?
|
168
|
+
# This far association name needs to be augmented for its role name
|
169
|
+
far_association_name = ic.references[0].to.rails_name
|
170
|
+
%Q{ has_many :#{far_association_name}, :through => :#{association_name}} # \# via #{ic.name}}
|
171
|
+
end
|
172
|
+
else
|
173
|
+
[]
|
174
|
+
end
|
175
|
+
end.flatten.compact
|
176
|
+
end
|
177
|
+
|
178
|
+
def column_constraints table
|
179
|
+
return [] unless @validations
|
180
|
+
ccs =
|
181
|
+
table.columns.map do |column|
|
182
|
+
name = column.rails_name
|
183
|
+
column.is_mandatory &&
|
184
|
+
!column.is_auto_assigned && !column.is_auto_timestamp ? [
|
185
|
+
" validates :#{name}, :presence => true"
|
186
|
+
] : []
|
187
|
+
end.flatten
|
188
|
+
ccs.unshift("") unless ccs.empty?
|
189
|
+
ccs
|
190
|
+
end
|
191
|
+
|
192
|
+
def model_body table
|
193
|
+
%Q{module #{table.rails_class_name}
|
194
194
|
extend ActiveSupport::Concern
|
195
195
|
included do} +
|
196
|
-
|
196
|
+
(table.identifier_columns.length == 1 ? %Q{
|
197
197
|
self.primary_key = '#{table.identifier_columns[0].rails_name}'
|
198
198
|
} : ''
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
199
|
+
) +
|
200
|
+
|
201
|
+
(
|
202
|
+
to_associations(table) +
|
203
|
+
from_associations(table) +
|
204
|
+
column_constraints(table)
|
205
|
+
) * "\n" +
|
206
|
+
%Q{
|
207
207
|
end
|
208
208
|
end
|
209
209
|
}
|
210
|
-
|
210
|
+
end
|
211
211
|
|
212
|
-
|
213
|
-
|
214
|
-
|
212
|
+
def generate_table table
|
213
|
+
old_out = @out
|
214
|
+
filename = table.rails_singular_name+'.rb'
|
215
215
|
|
216
|
-
|
216
|
+
return unless create_if_ok filename
|
217
217
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
218
|
+
puts "\n"
|
219
|
+
puts "module #{@concern}" if @concern
|
220
|
+
puts model_body(table).gsub(/^./, @concern ? ' \0' : '\0')
|
221
|
+
puts 'end' if @concern
|
222
222
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
223
|
+
true # We succeeded
|
224
|
+
ensure
|
225
|
+
@out = old_out
|
226
|
+
@individual_file.close if @individual_file
|
227
|
+
end
|
228
228
|
|
229
229
|
end
|
230
230
|
end
|
@@ -233,12 +233,12 @@ end
|
|
233
233
|
module RMap
|
234
234
|
class Column
|
235
235
|
def is_auto_timestamp
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
236
|
+
case name('_')
|
237
|
+
when /\A(created|updated)_(at|on)\Z/i
|
238
|
+
true
|
239
|
+
else
|
240
|
+
false
|
241
|
+
end
|
242
242
|
end
|
243
243
|
end
|
244
244
|
end
|
@@ -17,197 +17,197 @@ module ActiveFacts
|
|
17
17
|
# afgen --rails/schema[=options] <file>.cql
|
18
18
|
class SchemaRb
|
19
19
|
private
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
20
|
+
include RMap
|
21
|
+
|
22
|
+
def initialize(vocabulary, *options)
|
23
|
+
@vocabulary = vocabulary
|
24
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
25
|
+
help if options.include? "help"
|
26
|
+
@exclude_fks = options.include? "exclude_fks"
|
27
|
+
@include_comments = options.include? "include_comments"
|
28
|
+
@closed_world = options.include? "closed_world"
|
29
|
+
end
|
30
|
+
|
31
|
+
def help
|
32
|
+
@helping = true
|
33
|
+
warn %Q{Options for --rails/schema:
|
34
|
+
exclude_fks Don't generate foreign key definitions for use with Rails 4 or the foreigner gem
|
35
|
+
include_comments Generate a comment for each column showing the absorption path
|
36
|
+
closed_world Set this if your DBMS only allows one null in a unique index (MS SQL)
|
37
37
|
}
|
38
|
-
|
38
|
+
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
def warn *a
|
41
|
+
$stderr.puts *a
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def puts s
|
45
|
+
@out.puts s
|
46
|
+
end
|
47
47
|
|
48
48
|
public
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
50
|
+
# We sort the columns here, not in the rmap layer, because it affects
|
51
|
+
# the ordering of columns in an index :-(.
|
52
|
+
def sorted_columns table, pk, fk_columns
|
53
|
+
table.columns.sort_by do |column|
|
54
|
+
[ # Emit columns alphabetically, but PK first, then FKs, then others
|
55
|
+
case
|
56
|
+
when i = pk.index(column)
|
57
|
+
i
|
58
|
+
when fk_columns.include?(column)
|
59
|
+
pk.size+1
|
60
|
+
else
|
61
|
+
pk.size+2
|
62
|
+
end,
|
63
|
+
column.rails_name
|
64
|
+
]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_column table, pk, column
|
69
|
+
name = column.rails_name
|
70
|
+
type, params, constraints = *column.type
|
71
|
+
length = params[:length]
|
72
|
+
length &&= length.to_i
|
73
|
+
scale = params[:scale]
|
74
|
+
scale &&= scale.to_i
|
75
|
+
rails_type, length = *column.rails_type
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
77
|
+
length_name = rails_type == 'decimal' ? 'precision' : 'limit'
|
78
|
+
length_option = length ? ", #{length_name}: #{length}" : ''
|
79
|
+
scale_option = scale ? ", scale: #{scale}" : ''
|
80
|
+
|
81
|
+
comment = column.comment
|
82
|
+
null_option = ", null: #{!column.is_mandatory}"
|
83
|
+
if pk.size == 1 && pk[0] == column
|
84
|
+
case rails_type
|
85
|
+
when 'serial'
|
86
|
+
rails_type = "primary_key"
|
87
|
+
when 'uuid'
|
88
|
+
rails_type = "uuid, default: 'gen_random_uuid()', primary_key: true"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
case rails_type
|
92
|
+
when 'serial'
|
93
|
+
rails_type = 'integer' # An integer foreign key
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
(@include_comments ? [" \# #{comment}"] : []) +
|
98
|
+
[
|
99
|
+
%Q{ t.column "#{name}", :#{rails_type}#{length_option}#{scale_option}#{null_option}}
|
100
|
+
]
|
101
|
+
end
|
102
|
+
|
103
|
+
def generate_columns table, pk, fk_columns
|
104
|
+
sc = sorted_columns(table, pk, fk_columns)
|
105
|
+
lines = sc.map do |column|
|
106
|
+
generate_column table, pk, column
|
107
|
+
end
|
108
|
+
lines.flatten
|
109
|
+
end
|
110
|
+
|
111
|
+
def generate_table table, foreign_keys
|
112
|
+
ar_table_name = table.rails_name
|
113
|
+
|
114
|
+
pk = table.identifier_columns
|
115
|
+
if pk[0].is_auto_assigned
|
116
|
+
identity_column = pk[0]
|
117
|
+
warn "Warning: redundant column(s) after #{identity_column.name} in primary key of #{ar_table_name}" if pk.size > 1
|
118
|
+
end
|
119
|
+
|
120
|
+
# Get the list of references that give rise to foreign keys:
|
121
|
+
fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
|
122
|
+
|
123
|
+
# Get the list of columns that embody the foreign keys:
|
124
|
+
fk_columns = table.columns.select do |column|
|
125
|
+
column.references[0].is_simple_reference
|
126
|
+
end
|
127
|
+
|
128
|
+
# Detect if this table is a join table.
|
129
|
+
# Join tables have multi-part primary keys that are made up only of foreign keys
|
130
|
+
is_join_table = pk.length > 1 and
|
131
|
+
!pk.detect do |pk_column|
|
132
|
+
!fk_columns.include?(pk_column)
|
133
|
+
end
|
134
|
+
warn "Warning: #{table.name} has a multi-part primary key" if pk.length > 1 and !is_join_table
|
135
|
+
|
136
|
+
puts %Q{ create_table "#{ar_table_name}", id: false, force: true do |t|}
|
137
|
+
|
138
|
+
columns = generate_columns table, pk, fk_columns
|
139
|
+
|
140
|
+
unless @exclude_fks
|
141
|
+
table.foreign_keys.each do |fk|
|
142
|
+
from_columns = fk.from_columns.map{|column| column.rails_name}
|
143
|
+
to_columns = fk.to_columns.map{|column| column.rails_name}
|
144
|
+
|
145
|
+
foreign_keys.concat(
|
146
|
+
if (from_columns.length == 1)
|
147
|
+
index_name = RMap.rails_name_trunc('index_'+fk.from.rails_name+'_on_'+from_columns[0])
|
148
|
+
[
|
149
|
+
" add_foreign_key :#{fk.from.rails_name}, :#{fk.to.rails_name}, column: :#{from_columns[0]}, primary_key: :#{to_columns[0]}, on_delete: :cascade"
|
150
|
+
]+
|
151
|
+
Array(
|
152
|
+
# Index it non-uniquely only if it's not unique already:
|
153
|
+
fk.jump_reference.to_role.is_unique ? nil :
|
154
|
+
" add_index :#{fk.from.rails_name}, [:#{from_columns[0]}], unique: false, name: :#{index_name}"
|
155
|
+
)
|
156
|
+
else
|
157
|
+
# This probably isn't going to work without Dr Nic's CPK gem:
|
158
|
+
[
|
159
|
+
" add_foreign_key :#{fk.to.rails_name}, :#{fk.from.rails_name}, column: [:#{from_columns.join(':, ')}], primary_key: [:#{to_columns.join(':, ')}], on_delete: :cascade"
|
160
|
+
]
|
161
|
+
end
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
indices = table.indices
|
167
|
+
index_text = []
|
168
|
+
indices.each do |index|
|
169
|
+
next if index.is_primary && index.columns.size == 1 # We've handled this already
|
170
|
+
|
171
|
+
index_name = index.rails_name
|
172
|
+
|
173
|
+
unique = !index.columns.detect{|column| !column.is_mandatory} and !@closed_world
|
174
|
+
index_text << %Q{ add_index "#{ar_table_name}", ["#{index.columns.map{|c| c.rails_name}*'", "'}"], name: :#{index_name}#{
|
175
|
+
unique ? ", unique: true" : ''
|
176
|
+
}}
|
177
|
+
end
|
178
|
+
|
179
|
+
puts columns.join("\n")
|
180
|
+
puts " end\n\n"
|
181
|
+
|
182
|
+
puts index_text.join("\n")
|
183
|
+
puts "\n" unless index_text.empty?
|
184
|
+
end
|
185
|
+
|
186
|
+
def generate(out = $>) #:nodoc:
|
187
|
+
return if @helping
|
188
|
+
@out = out
|
189
|
+
|
190
|
+
foreign_keys = []
|
191
|
+
|
192
|
+
# If we get index names that need to be truncated, add a counter to ensure uniqueness
|
193
|
+
dup_id = 0
|
194
|
+
|
195
|
+
puts "#\n# schema.rb auto-generated using ActiveFacts for #{@vocabulary.name} on #{Date.today}\n#\n\n"
|
196
|
+
puts "ActiveRecord::Base.logger = Logger.new(STDOUT)\n"
|
197
|
+
puts "ActiveRecord::Schema.define(version: #{Time.now.strftime('%Y%m%d%H%M%S')}) do"
|
198
|
+
puts " enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')\n"
|
199
|
+
|
200
|
+
@vocabulary.tables.each do |table|
|
201
|
+
generate_table table, foreign_keys
|
202
|
+
end
|
203
|
+
|
204
|
+
unless @exclude_fks
|
205
|
+
puts ' unless ENV["EXCLUDE_FKS"]'
|
206
|
+
puts foreign_keys.join("\n")
|
207
|
+
puts ' end'
|
208
|
+
end
|
209
|
+
puts "end"
|
210
|
+
end
|
211
211
|
|
212
212
|
end
|
213
213
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activefacts-generators
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Clifford Heath
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -17,9 +17,6 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.10'
|
20
|
-
- - "~>"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 1.10.6
|
23
20
|
type: :development
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,9 +24,6 @@ dependencies:
|
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '1.10'
|
30
|
-
- - "~>"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 1.10.6
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: rake
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|