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