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