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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module DomainModel
5
+ # Rails model represents information that is found the model.rb class in the rails project
6
+ class RailsModel
7
+ attr_accessor :name
8
+ attr_accessor :name_plural
9
+ attr_accessor :name_original
10
+ attr_accessor :documentation_rel_path
11
+ attr_accessor :model_path
12
+
13
+ # @param [Symbol] value The value of ID has different meanings
14
+ # @option value :true Id column exists and it uses an Integer type
15
+ # @option value :false Id column does not exist
16
+ # @option value :bigserial Id column exists and it uses a BigSerial type
17
+ attr_accessor :id
18
+
19
+ attr_accessor :force
20
+ attr_accessor :primary_key
21
+ attr_accessor :quirks
22
+
23
+ attr_accessor :ruby_raw
24
+ attr_accessor :ruby_code
25
+ attr_accessor :ruby_frozen
26
+ attr_accessor :ruby_header
27
+ attr_accessor :ruby_code_public
28
+ attr_accessor :ruby_code_private
29
+
30
+ attr_accessor :default_scope
31
+ attr_accessor :scopes
32
+ attr_accessor :public_class_methods
33
+ attr_accessor :public_instance_methods
34
+ attr_accessor :private_instance_methods
35
+
36
+ # stats
37
+ attr_accessor :time_stamp1
38
+ attr_accessor :time_stamp2
39
+ attr_accessor :time_stamp3
40
+
41
+ def code_length
42
+ ruby_raw&.length
43
+ end
44
+
45
+ def display_quirks
46
+ quirks.join(' ')
47
+ end
48
+
49
+ def exists?
50
+ File.exist?(model_path)
51
+ end
52
+
53
+ def initialize
54
+ @quirks = []
55
+ end
56
+
57
+ def add_quirk(quirk)
58
+ @quirks << quirk
59
+ end
60
+
61
+ def to_h
62
+ {
63
+ name: name,
64
+ name_plural: name_plural,
65
+ name_original: name_original,
66
+ documentation_rel_path: documentation_rel_path,
67
+ model_path: model_path,
68
+ id: id,
69
+ force: force,
70
+ primary_key: primary_key,
71
+ quirks: quirks,
72
+ ruby_raw: ruby_raw,
73
+ ruby_code: ruby_code,
74
+ ruby_frozen: ruby_frozen,
75
+ ruby_header: ruby_header,
76
+ ruby_code_public: ruby_code_public,
77
+ ruby_code_private: ruby_code_private,
78
+ default_scope: default_scope,
79
+ scopes: scopes,
80
+ public_class_methods: public_class_methods,
81
+ public_instance_methods: public_instance_methods,
82
+ private_instance_methods: private_instance_methods,
83
+ time_stamp1: time_stamp1,
84
+ time_stamp2: time_stamp2,
85
+ time_stamp3: time_stamp3,
86
+ code_length: code_length,
87
+ exists: exists?
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module DomainModel
5
+ class RelatedEntity
6
+ # Name of the entity model
7
+ attr_accessor :name
8
+ attr_accessor :name_plural
9
+ attr_accessor :main_key
10
+
11
+ attr_accessor :trait1
12
+ attr_accessor :trait2
13
+ attr_accessor :trait3
14
+
15
+ def initialize(entity)
16
+ @name = entity.name
17
+ @name_plural = entity.name_plural
18
+ @main_key = entity.main_key
19
+ @trait1 = entity.trait1
20
+ @trait2 = entity.trait2
21
+ @trait3 = entity.trait3
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ name: name,
27
+ name_plural: name_plural,
28
+ main_key: main_key,
29
+ trait1: trait1,
30
+ trait2: trait2,
31
+ trait3: trait3
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # DomainModel holds the entire domain model including database and ancillary information
4
+ module KDomain
5
+ module DomainModel
6
+ class Schema < Dry::Struct
7
+ attribute :domain , KDomain::DomainModel::Domain
8
+ attribute :database , KDomain::RawDbSchema::Schema
9
+ attribute :investigate , KDomain::DomainModel::Investigate
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ # module KDomain
3
+ # module DomainModel
4
+ # # Rails model represents information that is found the model.rb class in the rails project
5
+ # class Statistics
6
+ # attr_accessor :column_counts
7
+ # attr_accessor :code_counts
8
+ # attr_accessor :code_dsl_counts
9
+ # attr_accessor :data_counts
10
+ # attr_accessor :issues
11
+
12
+ # def initialize(meta)
13
+ # @column_counts = OpenStruct.new(meta[:column_counts])
14
+ # @code_counts = OpenStruct.new(meta[:code_counts])
15
+ # @code_dsl_counts = OpenStruct.new(meta[:code_dsl_counts])
16
+ # @data_counts = OpenStruct.new(meta[:data_counts])
17
+ # @issues = meta[:issues]
18
+ # end
19
+ # end
20
+ # end
21
+ # end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module DomainModel
5
+ class Validate
6
+ KEYS = [:on].freeze
7
+
8
+ attr_accessor :methods
9
+
10
+ attr_accessor :on
11
+
12
+ def format_on
13
+ for_template(on)
14
+ end
15
+
16
+ def for_template(value)
17
+ return nil if value.nil?
18
+ return value.to_s if value.is_a?(Hash)
19
+ return ":#{value}" if value.is_a?(Symbol)
20
+
21
+ value
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module DomainModel
5
+ class Validates
6
+ KEYS = %i[length unless format presence].freeze
7
+
8
+ attr_accessor :name
9
+
10
+ attr_accessor :length
11
+ attr_accessor :unless
12
+ attr_accessor :format
13
+ attr_accessor :presence
14
+
15
+ def format_length
16
+ for_template(length)
17
+ end
18
+
19
+ def format_unless
20
+ for_template(self.unless)
21
+ end
22
+
23
+ def format_format
24
+ for_template(self.format)
25
+ end
26
+
27
+ def format_presence
28
+ for_template(presence)
29
+ end
30
+
31
+ def for_template(value)
32
+ return nil if value.nil?
33
+ return value.to_s if value.is_a?(Hash)
34
+ return ":#{value}" if value.is_a?(Symbol)
35
+
36
+ value
37
+ end
38
+
39
+ def to_h
40
+ {
41
+ name: name,
42
+ length: length,
43
+ unless: self.unless,
44
+ format: self.format,
45
+ presence: presence
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Annotates the original schema with methods that implement existing method calls
4
+ # that are already in the schema so that we can build a hash.
5
+ #
6
+ # Writes a new annotated schema.rb file with a public method called load that
7
+ # builds the hash
8
+
9
+ module KDomain
10
+ module DomainModel
11
+ class Load
12
+ include KLog::Logging
13
+
14
+ attr_reader :source_file
15
+ attr_reader :data
16
+
17
+ def initialize(source_file)
18
+ @source_file = source_file
19
+ end
20
+
21
+ def call
22
+ json = File.read(source_file)
23
+ data = KUtil.data.json_parse(json, as: :hash_symbolized)
24
+
25
+ @data = KDomain::DomainModel::Schema.new(data)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Loads the db schema object and works through a series of enrichment steps to
4
+ # that builds the domain modal
5
+
6
+ module KDomain
7
+ module DomainModel
8
+ class Transform
9
+ include KLog::Logging
10
+
11
+ attr_reader :db_schema
12
+ attr_reader :target_step_file
13
+ attr_reader :target_file
14
+ attr_reader :erd_path
15
+
16
+ def initialize(db_schema, target_file, target_step_file, erd_path)
17
+ @db_schema = db_schema
18
+ @target_step_file = target_step_file
19
+ @target_file = target_file
20
+ @erd_path = erd_path
21
+ end
22
+
23
+ def call
24
+ valid = true
25
+ valid &&= step1
26
+ valid &&= step2
27
+ valid &&= step3
28
+ valid &&= step4
29
+ valid &&= step5
30
+
31
+ raise 'DomainModal transform failed' unless valid
32
+
33
+ write
34
+
35
+ nil
36
+ end
37
+
38
+ def step1
39
+ Step1AttachDbSchema.run(domain_data, db_schema: db_schema)
40
+ write(step: '1-attach-db-schema')
41
+ end
42
+
43
+ def step2
44
+ Step2AttachModels.run(domain_data, erd_path: erd_path)
45
+ write(step: '2-attach-model')
46
+ end
47
+
48
+ def step3
49
+ Step3AttachColumns.run(domain_data)
50
+ write(step: '3-attach-columns')
51
+ end
52
+
53
+ def step4
54
+ Step4AttachErdFiles.run(domain_data, erd_path: erd_path)
55
+ write(step: '4-attach-erd-files')
56
+ end
57
+
58
+ def step5
59
+ Step5AttachDictionary.run(domain_data, erd_path: erd_path)
60
+ write(step: '5-attach-dictionary')
61
+ end
62
+
63
+ def write(step: nil)
64
+ file = if step.nil?
65
+ target_file
66
+ else
67
+ format(target_step_file, step: step)
68
+ end
69
+ FileUtils.mkdir_p(File.dirname(file))
70
+ File.write(file, JSON.pretty_generate(domain_data))
71
+ end
72
+
73
+ def domain_data
74
+ # The initial domain model structure is created here, but populated during the workflows.
75
+ @domain_data ||= {
76
+ domain: {
77
+ models: [],
78
+ erd_files: [],
79
+ dictionary: []
80
+ },
81
+ database: {
82
+ tables: [],
83
+ indexes: [],
84
+ foreign_keys: [],
85
+ meta: {}
86
+ },
87
+ investigate: {
88
+ issues: []
89
+ }
90
+ }
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The require order is important due to dependencies
4
+ require_relative './step'
5
+ require_relative './step1_attach_db_schema'
6
+ require_relative './step2_attach_models'
7
+ require_relative './step3_attach_columns'
8
+ require_relative './step4_attach_erd_files'
9
+ require_relative './step5_attach_dictionary'
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module DomainModel
5
+ class Step
6
+ include KLog::Logging
7
+
8
+ attr_reader :domain_data
9
+ attr_reader :opts
10
+ attr_reader :valid
11
+ alias valid? valid
12
+
13
+ def initialize(domain_data, **opts)
14
+ # Useful for debugging
15
+ # log.info "Initialize #{self.class.name}"
16
+
17
+ @domain_data = domain_data
18
+ @opts = opts
19
+ @valid = true
20
+ end
21
+
22
+ def call; end
23
+
24
+ def self.run(domain_data, **opts)
25
+ step = new(domain_data, **opts)
26
+ step.call
27
+ step
28
+ end
29
+
30
+ def guard(message)
31
+ log.error message
32
+ @valid = false
33
+ end
34
+
35
+ # Domain Model Accessor/Helpers
36
+ def domain
37
+ guard('domain is missing') if domain_data[:domain].nil?
38
+
39
+ domain_data[:domain]
40
+ end
41
+
42
+ def domain_models
43
+ domain[:models]
44
+ end
45
+
46
+ # Database Accessor/Helpers
47
+ def database=(value)
48
+ domain_data[:database] = value
49
+ end
50
+
51
+ def database
52
+ guard('database is missing') if domain_data[:database].nil?
53
+
54
+ domain_data[:database]
55
+ end
56
+
57
+ def database_tables
58
+ guard('database_tables is missing') if database[:tables].nil?
59
+
60
+ database[:tables]
61
+ end
62
+
63
+ def database_foreign_keys
64
+ guard('database_foreign_keys is missing') if database[:foreign_keys].nil?
65
+
66
+ database[:foreign_keys]
67
+ end
68
+
69
+ def find_table_for_model(model)
70
+ database_tables.find { |table| table[:name] == model[:table_name] }
71
+ end
72
+
73
+ def table_name_exist?(table_name)
74
+ if table_name.nil?
75
+ guard('table_name_exist? was provided with a table_name: nil')
76
+ return false
77
+ end
78
+ database_table_name_hash.key?(table_name)
79
+ end
80
+
81
+ def find_foreign_table(lhs_table_name, column_name)
82
+ fk = database_foreign_keys.find { |foreign_key| foreign_key[:left] == lhs_table_name && foreign_key[:column] == column_name }
83
+ return fk[:right] if fk
84
+
85
+ nil
86
+ end
87
+
88
+ def investigate(step:, location:, key:, message:)
89
+ unique_key = build_key(step, location, key)
90
+
91
+ return if issue_hash.key?(unique_key)
92
+
93
+ value = { step: step, location: location, key: key, message: message }
94
+
95
+ issues << value # list
96
+ issue_hash[unique_key] = value # lookup
97
+ end
98
+
99
+ def issues
100
+ domain_data[:investigate][:issues]
101
+ end
102
+
103
+ private
104
+
105
+ def database_table_name_hash
106
+ @database_table_name_hash ||= database_tables.to_h { |table| [table[:name], table[:name]] }
107
+ end
108
+
109
+ def build_key(*values)
110
+ values.join('-')
111
+ end
112
+
113
+ def issue_hash
114
+ return @issue_hash if defined? @issue_hash
115
+
116
+ @issue_hash = issues.to_h do |issue|
117
+ unique_key = build_key(issue[:step], issue[:location], issue[:key])
118
+ [unique_key, issue]
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDomain
4
+ module DomainModel
5
+ class Step1AttachDbSchema < KDomain::DomainModel::Step
6
+ # Map database schema to domain model
7
+ def call
8
+ raise 'Schema not supplied' if opts[:db_schema].nil?
9
+
10
+ self.database = opts[:db_schema].clone
11
+
12
+ guard('tables are missing') if database[:tables].nil?
13
+ guard('indexes are missing') if database[:indexes].nil?
14
+ guard('foreign keys are missing') if database[:foreign_keys].nil?
15
+ guard('rails version is missing') if database[:meta][:rails].nil?
16
+ guard('postgres extensions are missing') if database[:meta][:database][:extensions].nil?
17
+ guard('unique keys are missing') if database[:meta][:unique_keys].nil?
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Loop through the db_schema tables and build up a
4
+ # basic model for each table
5
+ class Step2AttachModels < KDomain::DomainModel::Step
6
+ # Map database schema to domain model
7
+ def call
8
+ raise 'ERD path not supplied' if opts[:erd_path].nil?
9
+
10
+ # Schema is re-shaped into a format designed for domain modeling
11
+ domain[:models] = database_tables.map { |table| model(table) }
12
+ end
13
+
14
+ def model(table)
15
+ table_name = table[:name].to_s
16
+ model_name = table_name.singularize
17
+
18
+ {
19
+ name: model_name,
20
+ name_plural: table_name, # need to check if this is correct as I know it is wrong for account_history_datum
21
+ table_name: table_name,
22
+ pk: primary_key(table),
23
+ erd_location: location(table_name, model_name),
24
+ statistics: {}, # Load in future step
25
+ columns: [] # Load in future step
26
+ }
27
+ end
28
+
29
+ def primary_key(table)
30
+ {
31
+ name: table[:primary_key],
32
+ type: table[:primary_key_type],
33
+ exist: !table[:primary_key].nil?
34
+ }
35
+ end
36
+
37
+ # Location of source code
38
+ def location(table_name, model_name)
39
+ file_normal = File.join(opts[:erd_path], "#{model_name}.rb")
40
+ file_custom = File.join(opts[:erd_path], "#{table_name}.rb")
41
+ file_exist = true
42
+ state = []
43
+
44
+ if File.exist?(file_normal)
45
+ file = file_normal
46
+ state.push(:has_ruby_model)
47
+ elsif File.exist?(file_custom)
48
+ file = file_custom
49
+ state.push(:has_ruby_model)
50
+ state.push(:nonconventional_name)
51
+ else
52
+ file = ''
53
+ file_exist = false
54
+ end
55
+
56
+ {
57
+ file: file,
58
+ exist: file_exist,
59
+ state: state # display_state: state.join(' ')
60
+ }
61
+ end
62
+ end