databasium 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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +109 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/builds/application.js +9045 -0
  7. data/app/assets/builds/application.js.map +7 -0
  8. data/app/assets/builds/databasium.css +2 -0
  9. data/app/assets/config/databasium_manifest.js +1 -0
  10. data/app/assets/javascript/databasium/application.js +2 -0
  11. data/app/assets/javascript/databasium/controllers/attribute_controller.js +27 -0
  12. data/app/assets/javascript/databasium/controllers/collapse_controller.js +18 -0
  13. data/app/assets/javascript/databasium/controllers/error_controller.js +15 -0
  14. data/app/assets/javascript/databasium/controllers/filter_controller.js +224 -0
  15. data/app/assets/javascript/databasium/controllers/flash_controller.js +18 -0
  16. data/app/assets/javascript/databasium/controllers/graph_controller.js +193 -0
  17. data/app/assets/javascript/databasium/controllers/index.js +7 -0
  18. data/app/assets/javascript/databasium/controllers/layout_controller.js +13 -0
  19. data/app/assets/javascript/databasium/controllers/model_controller.js +32 -0
  20. data/app/assets/javascript/databasium/controllers/new_migration_controller.js +107 -0
  21. data/app/assets/javascript/databasium/controllers/relation_controller.js +10 -0
  22. data/app/assets/javascript/databasium/controllers/search_controller.js +23 -0
  23. data/app/assets/javascript/databasium/controllers/table_controller.js +283 -0
  24. data/app/assets/javascript/databasium/controllers/table_select_controller.js +19 -0
  25. data/app/assets/javascript/databasium/controllers/toggle_controller.js +28 -0
  26. data/app/assets/javascript/databasium/controllers/validation_controller.js +78 -0
  27. data/app/assets/javascript/databasium/shapes/erd_table_shape.js +54 -0
  28. data/app/assets/stylesheets/databasium/application.css +15 -0
  29. data/app/assets/stylesheets/databasium/colors.css +55 -0
  30. data/app/assets/stylesheets/databasium/custom.css +36 -0
  31. data/app/assets/stylesheets/databasium/databasium_engine.css +6 -0
  32. data/app/assets/stylesheets/databasium/pagy-tailwind.css +66 -0
  33. data/app/components/base.rb +50 -0
  34. data/app/components/databasium/collapsable.rb +62 -0
  35. data/app/components/databasium/forms/model.rb +147 -0
  36. data/app/components/databasium/forms/search.rb +31 -0
  37. data/app/components/databasium/global/error.rb +60 -0
  38. data/app/components/databasium/global/flash.rb +73 -0
  39. data/app/components/databasium/global/header_actions.rb +36 -0
  40. data/app/components/databasium/global/sidebar.rb +45 -0
  41. data/app/components/databasium/global/suggestion.rb +25 -0
  42. data/app/components/databasium/migrations/action.rb +39 -0
  43. data/app/components/databasium/migrations/file.rb +58 -0
  44. data/app/components/databasium/migrations/form.rb +222 -0
  45. data/app/components/databasium/migrations/header_actions.rb +87 -0
  46. data/app/components/databasium/migrations/migration_status.rb +22 -0
  47. data/app/components/databasium/migrations/preview.rb +29 -0
  48. data/app/components/databasium/migrations/show_turbo_stream.rb +19 -0
  49. data/app/components/databasium/migrations/sidebar.rb +28 -0
  50. data/app/components/databasium/models/attributes.rb +49 -0
  51. data/app/components/databasium/models/form.rb +100 -0
  52. data/app/components/databasium/models/header_actions.rb +51 -0
  53. data/app/components/databasium/models/model_preview.rb +31 -0
  54. data/app/components/databasium/models/sidebar.rb +25 -0
  55. data/app/components/databasium/models/templates/attribute.rb +99 -0
  56. data/app/components/databasium/models/templates/base.rb +6 -0
  57. data/app/components/databasium/models/templates/relation.rb +56 -0
  58. data/app/components/databasium/models/templates/validation.rb +285 -0
  59. data/app/components/databasium/navigation/base_icon.rb +32 -0
  60. data/app/components/databasium/navigation/frontend_icon.rb +17 -0
  61. data/app/components/databasium/navigation/get_icon.rb +26 -0
  62. data/app/components/databasium/navigation/icon.rb +28 -0
  63. data/app/components/databasium/navigation/icon_panel.rb +26 -0
  64. data/app/components/databasium/navigation/post_icon.rb +25 -0
  65. data/app/components/databasium/navigation/put_icon.rb +18 -0
  66. data/app/components/databasium/records/filter.rb +73 -0
  67. data/app/components/databasium/records/foreign_records.rb +84 -0
  68. data/app/components/databasium/records/header_actions.rb +110 -0
  69. data/app/components/databasium/records/show_turbo_stream.rb +75 -0
  70. data/app/components/databasium/records/sidebar.rb +23 -0
  71. data/app/components/databasium/records/table/record_panel.rb +60 -0
  72. data/app/components/databasium/records/table/row.rb +104 -0
  73. data/app/components/databasium/records/table.rb +125 -0
  74. data/app/components/databasium/records/table_turbo_frame.rb +37 -0
  75. data/app/components/databasium/records/utilities.rb +25 -0
  76. data/app/components/databasium/schemas/header_actions.rb +99 -0
  77. data/app/components/databasium/schemas/sidebar.rb +25 -0
  78. data/app/components/databasium/search_results/migrations.rb +37 -0
  79. data/app/components/databasium/search_results/models.rb +36 -0
  80. data/app/components/databasium/search_results/schema_models.rb +37 -0
  81. data/app/components/databasium/search_results/tables.rb +31 -0
  82. data/app/components/databasium/type_select.rb +35 -0
  83. data/app/controllers/databasium/application_controller.rb +68 -0
  84. data/app/controllers/databasium/homepage_controller.rb +5 -0
  85. data/app/controllers/databasium/migrations_controller.rb +186 -0
  86. data/app/controllers/databasium/models_controller.rb +105 -0
  87. data/app/controllers/databasium/records_controller.rb +156 -0
  88. data/app/controllers/databasium/schemas_controller.rb +52 -0
  89. data/app/helpers/databasium/application_helper.rb +4 -0
  90. data/app/helpers/databasium/heroicon_helper.rb +21 -0
  91. data/app/helpers/databasium/models_helper.rb +4 -0
  92. data/app/jobs/databasium/application_job.rb +4 -0
  93. data/app/mailers/databasium/application_mailer.rb +6 -0
  94. data/app/models/databasium/application_record.rb +5 -0
  95. data/app/models/model.json +0 -0
  96. data/app/services/databasium/migration.rb +176 -0
  97. data/app/services/databasium/model.rb +182 -0
  98. data/app/services/databasium/record.rb +65 -0
  99. data/app/services/databasium/schema.rb +146 -0
  100. data/app/views/base.rb +13 -0
  101. data/app/views/databasium/errors/non_development.rb +21 -0
  102. data/app/views/databasium/homepage/index.rb +29 -0
  103. data/app/views/databasium/migrations/index.rb +33 -0
  104. data/app/views/databasium/migrations/new.rb +29 -0
  105. data/app/views/databasium/models/index.rb +31 -0
  106. data/app/views/databasium/models/new.rb +37 -0
  107. data/app/views/databasium/records/index.rb +24 -0
  108. data/app/views/databasium/schemas/index.rb +39 -0
  109. data/app/views/layouts/databasium/application.rb +56 -0
  110. data/config/importmap.rb +12 -0
  111. data/config/initializers/heroicon.rb +12 -0
  112. data/config/initializers/pagy.rb +48 -0
  113. data/config/initializers/phlex.rb +19 -0
  114. data/config/routes.rb +31 -0
  115. data/config/tailwind.config.js +10 -0
  116. data/lib/databasium/engine.rb +57 -0
  117. data/lib/databasium/engine_mount.rb +37 -0
  118. data/lib/databasium/middleware/conditional_check_pending.rb +27 -0
  119. data/lib/databasium/templates/create_table_migration.rb.tt +29 -0
  120. data/lib/databasium/templates/migration.rb.tt +48 -0
  121. data/lib/databasium/templates/model.rb.tt +23 -0
  122. data/lib/databasium/version.rb +3 -0
  123. data/lib/databasium.rb +11 -0
  124. data/lib/tasks/databasium_tasks.rake +4 -0
  125. metadata +272 -0
@@ -0,0 +1,25 @@
1
+ module Components
2
+ module Databasium
3
+ class Records::Utilities < Components::Base
4
+ def initialize(model:, columns_names_types:)
5
+ @model = model
6
+ @columns_names_types = columns_names_types
7
+ end
8
+
9
+ def view_template
10
+ div(id: "records_utilities") do
11
+ render Components::Databasium::Records::Filter.new(
12
+ model: @model,
13
+ turbo_frame: "records",
14
+ columns_names_types: @columns_names_types,
15
+ hidden: true
16
+ )
17
+ render Components::Databasium::Forms::Model.new(
18
+ columns_names_types: @columns_names_types,
19
+ model: @model
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Schemas::HeaderActions < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::FormWith
8
+ include Phlex::Rails::Helpers::HiddenFieldTag
9
+ attr_reader :model, :layers
10
+
11
+ def initialize(model:, layers:)
12
+ @model = model
13
+ @layers = layers
14
+ end
15
+
16
+ def view_template
17
+ div(id: "header_actions", class: "flex max-w-full gap-2 overflow-x-auto") do
18
+ unless model
19
+ render Components::Databasium::Navigation::IconPanel.new(
20
+ icons_with_text: [
21
+ {
22
+ icon: "arrow-path-rounded-square",
23
+ text: "Sync Schema",
24
+ method: :put,
25
+ path: databasium.sync_schema_schemas_path
26
+ }
27
+ ]
28
+ )
29
+ end
30
+ if model
31
+ render Components::Databasium::Navigation::IconPanel.new(
32
+ icons_with_text: [
33
+ {
34
+ icon: "arrow-path-rounded-square",
35
+ text: "Sync Schema",
36
+ method: :put,
37
+ path: databasium.sync_schema_schemas_path
38
+ },
39
+ {
40
+ icon: "table-cells",
41
+ text: "Model Schema",
42
+ method: :get,
43
+ turbo_frame: "records",
44
+ path: model_layers_path(model, 0),
45
+ active: layers == 0
46
+ },
47
+ {
48
+ icon: "share",
49
+ text: "Model Associations",
50
+ method: :get,
51
+ turbo_frame: "records",
52
+ path: model_layers_path(model, 1),
53
+ active: layers == 1
54
+ },
55
+ {
56
+ icon: "square-2-stack",
57
+ text: "Nested associations",
58
+ method: :get,
59
+ turbo_frame: "records",
60
+ path: model_layers_path(model, 2),
61
+ active: layers == 2
62
+ },
63
+ {
64
+ icon: "squares-2x2",
65
+ text: "All associations",
66
+ method: :get,
67
+ turbo_frame: "records",
68
+ path: model_layers_path(model, nil),
69
+ active: layers == nil
70
+ }
71
+ ]
72
+ )
73
+ end
74
+ if model
75
+ form_with(
76
+ method: :get,
77
+ url: model_layers_path(model, layers),
78
+ class: "border-l border-border pl-2 inline-flex gap-2 items-center"
79
+ ) do |form|
80
+ hidden_field_tag :model, model
81
+ form.number_field :layers,
82
+ value: layers,
83
+ class:
84
+ "w-10 border-1 border-border rounded-xl p-1 text-center text-sm"
85
+ form.submit "Search for associations",
86
+ class: "text-sm bg-accent shadow-accent rounded-xl p-1.5 text-center"
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def model_layers_path(model, layers)
95
+ databasium.schemas_path(model: model, layers: layers)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class Schemas::Sidebar < Components::Base
6
+ include Phlex::Rails::Helpers::TurboFrameTag
7
+
8
+ def initialize
9
+ end
10
+
11
+ def view_template
12
+ div(class: "flex flex-col gap-2", data: { controller: "search" }) do
13
+ render Components::Databasium::Forms::Search.new(
14
+ url: databasium.sidebar_schemas_path,
15
+ turbo_frame: "results",
16
+ placeholder: "Search for a model"
17
+ )
18
+ turbo_frame_tag("results", src: databasium.sidebar_schemas_path) do
19
+ p(class: "mt-2 animate-pulse") { "Loading models..." }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class SearchResults::Migrations < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::TurboFrameTag
8
+
9
+ def initialize(migrations:, pending_migrations:, pagy:)
10
+ @migrations = migrations
11
+ @pending_migrations = pending_migrations
12
+ @pagy = pagy
13
+ end
14
+
15
+ def view_template
16
+ turbo_frame_tag("results") do
17
+ @migrations.each do |m|
18
+ status = @pending_migrations.include?(m.version) ? "pending" : "applied"
19
+ link_to(
20
+ databasium.migration_path(m.version),
21
+ data: {
22
+ turbo_stream: true
23
+ },
24
+ class:
25
+ "text-main-text hover:text-hover hover:cursor-pointer flex items-center gap-2 p-1 border-b
26
+ border-border flex items-center justify-between"
27
+ ) do
28
+ p(class: "max-w-fit overflow-x-auto me-2 scrollbar-thin p-1") { "#{m.name}" }
29
+ render Migrations::MigrationStatus.new(status: status, version: m.version)
30
+ end
31
+ end
32
+ div(class: "mt-4 flex justify-start") { raw @pagy.series_nav.html_safe } if @pagy
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class SearchResults::Models < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::TurboFrameTag
8
+
9
+ def initialize(models:, pagy:)
10
+ @models = models
11
+ @pagy = pagy
12
+ end
13
+
14
+ def view_template
15
+ turbo_frame_tag("results") do
16
+ @models&.each do |model|
17
+ link_to(
18
+ databasium.model_path(model),
19
+ data: {
20
+ turbo_frame: "main"
21
+ },
22
+ class:
23
+ "text-main-text hover:text-hover hover:cursor-pointer flex items-center gap-2 p-1 border-b
24
+ border-border flex items-center justify-between"
25
+ ) do
26
+ p(class: "max-w-fit overflow-x-auto me-2 scrollbar-thin p-1") do
27
+ "#{model.upcase_first}"
28
+ end
29
+ end
30
+ end
31
+ div(class: "mt-4 flex justify-start") { raw @pagy.series_nav.html_safe } if @pagy
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class SearchResults::SchemaModels < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::TurboFrameTag
8
+
9
+ def initialize(models:, pagy:)
10
+ @models = models
11
+ @pagy = pagy
12
+ end
13
+
14
+ def view_template
15
+ turbo_frame_tag("results") do
16
+ @models&.each do |model|
17
+ link_to(
18
+ databasium.schemas_path(model: model.upcase_first, layers: 0),
19
+ data: {
20
+ turbo_frame: "main"
21
+ },
22
+ class:
23
+ "text-main-text hover:text-hover hover:cursor-pointer flex items-center gap-2 p-1 border-b
24
+ border-border justify-between"
25
+ ) do
26
+ p(class: "max-w-fit overflow-x-auto me-2 scrollbar-thin p-1") do
27
+ "#{model.upcase_first}"
28
+ end
29
+ end
30
+ end
31
+ p(class: "text-main-text text-center p-4") { "No models found" } if @models.empty?
32
+ div(class: "mt-4 flex justify-start") { raw @pagy.series_nav.html_safe } if @pagy
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class SearchResults::Tables < Components::Base
6
+ include Phlex::Rails::Helpers::LinkTo
7
+ include Phlex::Rails::Helpers::TurboFrameTag
8
+
9
+ def initialize(tables:, pagy:)
10
+ @tables = tables
11
+ @pagy = pagy
12
+ end
13
+
14
+ def view_template
15
+ turbo_frame_tag("results") do
16
+ @tables&.each do |table|
17
+ div(class: "border-b-2 border-b-border py-2 px-3") do
18
+ link_to "#{table}",
19
+ databasium.records_records_path(table: table, refresh: true),
20
+ data: {
21
+ turbo_frame: "_top",
22
+ turbo_stream: true
23
+ }
24
+ end
25
+ end
26
+ div(class: "mt-4 flex justify-start") { raw @pagy.series_nav.html_safe } if @pagy
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Databasium
5
+ class TypeSelect < Components::Base
6
+ def initialize(name: nil, value: nil)
7
+ @name = name
8
+ @value = value
9
+ end
10
+
11
+ def view_template
12
+ select(
13
+ name: @name,
14
+ class:
15
+ "border-2 rounded-xl px-2 py-1 border-border h-full bg-background focus:outline-none"
16
+ ) do
17
+ [
18
+ %w[text Text],
19
+ %w[string String],
20
+ %w[integer Integer],
21
+ %w[float Float],
22
+ %w[decimal Decimal],
23
+ %w[time Time],
24
+ %w[date Date],
25
+ %w[datetime Datetime],
26
+ %w[timestamp Timestamp],
27
+ %w[binary Binary],
28
+ %w[boolean Boolean],
29
+ %w[references Reference]
30
+ ].each { |value, label| option(value: value, selected: @value == value) { label } }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ module Databasium
2
+ class ApplicationController < ActionController::Base
3
+ helper ::Databasium::HeroiconHelper
4
+
5
+ layout -> { Views::Layouts::Databasium::Application.new }
6
+ before_action :check_development_environment
7
+
8
+ rescue_from Exception, with: :render_error_flash if Rails.env.development?
9
+
10
+ private
11
+
12
+ def check_development_environment
13
+ render Views::Databasium::Errors::NonDevelopment.new if Rails.env.production?
14
+ end
15
+
16
+ def render_error_flash(error)
17
+ Rails.logger.error("[Databasium] #{error.class}: #{error.message}")
18
+ Rails.logger.error(error.backtrace.join("\n")) if error.backtrace
19
+
20
+ return if performed?
21
+
22
+ message, details = error_message_parts(error)
23
+
24
+ respond_to do |format|
25
+ format.turbo_stream do
26
+ render turbo_stream: [
27
+ turbo_stream.replace(
28
+ "flash",
29
+ Components::Databasium::Global::Flash.new(
30
+ success: flash[:success],
31
+ error: flash[:error]
32
+ )
33
+ ),
34
+ turbo_stream.replace(
35
+ "error",
36
+ Components::Databasium::Global::Error.new(
37
+ message: message,
38
+ details: details,
39
+ type: error.class.name
40
+ )
41
+ )
42
+ ],
43
+ status: :internal_server_error
44
+ end
45
+ format.any do
46
+ render Components::Databasium::Global::Error.new(
47
+ message: message,
48
+ details: details,
49
+ type: error.class.name
50
+ ),
51
+ status: :internal_server_error
52
+ end
53
+ end
54
+ end
55
+
56
+ def error_message_parts(error)
57
+ lines = strip_ansi(error.message.to_s).lines.map(&:chomp)
58
+ message = lines.shift.presence || "Something went wrong"
59
+ details = lines.join("\n")
60
+
61
+ [ message, details.presence || strip_ansi(error.backtrace&.join("\n").to_s).presence ]
62
+ end
63
+
64
+ def strip_ansi(text)
65
+ text.gsub(/\e\[[0-9;]*m/, "")
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ class Databasium::HomepageController < Databasium::ApplicationController
2
+ def index
3
+ render Views::Databasium::Homepage::Index.new
4
+ end
5
+ end
@@ -0,0 +1,186 @@
1
+ class Databasium::MigrationsController < Databasium::ApplicationController
2
+ before_action :set_migration_service, except: [ :index ]
3
+ after_action -> { Databasium::Schema.new.sync! },
4
+ only: %i[run_migration rollback_migration run_pending_migrations]
5
+ include Pagy::Method
6
+
7
+ def index
8
+ render Views::Databasium::Migrations::Index.new
9
+ end
10
+
11
+ def show
12
+ @migration = @migration_service.find_migration!(params[:id])
13
+ @content = File.read(@migration.filename)
14
+
15
+ respond_to do |format|
16
+ format.html do
17
+ render Components::Databasium::Migrations::File.new(
18
+ migration: @migration,
19
+ content: @content
20
+ )
21
+ end
22
+ format.turbo_stream do
23
+ render Components::Databasium::Migrations::ShowTurboStream.new(
24
+ migration: @migration,
25
+ content: @content
26
+ ),
27
+ layout: false
28
+ end
29
+ end
30
+ end
31
+
32
+ def new
33
+ @tables = Databasium::Schema.new.tables
34
+ render Views::Databasium::Migrations::New.new(tables: @tables)
35
+ end
36
+
37
+ def create
38
+ require "rails/generators"
39
+ Rails.application.load_generators
40
+ require "rails/generators/active_record/migration/migration_generator"
41
+
42
+ if params[:add_migration] == "Save"
43
+ success = @migration_service.save_migration(migration_params)
44
+ else
45
+ content = @migration_service.generate_migration(migration_params)
46
+ end
47
+
48
+ if success
49
+ flash[:success] = "Migration for table #{migration_params[:table_name]} saved successfully."
50
+ redirect_to migrations_path, status: :see_other
51
+ elsif content
52
+ render turbo_stream:
53
+ turbo_stream.replace(
54
+ "migration_preview",
55
+ Components::Databasium::Migrations::Preview.new(content: content)
56
+ )
57
+ else
58
+ head :unprocessable_entity
59
+ end
60
+ end
61
+
62
+ def sidebar
63
+ pagy, migrations =
64
+ pagy(@migration_service.get_migrations(params[:search]), limit: 5, root_key: "migrations")
65
+ pending_migrations = @migration_service.pending_migrations
66
+
67
+ render Components::Databasium::SearchResults::Migrations.new(
68
+ migrations: migrations,
69
+ pending_migrations: pending_migrations,
70
+ pagy: pagy
71
+ )
72
+ end
73
+
74
+ def run_pending_migrations
75
+ versions = @migration_service.run_pending_migrations
76
+ message =
77
+ if versions.any?
78
+ "Pending migrations(#{versions.count}) run successfully"
79
+ else
80
+ "No pending migrations to run"
81
+ end
82
+
83
+ respond_to do |format|
84
+ format.html { redirect_to migrations_path, notice: message }
85
+ format.turbo_stream do
86
+ streams = [
87
+ turbo_stream.replace("error", Components::Databasium::Global::Error.new),
88
+ turbo_stream.replace(
89
+ "flash",
90
+ Components::Databasium::Global::Flash.new(success: message, error: nil)
91
+ )
92
+ ]
93
+
94
+ streams +=
95
+ versions.map do |version|
96
+ turbo_stream.replace(
97
+ "migration_#{version}_status",
98
+ Components::Databasium::Migrations::MigrationStatus.new(
99
+ status: "applied",
100
+ version: version
101
+ )
102
+ )
103
+ end
104
+
105
+ render turbo_stream: streams
106
+ end
107
+ end
108
+ end
109
+
110
+ def rollback_migration
111
+ version = rollback_migration_params[:version]
112
+ success =
113
+ @migration_service.rollback_migration(
114
+ version,
115
+ rollback_migration_params[:rollback_steps],
116
+ rollback_migration_params[:till_this_migration]
117
+ )
118
+ message = "Migration rolled back successfully"
119
+ if rollback_migration_params[:till_this_migration] == "true" ||
120
+ rollback_migration_params[:rollback_steps].present?
121
+ set_action_flash(message, nil)
122
+ redirect_to migrations_path(version: version)
123
+ else
124
+ response_to_action(message, nil, version, success ? "pending" : nil)
125
+ end
126
+ end
127
+
128
+ def run_migration
129
+ version = run_migration_params[:version]
130
+ @migration_service.run_migration(version)
131
+ response_to_action("Migration run successfully", nil, version, "applied")
132
+ end
133
+
134
+ private
135
+
136
+ def set_migration_service
137
+ @migration_service = Databasium::Migration.new
138
+ end
139
+
140
+ def migration_params
141
+ params.permit(
142
+ :add_migration,
143
+ :migration_action,
144
+ :table_name,
145
+ :table_name_from,
146
+ :table_name_to,
147
+ :add_model,
148
+ columns: %i[column_name column_type],
149
+ validation: %i[column_name type]
150
+ )
151
+ end
152
+
153
+ def run_migration_params
154
+ params.permit(:version)
155
+ end
156
+
157
+ def rollback_migration_params
158
+ params.permit(:version, :till_this_migration, :rollback_steps)
159
+ end
160
+
161
+ def new_action_response(success, error, migration_version, status)
162
+ Components::Databasium::Migrations::Action.new(
163
+ success: success,
164
+ error: error,
165
+ migration_version: migration_version,
166
+ status: status
167
+ )
168
+ end
169
+
170
+ def response_to_action(success, error, migration_version, status)
171
+ respond_to do |format|
172
+ format.turbo_stream do
173
+ render new_action_response(success, error, migration_version, status), layout: false
174
+ end
175
+ format.html do
176
+ set_action_flash(success, error)
177
+ redirect_to migrations_path(version: migration_version)
178
+ end
179
+ end
180
+ end
181
+
182
+ def set_action_flash(success, error)
183
+ flash[:success] = success if success.present?
184
+ flash[:error] = error if error.present?
185
+ end
186
+ end
@@ -0,0 +1,105 @@
1
+ class Databasium::ModelsController < Databasium::ApplicationController
2
+ include Pagy::Method
3
+ before_action :create_model_service
4
+ MODEL_TEMPLATE_PATH = Databasium::Engine.root.join("lib/databasium/templates/model.rb.tt")
5
+
6
+ def new
7
+ models = @model_service.get_all_models_from_db(search: params[:search])
8
+
9
+ render Views::Databasium::Models::New.new(content: nil, models: models)
10
+ end
11
+
12
+ def show
13
+ model = params[:id]
14
+ content = @model_service.read_model_file(model)
15
+ attributes = @model_service.get_model_data_from_file(model)
16
+ models = @model_service.get_all_models_from_db(search: params[:search])
17
+
18
+ respond_to do |format|
19
+ format.html do
20
+ render Views::Databasium::Models::New.new(
21
+ content: content,
22
+ model: model,
23
+ attributes: attributes,
24
+ models: models
25
+ )
26
+ end
27
+ format.turbo_stream do
28
+ render turbo_stream:
29
+ turbo_stream.replace(
30
+ "model_preview",
31
+ Components::Databasium::Models::ModelPreview.new(content: content)
32
+ )
33
+ end
34
+ end
35
+ end
36
+
37
+ def sidebar
38
+ models = @model_service.get_all_models_from_db(search: params[:search])
39
+ pagy, models = pagy(models, limit: 7, root_key: "models")
40
+
41
+ render Components::Databasium::SearchResults::Models.new(models: models, pagy: pagy)
42
+ end
43
+
44
+ def create
45
+ content = generate_model_content
46
+ if params[:commit] == "Create model file"
47
+ if write_file(content)
48
+ render turbo_stream:
49
+ turbo_stream.replace(
50
+ "flash",
51
+ Components::Databasium::Global::Flash.new(
52
+ success:
53
+ "Model file created successfully, be sure to create a migration for this model if you haven't already"
54
+ )
55
+ )
56
+ end
57
+ else
58
+ respond_to do |format|
59
+ format.html
60
+ format.turbo_stream do
61
+ render turbo_stream:
62
+ turbo_stream.replace(
63
+ "model_preview",
64
+ Components::Databasium::Models::ModelPreview.new(content: content)
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def create_model_service
74
+ @model_service = Databasium::Model.new
75
+ end
76
+
77
+ def generate_model_content
78
+ renderer = ERB.new(File.read(MODEL_TEMPLATE_PATH), trim_mode: "-")
79
+
80
+ context =
81
+ @model_service.create_model_data(
82
+ model_name: model_params[:model_name],
83
+ attributes: model_params[:attributes],
84
+ relations: model_params[:relations],
85
+ unknown: model_params[:unknown]
86
+ )
87
+
88
+ renderer.result(context.get_binding)
89
+ end
90
+
91
+ def write_file(content)
92
+ model_name = model_params[:model_name].underscore
93
+ destination_path = Rails.root.join("app/models/#{model_name}.rb")
94
+ File.open(destination_path, "w") { |file| file.write(content) }
95
+ end
96
+
97
+ def model_params
98
+ params.require(:model).permit(
99
+ :model_name,
100
+ attributes: [ :name, :type, validations: %i[name type value] ],
101
+ relations: %i[type table_name],
102
+ unknown: []
103
+ )
104
+ end
105
+ end