k_domain 0.0.2 → 0.0.5

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.
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