dbdoc_engine 0.1.0

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 (198) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +331 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/builds/dbdoc_engine/application.css +5 -0
  5. data/app/assets/images/dbdoc_engine/arrowdown.svg +3 -0
  6. data/app/assets/images/dbdoc_engine/arrowhorizontal.svg +3 -0
  7. data/app/assets/images/dbdoc_engine/arrowleft.svg +3 -0
  8. data/app/assets/images/dbdoc_engine/changelog.svg +3 -0
  9. data/app/assets/images/dbdoc_engine/column_stats_dbdocs.svg +23 -0
  10. data/app/assets/images/dbdoc_engine/diagram.svg +3 -0
  11. data/app/assets/images/dbdoc_engine/double_arrow.svg +4 -0
  12. data/app/assets/images/dbdoc_engine/group_bu.svg +3 -0
  13. data/app/assets/images/dbdoc_engine/japan_circle.png +0 -0
  14. data/app/assets/images/dbdoc_engine/log_in_image.png +0 -0
  15. data/app/assets/images/dbdoc_engine/logo.svg +12 -0
  16. data/app/assets/images/dbdoc_engine/orange_changelog.svg +3 -0
  17. data/app/assets/images/dbdoc_engine/orange_fields.svg +23 -0
  18. data/app/assets/images/dbdoc_engine/orange_logo.svg +12 -0
  19. data/app/assets/images/dbdoc_engine/orange_table.svg +21 -0
  20. data/app/assets/images/dbdoc_engine/orange_updates.svg +43 -0
  21. data/app/assets/images/dbdoc_engine/orange_wiki.svg +3 -0
  22. data/app/assets/images/dbdoc_engine/search.svg +3 -0
  23. data/app/assets/images/dbdoc_engine/setting.svg +3 -0
  24. data/app/assets/images/dbdoc_engine/table_dbdocs.svg +21 -0
  25. data/app/assets/images/dbdoc_engine/uk_circle_transparent.png +0 -0
  26. data/app/assets/images/dbdoc_engine/update_stats_dbdocs.svg +43 -0
  27. data/app/assets/images/dbdoc_engine/wiki.svg +3 -0
  28. data/app/assets/stylesheets/dbdoc_engine/admin.css +176 -0
  29. data/app/assets/stylesheets/dbdoc_engine/admin_header.css +179 -0
  30. data/app/assets/stylesheets/dbdoc_engine/application.scss +1 -0
  31. data/app/assets/stylesheets/dbdoc_engine/changelog.css +173 -0
  32. data/app/assets/stylesheets/dbdoc_engine/dashboard.css +513 -0
  33. data/app/assets/stylesheets/dbdoc_engine/dbdoc_application.css +117 -0
  34. data/app/assets/stylesheets/dbdoc_engine/ecommerce.css +253 -0
  35. data/app/assets/stylesheets/dbdoc_engine/group_details.css +178 -0
  36. data/app/assets/stylesheets/dbdoc_engine/header.css +212 -0
  37. data/app/assets/stylesheets/dbdoc_engine/loading_spinner.css +127 -0
  38. data/app/assets/stylesheets/dbdoc_engine/login.css +213 -0
  39. data/app/assets/stylesheets/dbdoc_engine/schema_diagram.css +149 -0
  40. data/app/assets/stylesheets/dbdoc_engine/sidebar.css +296 -0
  41. data/app/assets/stylesheets/dbdoc_engine/table_details.css +417 -0
  42. data/app/controllers/dbdoc_engine/admin/base_controller.rb +23 -0
  43. data/app/controllers/dbdoc_engine/admin/dashboard_controller.rb +16 -0
  44. data/app/controllers/dbdoc_engine/admin/data_transfer_controller.rb +63 -0
  45. data/app/controllers/dbdoc_engine/admin/db_design_dynamic_tables_controller.rb +198 -0
  46. data/app/controllers/dbdoc_engine/admin/db_design_table_groups_controller.rb +107 -0
  47. data/app/controllers/dbdoc_engine/application_controller.rb +65 -0
  48. data/app/controllers/dbdoc_engine/concerns/internationalization.rb +57 -0
  49. data/app/controllers/dbdoc_engine/db_doc_sessions_controller.rb +33 -0
  50. data/app/controllers/dbdoc_engine/home_controller.rb +79 -0
  51. data/app/controllers/dbdoc_engine/schema_diagram_controller.rb +293 -0
  52. data/app/helper/dbdoc_engine/application_helper.rb +35 -0
  53. data/app/helpers/dbdoc_engine/application_helper.rb +4 -0
  54. data/app/helpers/dbdoc_engine/changelogs_helper.rb +27 -0
  55. data/app/helpers/dbdoc_engine/column_helper.rb +30 -0
  56. data/app/helpers/dbdoc_engine/db_design_dynamic_tables_helper.rb +15 -0
  57. data/app/helpers/dbdoc_engine/home_helper.rb +75 -0
  58. data/app/javascript/dbdoc_engine/application.js +12 -0
  59. data/app/javascript/dbdoc_engine/controllers/application.js +29 -0
  60. data/app/javascript/dbdoc_engine/controllers/auto_submit_controller.js +17 -0
  61. data/app/javascript/dbdoc_engine/controllers/chart_controller.js +58 -0
  62. data/app/javascript/dbdoc_engine/controllers/column-type_controller.js +149 -0
  63. data/app/javascript/dbdoc_engine/controllers/column_controller.js +362 -0
  64. data/app/javascript/dbdoc_engine/controllers/column_search_controller.js +42 -0
  65. data/app/javascript/dbdoc_engine/controllers/dbdoc_accordion_controller.js +42 -0
  66. data/app/javascript/dbdoc_engine/controllers/ecommerce_controller.js +73 -0
  67. data/app/javascript/dbdoc_engine/controllers/group_details_controller.js +88 -0
  68. data/app/javascript/dbdoc_engine/controllers/import_export_controller.js +200 -0
  69. data/app/javascript/dbdoc_engine/controllers/index.js +9 -0
  70. data/app/javascript/dbdoc_engine/controllers/language_controller.js +100 -0
  71. data/app/javascript/dbdoc_engine/controllers/loading_spinner_controller.js +48 -0
  72. data/app/javascript/dbdoc_engine/controllers/login_controller.js +75 -0
  73. data/app/javascript/dbdoc_engine/controllers/notification_controller.js +15 -0
  74. data/app/javascript/dbdoc_engine/controllers/schema_diagram_controller.js +1129 -0
  75. data/app/javascript/dbdoc_engine/controllers/select2_controller.js +67 -0
  76. data/app/javascript/dbdoc_engine/controllers/sidebar_controller.js +943 -0
  77. data/app/javascript/dbdoc_engine/controllers/table_details_controller.js +245 -0
  78. data/app/javascript/dbdoc_engine/controllers/table_group_validation_controller.js +148 -0
  79. data/app/javascript/dbdoc_engine/controllers/table_validation_controller.js +423 -0
  80. data/app/jobs/dbdoc_engine/application_job.rb +4 -0
  81. data/app/mailers/dbdoc_engine/application_mailer.rb +6 -0
  82. data/app/models/dbdoc_engine/application_record.rb +6 -0
  83. data/app/models/dbdoc_engine/concerns/soft_deletable.rb +30 -0
  84. data/app/models/dbdoc_engine/db_design_changelog.rb +44 -0
  85. data/app/models/dbdoc_engine/db_design_dynamic_column.rb +211 -0
  86. data/app/models/dbdoc_engine/db_design_dynamic_table.rb +124 -0
  87. data/app/models/dbdoc_engine/db_design_table_group.rb +88 -0
  88. data/app/models/dbdoc_engine/user.rb +21 -0
  89. data/app/queries/dbdoc_engine/admin_dashboard_queries.rb +71 -0
  90. data/app/queries/dbdoc_engine/db_design_changelog_queries.rb +68 -0
  91. data/app/queries/dbdoc_engine/db_design_dynamic_column_queries.rb +37 -0
  92. data/app/queries/dbdoc_engine/db_design_dynamic_table_commands.rb +106 -0
  93. data/app/queries/dbdoc_engine/db_design_dynamic_table_queries.rb +194 -0
  94. data/app/queries/dbdoc_engine/db_design_table_group_queries.rb +154 -0
  95. data/app/services/dbdoc_engine/db_design_dynamic_table_export_service.rb +38 -0
  96. data/app/services/dbdoc_engine/db_design_dynamic_table_handler_service.rb +49 -0
  97. data/app/services/dbdoc_engine/db_design_dynamic_tables_service.rb +21 -0
  98. data/app/services/dbdoc_engine/error_handler_service.rb +43 -0
  99. data/app/services/dbdoc_engine/schema_rb_import_service.rb +194 -0
  100. data/app/services/dbdoc_engine/schema_rb_parser_service.rb +339 -0
  101. data/app/services/dbdoc_engine/table_filter_service.rb +35 -0
  102. data/app/services/dbdoc_engine/table_groups_service.rb +199 -0
  103. data/app/services/dbdoc_engine/table_management_service.rb +192 -0
  104. data/app/views/dbdoc_engine/admin/dashboard/_action_badge.html.erb +11 -0
  105. data/app/views/dbdoc_engine/admin/dashboard/_changelog_rows.html.erb +22 -0
  106. data/app/views/dbdoc_engine/admin/dashboard/_changelog_table_headers.html.erb +8 -0
  107. data/app/views/dbdoc_engine/admin/dashboard/_filter_fields.html.erb +43 -0
  108. data/app/views/dbdoc_engine/admin/dashboard/index.html.erb +159 -0
  109. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_column_fields.html.erb +225 -0
  110. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_deleted_table_index.html.erb +110 -0
  111. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_foreign_key_fields.html.erb +51 -0
  112. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_form.html.erb +75 -0
  113. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_recent_activity.html.erb +39 -0
  114. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_columns.html.erb +127 -0
  115. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_index.html.erb +109 -0
  116. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_information.html.erb +99 -0
  117. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/deleted_tables.html.erb +95 -0
  118. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/edit.html.erb +23 -0
  119. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_all_to_excel.xlsx.axlsx +240 -0
  120. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_to_excel.xlsx.axlsx +135 -0
  121. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/index.html.erb +109 -0
  122. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/new.html.erb +25 -0
  123. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/show_table_info.html.erb +125 -0
  124. data/app/views/dbdoc_engine/admin/db_design_table_groups/_deleted_table_groups_list.html.erb +75 -0
  125. data/app/views/dbdoc_engine/admin/db_design_table_groups/_form.html.erb +88 -0
  126. data/app/views/dbdoc_engine/admin/db_design_table_groups/_table_groups_list.html.erb +82 -0
  127. data/app/views/dbdoc_engine/admin/db_design_table_groups/deleted_groups.html.erb +60 -0
  128. data/app/views/dbdoc_engine/admin/db_design_table_groups/edit.html.erb +25 -0
  129. data/app/views/dbdoc_engine/admin/db_design_table_groups/index.html.erb +85 -0
  130. data/app/views/dbdoc_engine/admin/db_design_table_groups/new.html.erb +26 -0
  131. data/app/views/dbdoc_engine/db_doc_sessions/new.html.erb +59 -0
  132. data/app/views/dbdoc_engine/home/changelog_details.html.erb +80 -0
  133. data/app/views/dbdoc_engine/home/changelogs.html.erb +20 -0
  134. data/app/views/dbdoc_engine/home/group_details.html.erb +94 -0
  135. data/app/views/dbdoc_engine/home/index.html.erb +11 -0
  136. data/app/views/dbdoc_engine/home/partials/_action_badge.html.erb +11 -0
  137. data/app/views/dbdoc_engine/home/partials/_breadcrumb_navigation.html.erb +30 -0
  138. data/app/views/dbdoc_engine/home/partials/_changelog_rows.html.erb +35 -0
  139. data/app/views/dbdoc_engine/home/partials/_changelog_table_headers.html.erb +16 -0
  140. data/app/views/dbdoc_engine/home/partials/_column_headers.html.erb +23 -0
  141. data/app/views/dbdoc_engine/home/partials/_column_row.html.erb +157 -0
  142. data/app/views/dbdoc_engine/home/partials/_filter_form.html.erb +47 -0
  143. data/app/views/dbdoc_engine/home/partials/_group_section.html.erb +84 -0
  144. data/app/views/dbdoc_engine/home/partials/_pagination.html.erb +5 -0
  145. data/app/views/dbdoc_engine/home/partials/_stats_container.html.erb +46 -0
  146. data/app/views/dbdoc_engine/home/partials/_table_groups.html.erb +7 -0
  147. data/app/views/dbdoc_engine/home/partials/_table_information_section.html.erb +50 -0
  148. data/app/views/dbdoc_engine/home/partials/_table_section.html.erb +48 -0
  149. data/app/views/dbdoc_engine/home/table_details.html.erb +9 -0
  150. data/app/views/dbdoc_engine/schema_diagram/index.html.erb +102 -0
  151. data/app/views/dbdoc_engine/shared/_admin_header.html.erb +78 -0
  152. data/app/views/dbdoc_engine/shared/_header.html.erb +94 -0
  153. data/app/views/dbdoc_engine/shared/_js_translations.html.erb +3 -0
  154. data/app/views/dbdoc_engine/shared/_language_button.html.erb +14 -0
  155. data/app/views/dbdoc_engine/shared/_sidebar.html.erb +128 -0
  156. data/app/views/kaminari/dbdoc_engine/_first_page.html.erb +3 -0
  157. data/app/views/kaminari/dbdoc_engine/_gap.html.erb +3 -0
  158. data/app/views/kaminari/dbdoc_engine/_last_page.html.erb +3 -0
  159. data/app/views/kaminari/dbdoc_engine/_next_page.html.erb +3 -0
  160. data/app/views/kaminari/dbdoc_engine/_page.html.erb +9 -0
  161. data/app/views/kaminari/dbdoc_engine/_paginator.html.erb +17 -0
  162. data/app/views/kaminari/dbdoc_engine/_prev_page.html.erb +3 -0
  163. data/app/views/layouts/dbdoc_engine/application.html.erb +107 -0
  164. data/app/views/layouts/dbdoc_engine/header.html.erb +108 -0
  165. data/config/importmap.rb +11 -0
  166. data/config/locales/en.yml +307 -0
  167. data/config/locales/ja.yml +306 -0
  168. data/config/routes.rb +73 -0
  169. data/db/migrate/rails7/20250227060610_create_db_design_table_groups.rb +15 -0
  170. data/db/migrate/rails7/20250227094626_create_db_design_dynamic_tables.rb +19 -0
  171. data/db/migrate/rails7/20250228022732_create_db_design_dynamic_columns.rb +34 -0
  172. data/db/migrate/rails7/20250401051453_create_db_design_changelogs.rb +26 -0
  173. data/db/migrate/rails7/20250411040822_create_users.rb +14 -0
  174. data/db/migrate/rails7/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
  175. data/db/migrate/rails8/20250227060610_create_db_design_table_groups.rb +15 -0
  176. data/db/migrate/rails8/20250227094626_create_db_design_dynamic_tables.rb +19 -0
  177. data/db/migrate/rails8/20250228022732_create_db_design_dynamic_columns.rb +34 -0
  178. data/db/migrate/rails8/20250401051453_create_db_design_changelogs.rb +26 -0
  179. data/db/migrate/rails8/20250411040822_create_users.rb +14 -0
  180. data/db/migrate/rails8/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
  181. data/db/seeds.rb +28 -0
  182. data/lib/dbdoc_engine/engine.rb +57 -0
  183. data/lib/dbdoc_engine/version.rb +3 -0
  184. data/lib/dbdoc_engine.rb +9 -0
  185. data/lib/generators/dbdoc_engine/install/install_generator.rb +245 -0
  186. data/lib/generators/dbdoc_engine/uninstall/uninstall_generator.rb +196 -0
  187. data/lib/tasks/dbdoc_engine_tasks.rake +44 -0
  188. data/public/dbdoc_engine_assets/images/camel_chess_head.png +0 -0
  189. data/public/dbdoc_engine_assets/images/dblogo.svg +4 -0
  190. data/public/dbdoc_engine_assets/images/japan_circle.png +0 -0
  191. data/public/dbdoc_engine_assets/images/king_chess_head.png +0 -0
  192. data/public/dbdoc_engine_assets/images/login-bg.svg +44 -0
  193. data/public/dbdoc_engine_assets/images/logo.png +0 -0
  194. data/public/dbdoc_engine_assets/images/logo.svg +12 -0
  195. data/public/dbdoc_engine_assets/images/queen_chess_head.png +0 -0
  196. data/public/dbdoc_engine_assets/images/soldier_chess_headd.png +0 -0
  197. data/public/dbdoc_engine_assets/images/uk_circle_transparent.png +0 -0
  198. metadata +415 -0
@@ -0,0 +1,293 @@
1
+ # app/controllers/dbdoc_engine/schema_diagram_controller.rb
2
+ module DbdocEngine
3
+ class SchemaDiagramController < ApplicationController
4
+ layout "dbdoc_engine/header"
5
+
6
+ # Skip CSRF protection for the data endpoint to allow AJAX calls
7
+ skip_before_action :verify_authenticity_token, only: [ :data ]
8
+
9
+ def index
10
+ @groups = DbdocEngine::DbDesignTableGroupQueries.groups_with_tables_having_columns
11
+
12
+ @foreign_keys = DbdocEngine::DbDesignDynamicColumnQueries.all_foreign_keys_with_tables
13
+ end
14
+
15
+ def data
16
+ @tables = DbDesignDynamicTableQueries.active_tables_with_columns
17
+ .includes(:db_design_dynamic_columns, :db_design_table_group)
18
+
19
+ @foreign_keys = DbdocEngine::DbDesignDynamicColumnQueries.all_foreign_keys_with_tables
20
+ .includes(db_design_dynamic_table: :db_design_dynamic_columns)
21
+ @existing_table_names = DbDesignDynamicTable.where(deleted_at: nil).pluck(:table_name).to_set
22
+
23
+ dot_code = generate_graphviz_dot(@tables)
24
+ svg = render_graphviz_svg(dot_code)
25
+
26
+ selected_group_id = params[:group_id].presence if params[:group_id] != "all"
27
+ @group_colors = {}
28
+
29
+ @tables.each do |table|
30
+ group_color = table.db_design_table_group&.group_color || "#cccccc"
31
+ if selected_group_id.nil? || table.db_design_table_group_id.to_s == selected_group_id
32
+ @group_colors[table.table_name] = group_color
33
+ else
34
+ @group_colors[table.table_name] = "#808080"
35
+ end
36
+ end
37
+
38
+ @column_data = generate_column_data(@tables)
39
+ @relationship_data = generate_relationship_data
40
+
41
+ respond_to do |format|
42
+ format.json do
43
+ render json: {
44
+ diagram_svg: svg,
45
+ group_colors: @group_colors,
46
+ column_data: @column_data,
47
+ relationship_data: @relationship_data
48
+ }
49
+ end
50
+ end
51
+ end
52
+
53
+ def render_graphviz_svg(dot_code)
54
+ require "open3"
55
+ svg = nil
56
+ Open3.popen3("dot -Tsvg") do |stdin, stdout, stderr, wait_thr|
57
+ stdin.puts dot_code
58
+ stdin.close
59
+ svg = stdout.read
60
+ raise "Graphviz error: #{stderr.read}" if wait_thr.value != 0
61
+ end
62
+ svg
63
+ end
64
+
65
+ def lighten_color(hex, amount = 0.7)
66
+ r = hex[1..2].to_i(16)
67
+ g = hex[3..4].to_i(16)
68
+ b = hex[5..6].to_i(16)
69
+ r = [ (r + (255 - r) * amount), 255 ].min.round
70
+ g = [ (g + (255 - g) * amount), 255 ].min.round
71
+ b = [ (b + (255 - b) * amount), 255 ].min.round
72
+ "#%02X%02X%02X" % [ r, g, b ]
73
+ end
74
+
75
+ private
76
+
77
+ def generate_graphviz_dot(tables)
78
+ selected_group_id = params[:group_id].presence if params[:group_id] != "all"
79
+
80
+ dot = <<~DOT
81
+ digraph G {
82
+ graph [overlap=false rankdir=LR ratio=auto splines=true fontname="Arial" fontsize=10 compound=true]
83
+ node [fontname="Arial" fontsize=10 shape=plain margin=0]
84
+ edge [fontname="Arial" fontsize=8]
85
+ DOT
86
+
87
+ table_colors = {}
88
+
89
+ # Generate table nodes
90
+ tables.each do |table|
91
+ group = table.db_design_table_group
92
+ color = if selected_group_id.nil? || table.db_design_table_group_id.to_s == selected_group_id
93
+ group&.group_color || "#cccccc"
94
+ else
95
+ "#808080"
96
+ end
97
+
98
+ table_colors[table.table_name] = color
99
+
100
+ dot << generate_table_node(table, color)
101
+ end
102
+
103
+ # Generate edges with better labeling and grouping
104
+ dot << generate_relationship_edges(tables, table_colors)
105
+
106
+ dot << "}\n"
107
+ dot
108
+ end
109
+
110
+ def generate_table_node(table, color)
111
+ node_content = <<~TABLE
112
+ "#{escape_dot_string(table.table_name)}" [id="#{escape_dot_string(table.table_name)}" label=<
113
+ <table border="1" cellborder="0" cellspacing="0" cellpadding="8" COLOR="#{color}">
114
+ <tr>
115
+ <td bgcolor="#{color}" port="header" colspan="2" align="center">
116
+ <font point-size="12" color="white"><b>#{escape_html(table.table_name)}</b></font>
117
+ </td>
118
+ </tr>
119
+ TABLE
120
+
121
+ table.db_design_dynamic_columns.each_with_index do |column, i|
122
+ port_name = "port_#{i}"
123
+ attributes = []
124
+ attributes << "🔑" if column.is_primary_key
125
+ attributes << "🔗" if column.is_foreign_key
126
+ attributes << "NN" if column.not_null
127
+
128
+ node_content << <<~COLUMN
129
+ <tr>
130
+ <td port="#{port_name}" align="left">
131
+ #{escape_html(column.column_name)} #{attributes.join(' ')}
132
+ </td>
133
+ <td align="right">
134
+ <i>#{escape_html(column.data_type)}</i>
135
+ </td>
136
+ </tr>
137
+ COLUMN
138
+ end
139
+
140
+ node_content << "</table> >];\n"
141
+ node_content
142
+ end
143
+
144
+ def generate_relationship_edges(tables, table_colors)
145
+ edges_content = ""
146
+
147
+ @foreign_keys.each do |fk|
148
+ next unless @existing_table_names.include?(fk.foreign_table_name)
149
+
150
+ source_table = escape_dot_string(fk.foreign_table_name)
151
+ target_table = escape_dot_string(fk.db_design_dynamic_table.table_name)
152
+ target_column_index = fk.db_design_dynamic_table.db_design_dynamic_columns
153
+ .find_index { |c| c.column_name == fk.column_name }
154
+ next unless target_column_index
155
+
156
+ target_port = "port_#{target_column_index}"
157
+ source_port = "header"
158
+ color = table_colors[fk.db_design_dynamic_table.table_name] || "#cccccc"
159
+ edge_attributes = get_edge_attributes(fk.relationship_type)
160
+ edge_id = "edge_#{fk.foreign_table_name}_#{fk.db_design_dynamic_table.table_name}_#{fk.column_name}"
161
+ edge_title = "#{fk.foreign_table_name}:#{fk.foreign_column_name || 'id'}->#{fk.db_design_dynamic_table.table_name}:#{fk.column_name}"
162
+ relationship_label = create_relationship_label(fk)
163
+
164
+ edges_content << <<~EDGE
165
+ "#{source_table}":#{source_port} -> "#{target_table}":#{target_port} [
166
+ id="#{escape_dot_string(edge_id)}",
167
+ #{edge_attributes},
168
+ color="#{color}",
169
+ penwidth=1.5,
170
+ label="#{relationship_label}",
171
+ labeltooltip="#{escape_dot_string(edge_title)}",
172
+ tooltip="#{escape_dot_string(edge_title)}"
173
+ ];
174
+ EDGE
175
+ end
176
+
177
+ edges_content
178
+ end
179
+
180
+ def get_edge_attributes(relationship_type)
181
+ case relationship_type&.to_s
182
+ when "one_to_one"
183
+ "arrowhead=tee, arrowtail=tee, dir=both, style=solid"
184
+ when "one_to_many"
185
+ "arrowhead=crow, arrowtail=tee, dir=both, style=solid"
186
+ when "many_to_one"
187
+ "arrowhead=tee, arrowtail=crow, dir=both, style=solid"
188
+ when "many_to_many"
189
+ "arrowhead=crow, arrowtail=crow, dir=both, style=solid"
190
+ else
191
+ "arrowhead=normal, arrowtail=none, dir=forward, style=solid"
192
+ end
193
+ end
194
+
195
+ def create_relationship_label(fk)
196
+ # Create a concise label for the relationship
197
+ case fk.relationship_type&.to_s
198
+ when "one_to_one"
199
+ "1:1"
200
+ when "one_to_many"
201
+ "1:N"
202
+ when "many_to_one"
203
+ "N:1"
204
+ when "many_to_many"
205
+ "N:N"
206
+ else
207
+ fk.foreign_column_name || "ref"
208
+ end
209
+ end
210
+
211
+ def escape_dot_string(str)
212
+ return "" if str.nil?
213
+ str.to_s.gsub(/["\\]/, '\\\\\\&')
214
+ end
215
+
216
+ def escape_html(str)
217
+ return "" if str.nil?
218
+ str.to_s.gsub(/[&<>"']/, {
219
+ "&" => "&amp;",
220
+ "<" => "&lt;",
221
+ ">" => "&gt;",
222
+ '"' => "&quot;",
223
+ "'" => "&#x27;"
224
+ })
225
+ end
226
+
227
+ def generate_column_data(tables)
228
+ column_data = {}
229
+
230
+ tables.each do |table|
231
+ column_data[table.table_name] = {}
232
+
233
+ table.db_design_dynamic_columns.each do |column|
234
+ column_data[table.table_name][column.column_name] = {
235
+ name: column.column_name,
236
+ data_type: column.data_type.to_s,
237
+ description: column.description || "No description available",
238
+ is_primary_key: column.is_primary_key,
239
+ is_foreign_key: column.is_foreign_key,
240
+ not_null: column.not_null,
241
+ is_unique_key: column.is_unique_key,
242
+ is_candidate_key: column.is_candidate_key,
243
+ is_indexed: column.is_indexed,
244
+ foreign_table_name: column.foreign_table_name,
245
+ foreign_column_name: column.foreign_column_name,
246
+ relationship_type: column.relationship_type
247
+ }
248
+ end
249
+ end
250
+
251
+ column_data
252
+ end
253
+
254
+ def generate_relationship_data
255
+ relationships = {}
256
+
257
+ @foreign_keys.each do |fk|
258
+ next unless @existing_table_names.include?(fk.foreign_table_name)
259
+
260
+ edge_id = "edge_#{fk.foreign_table_name}_#{fk.db_design_dynamic_table.table_name}_#{fk.column_name}"
261
+
262
+ relationships[edge_id] = {
263
+ id: edge_id,
264
+ source_table: fk.foreign_table_name,
265
+ source_column: fk.foreign_column_name || "id",
266
+ target_table: fk.db_design_dynamic_table.table_name,
267
+ target_column: fk.column_name,
268
+ relationship_type: fk.relationship_type || "many_to_one",
269
+ description: generate_relationship_description(fk)
270
+ }
271
+ end
272
+
273
+ relationships
274
+ end
275
+
276
+ def generate_relationship_description(fk)
277
+ type_description = case fk.relationship_type&.to_s
278
+ when "one_to_one"
279
+ "One-to-One"
280
+ when "one_to_many"
281
+ "One-to-Many"
282
+ when "many_to_one"
283
+ "Many-to-One"
284
+ when "many_to_many"
285
+ "Many-to-Many"
286
+ else
287
+ "Foreign Key"
288
+ end
289
+
290
+ "#{type_description} relationship between #{fk.foreign_table_name} and #{fk.db_design_dynamic_table.table_name}"
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,35 @@
1
+ # This module provides helper methods for views across the application.
2
+ # It includes methods for handling JavaScript translations for client-side validation.
3
+ module DbdocEngine
4
+ module ApplicationHelper
5
+ # Returns a JSON object containing localized error messages for JavaScript validation
6
+ def js_translations
7
+ {
8
+ group_name_required: I18n.t("dbdoc.group_name_required"),
9
+ group_name_unique: I18n.t("dbdoc.group_name_unique"),
10
+ group_color_required: I18n.t("dbdoc.group_color_required"),
11
+ table_name_required: I18n.t("dbdoc.table_name_required"),
12
+ physical_table_name_required: I18n.t("dbdoc.physical_table_name_required"),
13
+ table_group_must_be_selected: I18n.t("dbdoc.table_group_must_be_selected"),
14
+ created_by_field_required: I18n.t("dbdoc.created_by_field_required"),
15
+ column_name_required: I18n.t("dbdoc.column_name_required"),
16
+ physical_column_name_required: I18n.t("dbdoc.physical_column_name_required"),
17
+ data_type_must_be_selected: I18n.t("dbdoc.data_type_must_be_selected"),
18
+ length_is_required_for_this_data_type: I18n.t("dbdoc.length_is_required_for_this_data_type"),
19
+ referenced_table_is_required_for_foreign_key: I18n.t("dbdoc.referenced_table_is_required_for_foreign_key"),
20
+ referenced_column_is_required_for_foreign_key: I18n.t("dbdoc.referenced_column_is_required_for_foreign_key"),
21
+ relationship_type_is_required_for_foreign_key: I18n.t("dbdoc.relationship_type_is_required_for_foreign_key"),
22
+ created_by_required: I18n.t("dbdoc.created_by_required"),
23
+ duplicate_columns: I18n.t("dbdoc.duplicate_columns"),
24
+ table_name_already_exists: I18n.t("dbdoc.table_name_already_exists"),
25
+ updated_by_required: I18n.t("dbdoc.updated_by_required"),
26
+ username_required: I18n.t("dbdoc.username_required"),
27
+ password_required: I18n.t("dbdoc.password_required")
28
+ }.to_json.html_safe
29
+ end
30
+
31
+ def admin_nav_active?(controller_name)
32
+ controller_path == "dbdoc_engine/admin/#{controller_name}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ module DbdocEngine
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,27 @@
1
+ # app/helpers/changelogs_helper.rb
2
+ # Provides helper methods for changelog-related views and forms
3
+ module DbdocEngine
4
+ module ChangelogsHelper
5
+ # Returns an array of options for the entity type select dropdown
6
+ # Used for filtering changelogs by entity type
7
+ def entity_type_options
8
+ [
9
+ ['All Types', ''], # Option to show all entity types
10
+ ['Table Group', 'table_group'],
11
+ ['Table', 'table'],
12
+ ['Column', 'column']
13
+ ]
14
+ end
15
+
16
+ # Returns an array of options for the action type select dropdown
17
+ # Used for filtering changelogs by action type
18
+ def action_type_options
19
+ [
20
+ ['All Actions', ''], # Option to show all action types
21
+ ['Create', 'create'],
22
+ ['Update', 'update'],
23
+ ['Delete', 'delete']
24
+ ]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # app/helpers/column_helper.rb
2
+ module DbdocEngine
3
+ module ColumnHelper
4
+ def data_type_select(form, selected_value = nil)
5
+ # Define the available PostgreSQL data types
6
+ data_types = [
7
+ "integer", "smallint", "bigint", "numeric", "real", "double_precision",
8
+ "varchar", "character", "text", "char", "boolean", "date", "timestamp",
9
+ "timestamptz", "time", "timetz", "interval", "uuid", "json", "jsonb"
10
+ ]
11
+
12
+ # Generate the select field with select2 controller
13
+ content_tag(:div, data: {
14
+ controller: "select2",
15
+ select2_placeholder_value: t("dbdoc.select_data_type")
16
+ }) do
17
+ form.select :data_type,
18
+ options_for_select(data_types, selected_value),
19
+ { prompt: t("dbdoc.select_data_type") },
20
+ class: "form-select #{form.object.errors[:data_type].any? ? 'is-invalid' : ''}",
21
+ data: {
22
+ column_type_target: "dataType",
23
+ select2_target: "select",
24
+ action: "change->column-type#updateLength change->column#updateAccordionTitle",
25
+ error_target: "dataType"
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # app/helpers/db_design_dynamic_tables_helper.rb
2
+ module DbdocEngine
3
+ module DbDesignDynamicTablesHelper
4
+ # Returns a list of options for group filter dropdown
5
+ # @param groups [Array<DbDesignTableGroup>] Collection of table groups
6
+ # @param selected [String, Integer] The currently selected group ID
7
+ # @return [Array] Options array for select dropdown
8
+ def group_filter_options(groups, selected = nil)
9
+ options_for_select(
10
+ [ [ I18n.t("dbdoc.all_groups"), "" ] ] + groups.map { |g| [ g.group_name, g.id ] },
11
+ selected: selected
12
+ )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ # This module provides helper methods for the home view.
2
+ # It includes methods for formatting action badges, entity types, table row styles, and array counts.
3
+ module DbdocEngine
4
+ module HomeHelper
5
+ # Returns a badge element with appropriate styling based on the action type.
6
+ def format_action_badge(action_type)
7
+ badge_classes = 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium'
8
+
9
+ case action_type
10
+ when 'create'
11
+ content_tag(:span, 'Create', class: "#{badge_classes} bg-green-100 text-green-800")
12
+ when 'update'
13
+ content_tag(:span, 'Update', class: "#{badge_classes} bg-blue-100 text-blue-800")
14
+ when 'delete'
15
+ content_tag(:span, 'Delete', class: "#{badge_classes} bg-red-100 text-red-800")
16
+ else
17
+ content_tag(:span, action_type.capitalize, class: "#{badge_classes} bg-gray-100 text-gray-800")
18
+ end
19
+ end
20
+
21
+ # Formats the entity type into a human-readable format.
22
+ def format_entity_type(entity_type)
23
+ case entity_type
24
+ when 'table_group'
25
+ 'Table Group'
26
+ when 'table'
27
+ 'Table'
28
+ when 'column'
29
+ 'Column'
30
+ else
31
+ entity_type.capitalize
32
+ end
33
+ end
34
+
35
+ # Uses .size to leverage eager-loaded associations when available
36
+ def column_count(table)
37
+ table.db_design_dynamic_columns.size
38
+ end
39
+
40
+ # Converts a hex color (e.g., "#829ED4") to an RGBA string with transparency
41
+ def hex_to_rgba(hex_color, alpha = 0.3)
42
+ rgb_values = hex_color.delete('#').scan(/../).map { |component| component.to_i(16) }
43
+ "rgba(#{rgb_values.join(', ')}, #{alpha})"
44
+ end
45
+
46
+ # Returns the total count of dynamic columns
47
+ def dynamic_column_count
48
+ AdminDashboardQueries.columns_count
49
+ end
50
+
51
+ # Returns the total count of dynamic tables
52
+ def dynamic_table_count
53
+ AdminDashboardQueries.tables_count
54
+ end
55
+
56
+ # Returns the total count of changelog entries
57
+ def changelog_count
58
+ DbdocEngine::DbDesignChangelogQueries.filtered_changelogs.count
59
+ end
60
+
61
+ # Fetches all table groups with their associated dynamic tables
62
+ def fetch_table_groups
63
+ DbdocEngine::DbDesignTableGroupQueries.all_groups_with_tables_and_columns
64
+ end
65
+
66
+ def group_badge_style(group)
67
+ rgba_color = hex_to_rgba(group.group_color)
68
+ "background-color: #{rgba_color}; color: #{group.group_color};"
69
+ end
70
+
71
+ def table_column_count(table)
72
+ table.db_design_dynamic_columns.size
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,12 @@
1
+ // app/javascript/dbdoc_engine/application.js
2
+
3
+ import "@hotwired/turbo-rails";
4
+ import { Application } from "@hotwired/stimulus";
5
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
6
+
7
+
8
+ // Initialize Stimulus
9
+ const dbdoc_engine = Application.start();
10
+ eagerLoadControllersFrom("dbdoc_engine/controllers", dbdoc_engine); // Load gem controllers
11
+
12
+ window.Stimulus = dbdoc_engine;
@@ -0,0 +1,29 @@
1
+ import { Application } from "@hotwired/stimulus";
2
+
3
+ const dbdoc_engine = dbdoc_engine.start();
4
+
5
+ // Configure Stimulus development experience
6
+ dbdoc_engine.debug = false;
7
+
8
+ export { dbdoc_engine };
9
+ export default class extends Controller {
10
+ connect() {
11
+ document.addEventListener('turbo:load', this.updateLanguageDisplay);
12
+ }
13
+
14
+ disconnect() {
15
+ document.removeEventListener('turbo:load', this.updateLanguageDisplay);
16
+ }
17
+
18
+ updateLanguageDisplay = () => {
19
+ // Refresh language display after Turbo navigation
20
+ const langController = window.Stimulus.getControllerForElementAndIdentifier(
21
+ document.querySelector('[data-controller="language"]'),
22
+ 'language'
23
+ );
24
+
25
+ if (langController) {
26
+ langController.updateButtonDisplay();
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["form"]
5
+
6
+ connect() {
7
+ this.timeout = null
8
+ }
9
+
10
+ submit() {
11
+ clearTimeout(this.timeout)
12
+
13
+ this.timeout = setTimeout(() => {
14
+ this.element.requestSubmit()
15
+ }, 300) // 300ms delay to avoid too many requests while typing
16
+ }
17
+ }
@@ -0,0 +1,58 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = { data: Object }
5
+
6
+ connect() {
7
+ this.initializeChart()
8
+ }
9
+ initializeChart() {
10
+ const ctx = this.element.getContext("2d")
11
+ if (this.chart) this.chart.destroy()
12
+
13
+ // Check if showing all actions (multiple datasets)
14
+ const isAllActions = this.dataValue.datasets.length > 1
15
+
16
+ this.chart = new window.Chart(ctx, {
17
+ type: "bar",
18
+ data: this.dataValue,
19
+ options: {
20
+ responsive: true,
21
+ maintainAspectRatio: false,
22
+ plugins: {
23
+ legend: {
24
+ display: isAllActions,
25
+ position: 'top'
26
+ },
27
+ tooltip: {
28
+ callbacks: {
29
+ label: (context) => `${context.dataset.label}: ${context.parsed.y}`
30
+ }
31
+ }
32
+ },
33
+ scales: {
34
+ x: {
35
+ stacked: isAllActions,
36
+ grid: { display: false },
37
+ title: {
38
+ display: true,
39
+ text: 'Entity Types'
40
+ }
41
+ },
42
+ y: {
43
+ stacked: isAllActions,
44
+ beginAtZero: true,
45
+ ticks: {
46
+ stepSize: 1,
47
+ precision: 0
48
+ },
49
+ title: {
50
+ display: true,
51
+ text: 'Number of Actions'
52
+ }
53
+ }
54
+ }
55
+ }
56
+ })
57
+ }
58
+ }