k_domain 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +31 -1
  3. data/STORIES.md +6 -2
  4. data/k_domain.gemspec +2 -0
  5. data/lib/k_domain/domain_model/dtos/_.rb +88 -0
  6. data/lib/k_domain/domain_model/dtos/belongs_to.rb +25 -0
  7. data/lib/k_domain/domain_model/dtos/column_old.rb +225 -0
  8. data/lib/k_domain/domain_model/dtos/dictionary/dictionary.rb +17 -0
  9. data/lib/k_domain/domain_model/dtos/domain.rb +11 -0
  10. data/lib/k_domain/domain_model/dtos/domain_statistics.rb +29 -0
  11. data/lib/k_domain/domain_model/dtos/entity.rb +338 -0
  12. data/lib/k_domain/domain_model/dtos/entity_statistics.rb +22 -0
  13. data/lib/k_domain/domain_model/dtos/foreign_key.rb +17 -0
  14. data/lib/k_domain/domain_model/dtos/has_and_belongs_to_many.rb +20 -0
  15. data/lib/k_domain/domain_model/dtos/has_many.rb +27 -0
  16. data/lib/k_domain/domain_model/dtos/has_one.rb +41 -0
  17. data/lib/k_domain/domain_model/dtos/investigate/investigate.rb +10 -0
  18. data/lib/k_domain/domain_model/dtos/investigate/issue.rb +13 -0
  19. data/lib/k_domain/domain_model/dtos/models/column.rb +49 -0
  20. data/lib/k_domain/domain_model/dtos/models/model.rb +111 -0
  21. data/lib/k_domain/domain_model/dtos/name_options.rb +10 -0
  22. data/lib/k_domain/domain_model/dtos/rails_controller.rb +10 -0
  23. data/lib/k_domain/domain_model/dtos/rails_model.rb +92 -0
  24. data/lib/k_domain/domain_model/dtos/related_entity.rb +36 -0
  25. data/lib/k_domain/domain_model/dtos/schema.rb +12 -0
  26. data/lib/k_domain/domain_model/dtos/statistics.rb +21 -0
  27. data/lib/k_domain/domain_model/dtos/validate.rb +25 -0
  28. data/lib/k_domain/domain_model/dtos/validates.rb +50 -0
  29. data/lib/k_domain/domain_model/load.rb +29 -0
  30. data/lib/k_domain/domain_model/transform.rb +94 -0
  31. data/lib/k_domain/domain_model/transform_steps/_.rb +9 -0
  32. data/lib/k_domain/domain_model/transform_steps/step.rb +123 -0
  33. data/lib/k_domain/domain_model/transform_steps/step1_attach_db_schema.rb +21 -0
  34. data/lib/k_domain/domain_model/transform_steps/step2_attach_models.rb +62 -0
  35. data/lib/k_domain/domain_model/transform_steps/step3_attach_columns.rb +137 -0
  36. data/lib/k_domain/domain_model/transform_steps/step4_attach_erd_files.rb +454 -0
  37. data/lib/k_domain/domain_model/transform_steps/step5_attach_dictionary.rb +56 -0
  38. data/lib/k_domain/raw_db_schema/dtos/_.rb +14 -0
  39. data/lib/k_domain/raw_db_schema/dtos/column.rb +16 -0
  40. data/lib/k_domain/raw_db_schema/dtos/database.rb +11 -0
  41. data/lib/k_domain/raw_db_schema/dtos/foreign_key.rb +14 -0
  42. data/lib/k_domain/raw_db_schema/dtos/index.rb +14 -0
  43. data/lib/k_domain/raw_db_schema/dtos/schema.rb +18 -0
  44. data/lib/k_domain/raw_db_schema/dtos/table.rb +21 -0
  45. data/lib/k_domain/raw_db_schema/dtos/unique_key.rb +14 -0
  46. data/lib/k_domain/raw_db_schema/load.rb +29 -0
  47. data/lib/k_domain/{raw_schema → raw_db_schema}/template.rb +0 -0
  48. data/lib/k_domain/{raw_schema → raw_db_schema}/transform.rb +37 -21
  49. data/lib/k_domain/version.rb +1 -1
  50. data/lib/k_domain.rb +14 -1
  51. metadata +74 -4
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Attach columns to models
4
+ class Step3AttachColumns < KDomain::DomainModel::Step
5
+ attr_accessor :table
6
+ attr_reader :column_name
7
+ attr_reader :column_symbol
8
+
9
+ def call
10
+ build_columns
11
+ end
12
+
13
+ def build_columns
14
+ domain_models.each do |model|
15
+ @table = find_table_for_model(model)
16
+ columns = columns(table[:columns])
17
+ columns = insert_primary_key(model, columns)
18
+ model[:columns] = columns
19
+ end
20
+ end
21
+
22
+ def column_data(name)
23
+ @column_name = name
24
+ @column_symbol = name.to_sym
25
+ {
26
+ name: name,
27
+ name_plural: name.pluralize,
28
+ type: nil,
29
+ precision: nil,
30
+ scale: nil,
31
+ default: nil,
32
+ null: nil,
33
+ limit: nil,
34
+ array: nil
35
+ }
36
+ end
37
+
38
+ def columns(db_columns)
39
+ db_columns.map do |db_column|
40
+ column = column_data(db_column[:name]).merge(
41
+ type: check_type(db_column[:type]),
42
+ precision: db_column[:precision],
43
+ scale: db_column[:scale],
44
+ default: db_column[:default],
45
+ null: db_column[:null],
46
+ limit: db_column[:limit],
47
+ array: db_column[:array]
48
+ )
49
+
50
+ expand_column(column)
51
+ end
52
+ end
53
+
54
+ def insert_primary_key(model, columns)
55
+ return columns unless model[:pk][:exist]
56
+
57
+ column = column_data('id').merge(
58
+ type: check_type(model[:pk][:type])
59
+ )
60
+
61
+ columns.unshift(expand_column(column))
62
+ columns
63
+ end
64
+
65
+ def expand_column(column)
66
+ foreign_table = lookup_foreign_table(column_name)
67
+ is_foreign = !foreign_table.nil?
68
+ # is_foreign = foreign_key?(column_name)
69
+ structure_type = structure_type(is_foreign)
70
+
71
+ column.merge({
72
+ structure_type: structure_type,
73
+ foreign_key: is_foreign,
74
+ foreign_table: (foreign_table || '').singularize,
75
+ foreign_table_plural: (foreign_table || '').pluralize
76
+ })
77
+ end
78
+
79
+ def check_type(type)
80
+ type = type.to_sym if type.is_a?(String)
81
+
82
+ return type if %i[string integer bigint bigserial boolean float decimal datetime date hstore text jsonb].include?(type)
83
+
84
+ if type.nil?
85
+ guard('nil type detected for db_column[:type]')
86
+
87
+ return :string
88
+ end
89
+
90
+ guard("new type detected for db_column[:type] - #{type}")
91
+
92
+ camel.parse(type.to_s).downcase
93
+ end
94
+
95
+ def lookup_foreign_table(column_name)
96
+ foreign_table = find_foreign_table(table[:name], column_name)
97
+
98
+ return foreign_table if foreign_table
99
+
100
+ cn = column_name.to_s
101
+
102
+ if cn.ends_with?('_id')
103
+ table_name = column_name[0..-4]
104
+ table_name_plural = table_name.pluralize
105
+
106
+ if table_name_exist?(table_name_plural.to_s)
107
+ investigate(step: :step3_attach_columns,
108
+ location: :lookup_foreign_table,
109
+ key: column_name,
110
+ message: "#{@table[:name]}.#{column_name} => #{table_name_plural} - Relationship not found in DB, so have inferred this relationship. You may want to check that this relation is correct")
111
+
112
+ return table_name
113
+ end
114
+
115
+ investigate(step: :step3_attach_columns,
116
+ location: :lookup_foreign_table,
117
+ key: column_name,
118
+ message: "#{@table[:name]}.#{column_name} => #{table_name_plural} - Table not found for a column that looks like foreign_key")
119
+ end
120
+
121
+ nil
122
+ end
123
+
124
+ # Need some configurable data dictionary where by
125
+ # _token can be setup on a project by project basis
126
+ def structure_type(is_foreign)
127
+ return :foreign_key if is_foreign
128
+ return :primary_key if column_symbol == :id
129
+ return :timestamp if column_symbol == :created_at || column_symbol == :updated_at
130
+ return :timestamp if column_symbol == :created_at || column_symbol == :updated_at
131
+ return :deleted_at if column_symbol == :deleted_at
132
+ return :encrypted_password if column_symbol == :encrypted_password
133
+ return :token if column_name.ends_with?('_token') || column_name.ends_with?('_token_iv')
134
+
135
+ :data
136
+ end
137
+ end
@@ -0,0 +1,454 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Attach source code found in rails model definitions to models
4
+ class Step4AttachErdFiles < KDomain::DomainModel::Step
5
+ attr_accessor :ruby_code
6
+
7
+ # NOTE: This code could be rewritten using monkey patched modules and peak
8
+ def call
9
+ domain[:erd_files] = domain_models.map { |model| load_dsl(model) }
10
+ end
11
+
12
+ private
13
+
14
+ def reset_dsl
15
+ @ruby_code = nil
16
+ @dsl = nil
17
+ end
18
+
19
+ def dsl
20
+ @dsl ||= {
21
+ name: '',
22
+ name_plural: ''
23
+ }
24
+ end
25
+
26
+ def load_dsl(model)
27
+ reset_dsl
28
+
29
+ dsl[:name] = model[:name]
30
+ dsl[:name_plural] = model[:name_plural]
31
+ dsl[:dsl_file] = model[:erd_location][:exist] ? model[:erd_location][:file] : ''
32
+
33
+ return dsl unless File.exist?(dsl[:dsl_file])
34
+
35
+ @ruby_code = File.read(dsl[:dsl_file])
36
+
37
+ dsl[:source] = read_dsl_source
38
+ dsl[:dsl] = build_dsl
39
+ dsl[:todo] = todo
40
+
41
+ dsl
42
+ end
43
+
44
+ def read_dsl_source
45
+ regex_split_private_public = /(?<public>.+?)(?=\bprivate\b)(?<private>.*)/m
46
+
47
+ split_code = regex_split_private_public.match(ruby_code)
48
+
49
+ public_code = nil
50
+ private_code = nil
51
+
52
+ if split_code
53
+ public_code = split_code[:public]
54
+ private_code = split_code[:private]
55
+ end
56
+
57
+ {
58
+ ruby: ruby_code,
59
+ public: public_code,
60
+ private: private_code,
61
+ methods: grab_methods(public_code, private_code)
62
+ }
63
+ end
64
+
65
+ def build_dsl
66
+ return if ruby_code.nil?
67
+
68
+ # need to support options as hash instead of options as string in the future
69
+ {
70
+ default_scope: grab_default_scope,
71
+ scopes: grab_scopes,
72
+ belongs_to: grab_belongs_to,
73
+ has_one: grab_has_one,
74
+ has_many: grab_has_many,
75
+ has_and_belongs_to_many: grab_has_and_belongs_to_many,
76
+ validate_on: grab_validate,
77
+ validates_on: grab_validates
78
+ }
79
+
80
+ # ^(?<spaces>\s*)(?<event_type>before_create|before_save|before_destroy|after_create|after_save|after_destroy) (:(?<name>\w*)[, ]?(?<scope>.*)|(?<scope>\{.*?\}.*$))
81
+ end
82
+
83
+ def grab_default_scope
84
+ regex = /default_scope \{(?<scope>.*?)\}/m
85
+
86
+ m = regex.match(ruby_code)
87
+
88
+ return "{ #{m[:scope].strip.gsub('\n', '')} }" if m
89
+
90
+ nil
91
+ end
92
+
93
+ def grab_scopes
94
+ entries = []
95
+ # Start from beginning of line and capture
96
+ # - number of spaces scope
97
+ # - name of scope
98
+ # - value of scope to end of line
99
+ regex = /^(?<spaces>\s*)scope :(?<name>\w*)[, ]?(?<scope>.*)/
100
+
101
+ # rubocop:disable Metrics/BlockLength
102
+ ruby_code.scan(regex) do
103
+ m = $LAST_MATCH_INFO
104
+ spaces = m[:spaces] # .delete("\n")
105
+ last_lf = spaces.rindex("\n")
106
+ spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
107
+ name = m[:name]
108
+ scope = m[:scope].strip
109
+
110
+ # Found a valid one liner
111
+ if scope.ends_with?('}') && (scope.scan(/{/).count == scope.scan(/}/).count)
112
+ scope = escape_single_quote(scope)
113
+ entries << { name: name, scope: scope }
114
+ else
115
+ # Have a multiline scope, lets see if it is cleanly formatted
116
+
117
+ start_anchor = "#{spaces}scope :#{name}"
118
+ end_anchor = "#{spaces}}"
119
+
120
+ # log.kv 'spaces', spaces.length
121
+ # log.kv 'name', name
122
+ # log.kv 'start_anchor', start_anchor
123
+ # log.kv 'end_anchor', end_anchor
124
+
125
+ start_index = ruby_code.index(/#{start_anchor}/)
126
+
127
+ if start_index.nil?
128
+ log.error("[#{@current_entity[:name]}] could not find [start] anchor index for [#{name}]")
129
+ else
130
+ ruby_section = ruby_code[start_index..-1]
131
+ end_index = ruby_section.index(/^#{end_anchor}/) # Add ^ start of line
132
+ if end_index.nil?
133
+ log.error("[#{@current_entity[:name]}] could not find [end] anchor index for [#{name}]")
134
+ else
135
+ scope = ruby_section[start_anchor.length + 1..end_index].strip
136
+ scope = escape_single_quote("#{scope}#{end_anchor}")
137
+ entries << { name: name, scope: scope }
138
+ end
139
+ end
140
+ end
141
+ end
142
+ entries
143
+ rescue StandardError => e
144
+ # bin ding.pry
145
+ puts e.message
146
+ end
147
+ # rubocop:enable Metrics/BlockLength
148
+
149
+ def grab_belongs_to
150
+ entries = []
151
+
152
+ # Start from beginning of line and capture
153
+ # - number of spaces before belongs_to
154
+ # - name of the belongs_to
155
+ # - value of belongs_to to end of line
156
+ regex = /^(?<spaces>\s*)belongs_to :(?<name>\w*)[, ]?(?<options>.*)/
157
+
158
+ ruby_code.scan(regex) do
159
+ m = $LAST_MATCH_INFO
160
+
161
+ # spaces = m[:spaces] # .delete("\n")
162
+ # last_lf = spaces.rindex("\n")
163
+ # spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
164
+ name = m[:name]
165
+
166
+ options = m[:options]
167
+ .gsub(':polymorphic => ', 'polymorphic: ')
168
+ .gsub(':class_name => ', 'class_name: ')
169
+ .gsub(':foreign_key => ', 'foreign_key: ')
170
+ .strip
171
+
172
+ options = clean_lambda(options)
173
+
174
+ entries << { name: name, options: extract_options(options), raw_options: options }
175
+ end
176
+ entries
177
+ rescue StandardError => e
178
+ # bin ding.pry
179
+ puts e.message
180
+ end
181
+
182
+ def grab_has_one
183
+ entries = []
184
+
185
+ # Start from beginning of line and capture
186
+ # - number of spaces before has_one
187
+ # - name of the has_one
188
+ # - value of has_one to end of line
189
+ regex = /^(?<spaces>\s*)has_one :(?<name>\w*)[, ]?(?<options>.*)/
190
+
191
+ ruby_code.scan(regex) do
192
+ m = $LAST_MATCH_INFO
193
+
194
+ # spaces = m[:spaces] # .delete("\n")
195
+ # last_lf = spaces.rindex("\n")
196
+ # spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
197
+ name = m[:name]
198
+ options = m[:options]
199
+ .strip
200
+ # .gsub(':polymorphic => ', 'polymorphic: ')
201
+ # .gsub(':class_name => ', 'class_name: ')
202
+ # .gsub(':foreign_key => ', 'foreign_key: ')
203
+
204
+ options = clean_lambda(options)
205
+
206
+ entries << { name: name, options: extract_options(options), raw_options: options }
207
+ end
208
+ entries
209
+ rescue StandardError => e
210
+ # bin ding.pry
211
+ puts e.message
212
+ end
213
+
214
+ def grab_has_many
215
+ entries = []
216
+ # Start from beginning of line and capture
217
+ # - number of spaces before has_many
218
+ # - name of the has_many
219
+ # - value of has_many to end of line
220
+ regex = /^(?<spaces>\s*)has_many :(?<name>\w*)[, ]?(?<options>.*)/
221
+
222
+ ruby_code.scan(regex) do
223
+ m = $LAST_MATCH_INFO
224
+
225
+ # spaces = m[:spaces] # .delete("\n")
226
+ # last_lf = spaces.rindex("\n")
227
+ # spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
228
+ name = m[:name]
229
+ options = m[:options]
230
+ .gsub(':dependent => ', 'dependent: ')
231
+ .gsub(':class_name => ', 'class_name: ')
232
+ .gsub(':foreign_key => ', 'foreign_key: ')
233
+ .gsub(':primary_key => ', 'primary_key: ')
234
+ .strip
235
+
236
+ options = clean_lambda(options)
237
+
238
+ entries << { name: name, options: extract_options(options), raw_options: options }
239
+ end
240
+ entries
241
+ rescue StandardError => e
242
+ # bin ding.pry
243
+ puts e.message
244
+ end
245
+
246
+ def grab_has_and_belongs_to_many
247
+ entries = []
248
+ # Start from beginning of line and capture
249
+ # - number of spaces before has_and_belongs_to_many
250
+ # - name of the has_and_belongs_to_many
251
+ # - value of has_and_belongs_to_many to end of line
252
+ regex = /^(?<spaces>\s*)has_and_belongs_to_many :(?<name>\w*)[, ]?(?<options>.*)/
253
+
254
+ ruby_code.scan(regex) do
255
+ m = $LAST_MATCH_INFO
256
+
257
+ # spaces = m[:spaces] # .delete("\n")
258
+ # last_lf = spaces.rindex("\n")
259
+ # spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
260
+ name = m[:name]
261
+ options = m[:options]
262
+ .gsub(':dependent => ', 'dependent: ')
263
+ .gsub(':class_name => ', 'class_name: ')
264
+ .gsub(':foreign_key => ', 'foreign_key: ')
265
+ .gsub(':primary_key => ', 'primary_key: ')
266
+ .strip
267
+
268
+ options = clean_lambda(options)
269
+
270
+ entries << { name: name, options: {}, raw_options: options }
271
+ end
272
+ entries
273
+ rescue StandardError => e
274
+ # bin ding.pry
275
+ puts e.message
276
+ end
277
+
278
+ def grab_validates
279
+ entries = []
280
+ # Start from beginning of line and capture
281
+ # - number of spaces before validates
282
+ # - name of the validates
283
+ # - value of validates to end of line
284
+ regex = /^(?<spaces>\s*)validates :(?<name>\w*)[, ]?(?<options>.*)/
285
+
286
+ ruby_code.scan(regex) do
287
+ m = $LAST_MATCH_INFO
288
+
289
+ # spaces = m[:spaces] # .delete("\n")
290
+ # last_lf = spaces.rindex("\n")
291
+ # spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
292
+ name = m[:name]
293
+
294
+ options = m[:options].strip
295
+
296
+ options = clean_lambda(options)
297
+
298
+ entries << { name: name, raw_options: options }
299
+ end
300
+ entries
301
+ rescue StandardError => e
302
+ # bin ding.pry
303
+ puts e.message
304
+ end
305
+
306
+ def grab_validate
307
+ entries = []
308
+ # Start from beginning of line and capture
309
+ # - number of spaces before validate
310
+ # - list of methods to call until to end of line
311
+ # regex = /^(?<spaces>\s*)validate :(?<name>\w*)[, ]?(?<options>.*)/
312
+ regex = /^(?<spaces>\s*)validate (?<line>:.*)/
313
+ # puts @current_entity[:name]
314
+
315
+ ruby_code.scan(regex) do
316
+ m = $LAST_MATCH_INFO
317
+
318
+ # spaces = m[:spaces] # .delete("\n")
319
+ # last_lf = spaces.rindex("\n")
320
+ # spaces = last_lf ? spaces[spaces.rindex("\n") + 1..-1] : spaces
321
+ line = m[:line]
322
+
323
+ entries << { line: line }
324
+ # puts @current_entity[:validate]
325
+ end
326
+ entries
327
+ rescue StandardError => e
328
+ # bin ding.pry
329
+ puts e.message
330
+ end
331
+
332
+ def grab_methods(public_code = ruby_code, private_code = nil)
333
+ # public_code = ruby_code_public.nil? ? ruby_code : ruby_code_public
334
+ # private_code = ruby_code_private
335
+
336
+ regex = /def (?<method>.*)/
337
+
338
+ # log.info(@current_entity[:name])
339
+
340
+ public_methods = parse_methods(:public, public_code&.scan(regex)&.flatten || [])
341
+ private_methods = parse_methods(:private, private_code&.scan(regex)&.flatten || [])
342
+ methods = (public_methods + private_methods)
343
+
344
+ class_methods = methods.select { |method| method[:class_method] == true }
345
+
346
+ all_instance = methods.select { |method| method[:class_method] == false }
347
+ instance_public = all_instance.select { |method| method[:scope] == :public }
348
+ instance_private = all_instance.select { |method| method[:scope] == :private }
349
+
350
+ {
351
+ class: class_methods,
352
+ instance: all_instance,
353
+ instance_public: instance_public,
354
+ instance_private: instance_private
355
+ }
356
+ end
357
+
358
+ def parse_methods(scope, methods)
359
+ methods.map do |value|
360
+ class_method = value.starts_with?('self.')
361
+ name = class_method ? value[5..-1] : value
362
+ arguments = nil
363
+ arguments_index = name.index('(')
364
+
365
+ if arguments_index
366
+ arguments = name[arguments_index..-1]
367
+ name = name[0..arguments_index - 1]
368
+ end
369
+
370
+ arguments = escape_single_quote(arguments)
371
+
372
+ {
373
+ name: name,
374
+ scope: scope,
375
+ class_method: class_method,
376
+ arguments: arguments&.strip.to_s
377
+ }
378
+ end
379
+ end
380
+
381
+ def todo
382
+ {
383
+ after_destroy: [], # to do
384
+ before_save: [], # to do
385
+ after_save: [], # to do
386
+ before_create: [], # to do
387
+ after_create: [], # to do
388
+ enum: [], # to do
389
+ attr_encrypted: [], # to do
390
+ validates_uniqueness_of: [], # to do
391
+ validates_confirmation_of: [], # to do
392
+ attr_accessor: [], # to do
393
+ attr_reader: [], # to do
394
+ attr_writer: [] # to do
395
+ }
396
+ end
397
+
398
+ def escape_single_quote(value)
399
+ return nil if value.nil?
400
+
401
+ value.gsub("'", "\\\\'")
402
+ end
403
+
404
+ # rubocop:disable Style/EvalWithLocation, Security/Eval, Style/DocumentDynamicEvalDefinition
405
+ def extract_options(options)
406
+ eval("{ #{options} }")
407
+ rescue StandardError => e
408
+ investigate(
409
+ step: :step4_attach_erd_files_models,
410
+ location: :extract_options,
411
+ key: nil,
412
+ message: e.message
413
+ )
414
+ {}
415
+ rescue SyntaxError => e
416
+ # may be the issue is from a comment at the off the line
417
+ comment_index = options.rindex('#') - 1
418
+
419
+ if comment_index.positive?
420
+ options_minus_comment = options[0..comment_index].squish
421
+ return extract_options(options_minus_comment)
422
+ end
423
+
424
+ investigate(
425
+ step: :step4_attach_erd_files_models,
426
+ location: :extract_options,
427
+ key: nil,
428
+ message: e.message
429
+ )
430
+ {}
431
+ end
432
+ # rubocop:enable Style/EvalWithLocation, Security/Eval, Style/DocumentDynamicEvalDefinition
433
+
434
+ def clean_lambda(options)
435
+ if /^->/.match?(options)
436
+ index = options.index(/}\s*,/)
437
+ if index.nil?
438
+ if options.count('{') == options.count('}')
439
+ index = options.rindex(/}/)
440
+ options = "a_lambda: '#{escape_single_quote(options[0..index])}'"
441
+ else
442
+ log.error(options)
443
+ options = "a_lambda: '#{escape_single_quote(options)}'"
444
+ end
445
+ else
446
+ options = "a_lambda: '#{escape_single_quote(options[0..index])}', #{options[index + 2..-1]}"
447
+ end
448
+ end
449
+ options
450
+ rescue StandardError => e
451
+ # bin ding.pry
452
+ puts e.message
453
+ end
454
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Attach data dictionary
4
+ class Step5AttachDictionary < KDomain::DomainModel::Step
5
+ attr_reader :dictionary
6
+
7
+ def call
8
+ @dictionary = {}
9
+
10
+ domain_models.each do |model|
11
+ model[:columns].each do |column|
12
+ process(model[:name], column[:name], column[:type])
13
+ end
14
+ end
15
+
16
+ domain[:dictionary] = dictionary.values
17
+ end
18
+
19
+ private
20
+
21
+ def process(model_name, column_name, column_type)
22
+ if dictionary.key?(column_name)
23
+ entry = dictionary[column_name]
24
+ entry[:models] << model_name
25
+ entry[:model_count] = entry[:model_count] + 1
26
+
27
+ unless entry[:types].include?(column_type)
28
+ log.warn("#{model_name} has a type mismatch for column name: #{column_name}")
29
+ entry[:types] << column_type
30
+ entry[:type_count] = entry[:type_count] + 1
31
+ end
32
+ return
33
+ end
34
+
35
+ dictionary[column_name] = {
36
+ name: column_name,
37
+ type: column_type,
38
+ label: column_name.to_s.titleize,
39
+ segment: segment(column_name, column_type),
40
+ models: [model_name],
41
+ model_count: 1,
42
+ types: [column_type],
43
+ type_count: 1
44
+ }
45
+ rescue StandardError => e
46
+ log.error e.message
47
+ end
48
+
49
+ def segment(column_name, column_type)
50
+ n = column_name.to_s
51
+ return column_type == :integer ? :id : :id_variant if n.ends_with?('_id')
52
+ return column_type == :datetime ? :stamp : :stamp_variant if n == 'created_at'
53
+
54
+ :data
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Types
4
+ include Dry.Types()
5
+ end
6
+
7
+ # The require order is important due to dependencies
8
+ require_relative './column'
9
+ require_relative './database'
10
+ require_relative './index'
11
+ require_relative './table'
12
+ require_relative './foreign_key'
13
+ require_relative './unique_key'
14
+ require_relative './schema'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module RawDbSchema
5
+ class Column < Dry::Struct
6
+ attribute :name , Types::Strict::String
7
+ attribute :type , Types::Strict::String
8
+ attribute :precision? , Types::Strict::Integer.optional.default(nil)
9
+ attribute :scale? , Types::Strict::Integer.optional.default(nil)
10
+ attribute :default? , Types::Nominal::Any.optional.default(nil) # Types::Strict::Bool.optional.default(nil) | Types::Strict::Integer.optional.default(nil)
11
+ attribute :array? , Types::Strict::Bool.optional.default(nil)
12
+ attribute :null? , Types::Strict::Bool.optional.default(nil)
13
+ attribute :limit? , Types::Strict::Integer.optional.default(nil)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module RawDbSchema
5
+ class Database < Dry::Struct
6
+ attribute :type , Types::Strict::String
7
+ attribute :version , Types::Nominal::Any.optional.default(nil)
8
+ attribute :extensions , Types::Strict::Array
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module RawDbSchema
5
+ class ForeignKey < Dry::Struct
6
+ attribute :left , Types::Strict::String
7
+ attribute :right , Types::Strict::String
8
+ attribute :name? , Types::Strict::String.optional.default(nil)
9
+ attribute :on_update? , Types::Strict::String.optional.default(nil)
10
+ attribute :on_delete? , Types::Strict::String.optional.default(nil)
11
+ attribute :column? , Types::Strict::String.optional.default(nil)
12
+ end
13
+ end
14
+ end