mat_views 0.1.2 → 0.3.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -10
  3. data/app/assets/images/mat_views/android-chrome-192x192.png +0 -0
  4. data/app/assets/images/mat_views/android-chrome-512x512.png +0 -0
  5. data/app/assets/images/mat_views/apple-touch-icon.png +0 -0
  6. data/app/assets/images/mat_views/favicon-16x16.png +0 -0
  7. data/app/assets/images/mat_views/favicon-32x32.png +0 -0
  8. data/app/assets/images/mat_views/favicon-48x48.png +0 -0
  9. data/app/assets/images/mat_views/favicon.ico +0 -0
  10. data/app/assets/images/mat_views/favicon.svg +18 -0
  11. data/app/assets/images/mat_views/logo.svg +18 -0
  12. data/app/assets/images/mat_views/mask-icon.svg +5 -0
  13. data/app/assets/stylesheets/mat_views/application.css +323 -12
  14. data/app/controllers/mat_views/admin/application_controller.rb +135 -0
  15. data/app/controllers/mat_views/admin/dashboard_controller.rb +32 -0
  16. data/app/controllers/mat_views/admin/mat_view_definitions_controller.rb +248 -0
  17. data/app/controllers/mat_views/admin/preferences_controller.rb +91 -0
  18. data/app/controllers/mat_views/admin/runs_controller.rb +74 -0
  19. data/app/helpers/mat_views/admin/ui_helper.rb +385 -0
  20. data/app/javascript/mat_views/application.js +8 -0
  21. data/app/javascript/mat_views/controllers/application.js +10 -0
  22. data/app/javascript/mat_views/controllers/details_controller.js +122 -0
  23. data/app/javascript/mat_views/controllers/drawer_controller.js +252 -0
  24. data/app/javascript/mat_views/controllers/filter_controller.js +90 -0
  25. data/app/javascript/mat_views/controllers/flash_controller.js +13 -0
  26. data/app/javascript/mat_views/controllers/index.js +10 -0
  27. data/app/javascript/mat_views/controllers/mv_confirm_controller.js +281 -0
  28. data/app/javascript/mat_views/controllers/submitter_controller.js +15 -0
  29. data/app/javascript/mat_views/controllers/tabs_controller.js +67 -0
  30. data/app/javascript/mat_views/controllers/timezone_controller.js +16 -0
  31. data/app/javascript/mat_views/controllers/tooltip_controller.js +328 -0
  32. data/app/javascript/mat_views/controllers/turbo_frame_lifecycle_controller.js +49 -0
  33. data/app/jobs/mat_views/application_job.rb +107 -2
  34. data/app/jobs/mat_views/create_view_job.rb +21 -122
  35. data/app/jobs/mat_views/delete_view_job.rb +22 -129
  36. data/app/jobs/mat_views/refresh_view_job.rb +12 -133
  37. data/app/models/concerns/mat_views_i18n.rb +139 -0
  38. data/app/models/mat_views/application_record.rb +1 -0
  39. data/app/models/mat_views/mat_view_definition.rb +12 -7
  40. data/app/models/mat_views/mat_view_run.rb +34 -16
  41. data/app/views/layouts/mat_views/_footer.html.erb +41 -0
  42. data/app/views/layouts/mat_views/_header.html.erb +25 -0
  43. data/app/views/layouts/mat_views/admin.html.erb +47 -0
  44. data/app/views/layouts/mat_views/turbo_frame.html.erb +3 -0
  45. data/app/views/mat_views/admin/dashboard/index.html.erb +33 -0
  46. data/app/views/mat_views/admin/mat_view_definitions/_definition_actions.html.erb +94 -0
  47. data/app/views/mat_views/admin/mat_view_definitions/_table.html.erb +48 -0
  48. data/app/views/mat_views/admin/mat_view_definitions/empty.html.erb +1 -0
  49. data/app/views/mat_views/admin/mat_view_definitions/form.html.erb +79 -0
  50. data/app/views/mat_views/admin/mat_view_definitions/index.html.erb +10 -0
  51. data/app/views/mat_views/admin/mat_view_definitions/show.html.erb +40 -0
  52. data/app/views/mat_views/admin/preferences/show.html.erb +50 -0
  53. data/app/views/mat_views/admin/runs/_table.html.erb +61 -0
  54. data/app/views/mat_views/admin/runs/index.html.erb +38 -0
  55. data/app/views/mat_views/admin/runs/show.html.erb +64 -0
  56. data/app/views/mat_views/admin/ui/_card.html.erb +15 -0
  57. data/app/views/mat_views/admin/ui/_details.html.erb +10 -0
  58. data/app/views/mat_views/admin/ui/_flash.html.erb +6 -0
  59. data/app/views/mat_views/admin/ui/_table.html.erb +8 -0
  60. data/config/importmap.rb +9 -0
  61. data/config/locales/en-AU-ocker.yml +187 -0
  62. data/config/locales/en-AU.yml +187 -0
  63. data/config/locales/en-BB.yml +187 -0
  64. data/config/locales/en-BD.yml +187 -0
  65. data/config/locales/en-BE.yml +187 -0
  66. data/config/locales/en-BORK.yml +187 -0
  67. data/config/locales/en-BS.yml +187 -0
  68. data/config/locales/en-BZ.yml +187 -0
  69. data/config/locales/en-CA.yml +187 -0
  70. data/config/locales/en-CM.yml +187 -0
  71. data/config/locales/en-CY.yml +187 -0
  72. data/config/locales/en-EG.yml +187 -0
  73. data/config/locales/en-FJ.yml +187 -0
  74. data/config/locales/en-GB.yml +187 -0
  75. data/config/locales/en-GH.yml +187 -0
  76. data/config/locales/en-GI.yml +187 -0
  77. data/config/locales/en-GM.yml +187 -0
  78. data/config/locales/en-GY.yml +187 -0
  79. data/config/locales/en-HK.yml +187 -0
  80. data/config/locales/en-IE.yml +187 -0
  81. data/config/locales/en-IN.yml +187 -0
  82. data/config/locales/en-JM.yml +187 -0
  83. data/config/locales/en-KE.yml +187 -0
  84. data/config/locales/en-LK.yml +187 -0
  85. data/config/locales/en-LOL.yml +187 -0
  86. data/config/locales/en-LR.yml +187 -0
  87. data/config/locales/en-MS.yml +187 -0
  88. data/config/locales/en-MT.yml +187 -0
  89. data/config/locales/en-MW.yml +187 -0
  90. data/config/locales/en-MY.yml +187 -0
  91. data/config/locales/en-NG.yml +187 -0
  92. data/config/locales/en-NP.yml +187 -0
  93. data/config/locales/en-NZ.yml +187 -0
  94. data/config/locales/en-PG.yml +187 -0
  95. data/config/locales/en-PH.yml +187 -0
  96. data/config/locales/en-PK.yml +187 -0
  97. data/config/locales/en-RW.yml +187 -0
  98. data/config/locales/en-SCOT.yml +187 -0
  99. data/config/locales/en-SG.yml +187 -0
  100. data/config/locales/en-SHAKESPEARE.yml +187 -0
  101. data/config/locales/en-SL.yml +187 -0
  102. data/config/locales/en-SS.yml +187 -0
  103. data/config/locales/en-TH.yml +187 -0
  104. data/config/locales/en-TT.yml +187 -0
  105. data/config/locales/en-TZ.yml +187 -0
  106. data/config/locales/en-UG.yml +187 -0
  107. data/config/locales/en-US-pirate.yml +187 -0
  108. data/config/locales/en-US.yml +187 -0
  109. data/config/locales/en-YODA.yml +187 -0
  110. data/config/locales/en-ZA.yml +187 -0
  111. data/config/locales/en-ZW.yml +187 -0
  112. data/config/locales/en.yml +187 -0
  113. data/config/routes.rb +27 -3
  114. data/lib/ext/exception.rb +20 -0
  115. data/lib/generators/mat_views/install/templates/create_mat_view_definitions.rb +7 -7
  116. data/lib/generators/mat_views/install/templates/create_mat_view_runs.rb +7 -7
  117. data/lib/mat_views/admin/auth_bridge.rb +93 -0
  118. data/lib/mat_views/admin/default_auth.rb +61 -0
  119. data/lib/mat_views/configuration.rb +9 -0
  120. data/lib/mat_views/engine.rb +50 -2
  121. data/lib/mat_views/helpers/ui_test_ids.rb +43 -0
  122. data/lib/mat_views/jobs/adapter.rb +8 -5
  123. data/lib/mat_views/service_response.rb +30 -15
  124. data/lib/mat_views/services/base_service.rb +204 -41
  125. data/lib/mat_views/services/check_matview_exists.rb +76 -0
  126. data/lib/mat_views/services/concurrent_refresh.rb +38 -121
  127. data/lib/mat_views/services/create_view.rb +72 -55
  128. data/lib/mat_views/services/delete_view.rb +46 -95
  129. data/lib/mat_views/services/regular_refresh.rb +38 -94
  130. data/lib/mat_views/services/swap_refresh.rb +83 -123
  131. data/lib/mat_views/version.rb +1 -1
  132. data/lib/mat_views.rb +13 -6
  133. data/lib/tasks/helpers.rb +27 -27
  134. data/lib/tasks/mat_views_tasks.rake +48 -42
  135. metadata +131 -5
@@ -5,8 +5,8 @@
5
5
  # This source code is licensed under the MIT license found in the
6
6
  # LICENSE file in the root directory of this source tree.
7
7
 
8
- # This migration creates the mat_view_runs table, which tracks the mutation runs(create,refresh,drop) of materialized views.
9
- # It includes references to the materialized view definition, status, operation type, timestamps,
8
+ # This migration creates the mat_view_runs table, which tracks the mutation runs(create,refresh,drop) of materialised views.
9
+ # It includes references to the materialised view definition, status, operation type, timestamps,
10
10
  # duration, error messages, and additional metadata.
11
11
  class CreateMatViewRuns < ActiveRecord::Migration[7.1]
12
12
  def change
@@ -14,17 +14,17 @@ class CreateMatViewRuns < ActiveRecord::Migration[7.1]
14
14
  t.references :mat_view_definition,
15
15
  null: false,
16
16
  foreign_key: true,
17
- comment: 'Reference to the materialized view definition'
17
+ comment: 'Reference to the materialised view definition'
18
18
 
19
- # 0=pending, 1=running, 2=success, 3=failed
20
- t.integer :status, null: false, default: 0, comment: '0=pending,1=running,2=success,3=failed'
19
+ # 0=running, 1=success, 2=failed
20
+ t.integer :status, null: false, default: 0, comment: '0=running,1=success,2=failed'
21
21
  # 0=create, 1=refresh, 2=drop
22
22
  t.integer :operation, null: false, default: 0, comment: '0=create,1=refresh,2=drop'
23
23
  t.datetime :started_at, comment: 'Timestamp when the operation started'
24
24
  t.datetime :finished_at, comment: 'Timestamp when the operation finished'
25
25
  t.integer :duration_ms, comment: 'Duration of the operation in milliseconds'
26
- t.text :error, comment: 'Error message if the operation failed'
27
- t.jsonb :meta, null: false, default: {}, comment: 'Additional metadata about the run, such as job ID, row count or parameters'
26
+ t.jsonb :error, comment: 'Error details if the operation failed. :message, :class, :backtrace'
27
+ t.jsonb :meta, null: false, default: {}, comment: 'Additional metadata about the run, such as job ID, row count or parameters'
28
28
 
29
29
  t.timestamps
30
30
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ module MatViews
9
+ module Admin
10
+ #
11
+ # MatViews::Admin::AuthBridge
12
+ # ---------------------------
13
+ # Bridge module that wires the MatViews admin engine to a **host-provided**
14
+ # authentication/authorization layer, while providing safe defaults.
15
+ #
16
+ # ### How it works
17
+ # - Includes {MatViews::Admin::DefaultAuth} first (fallback, no-op/hostable).
18
+ # - Then includes the **host auth module** returned by {.host_auth_module}.
19
+ # Because Ruby searches the most recently included module first, the host
20
+ # module cleanly **overrides** any defaults from `DefaultAuth`.
21
+ # - Registers a before_action `authenticate_mat_views!`.
22
+ # - Exposes helpers: `mat_views_current_user` and its alias {#user}.
23
+ #
24
+ # ### Host integration options (define one of these):
25
+ # 1) A top-level module:
26
+ # ```ruby
27
+ # # app/lib/mat_views_admin.rb (or any autoloaded path)
28
+ # module MatViewsAdmin
29
+ # def authenticate_mat_views!; end
30
+ # def authorize_mat_views!(*); end
31
+ # def mat_views_current_user; end
32
+ # end
33
+ # ```
34
+ # 2) A namespaced module:
35
+ # ```ruby
36
+ # # app/lib/mat_views/admin/host_auth.rb
37
+ # module MatViews
38
+ # module Admin
39
+ # module HostAuth
40
+ # def authenticate_mat_views!; end
41
+ # def authorize_mat_views!(*); end
42
+ # def mat_views_current_user; end
43
+ # end
44
+ # end
45
+ # end
46
+ # ```
47
+ #
48
+ # If neither module is present, a blank `Module.new` is included and the
49
+ # defaults in {MatViews::Admin::DefaultAuth} remain in effect.
50
+ #
51
+ # @see MatViews::Admin::DefaultAuth
52
+ #
53
+ module AuthBridge
54
+ extend ActiveSupport::Concern
55
+
56
+ included do
57
+ # Include defaults first, so the host module (included below) can override.
58
+ include MatViews::Admin::DefaultAuth
59
+ include host_auth_module
60
+
61
+ before_action :authenticate_mat_views!
62
+ helper_method :mat_views_current_user, :user
63
+ end
64
+
65
+ # Convenience alias for `mat_views_current_user` exposed to views.
66
+ #
67
+ # @return [Object, nil] the current user object as defined by host auth
68
+ def user = mat_views_current_user
69
+
70
+ class_methods do
71
+ # Resolves the host's auth module, if any.
72
+ #
73
+ # Lookup order:
74
+ # 1. `::MatViewsAdmin`
75
+ # 2. `::MatViews::Admin::HostAuth`
76
+ # 3. Fallback: a blank Module (no overrides)
77
+ #
78
+ # @return [Module] the module to include for host auth overrides
79
+ def host_auth_module
80
+ if Object.const_defined?('MatViewsAdmin')
81
+ ::MatViewsAdmin
82
+ elsif Object.const_defined?('MatViews') &&
83
+ MatViews.const_defined?('Admin') &&
84
+ MatViews::Admin.const_defined?('HostAuth')
85
+ ::MatViews::Admin::HostAuth
86
+ else
87
+ Module.new
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ module MatViews
9
+ module Admin
10
+ # MatViews::Admin::DefaultAuth
11
+ # ----------------------------
12
+ # Development-friendly **fallback** authentication/authorization for the MatViews
13
+ # admin UI. It is included first by {MatViews::Admin::AuthBridge}, and is meant
14
+ # to be overridden by a host-provided module (`MatViewsAdmin` or
15
+ # `MatViews::Admin::HostAuth`).
16
+ #
17
+ # ❗ **Not for production**: this module allows all access and returns a dummy user.
18
+ #
19
+ # @see MatViews::Admin::AuthBridge
20
+ #
21
+ module DefaultAuth
22
+ # Minimal stand-in user object used by the default auth.
23
+ #
24
+ # @!attribute [rw] email
25
+ # @return [String] the email address of the sample user
26
+ class SampleUser
27
+ attr_accessor :email
28
+
29
+ # @param email [String]
30
+ def initialize(email) = @email = email
31
+
32
+ # @return [String] the user's email
33
+ def to_s = email
34
+ end
35
+
36
+ # Authenticates the current request.
37
+ # Always returns true in the default implementation.
38
+ #
39
+ # @return [Boolean] true
40
+ # rubocop:disable Naming/PredicateMethod
41
+ def authenticate_mat_views! = true
42
+ # rubocop:enable Naming/PredicateMethod
43
+
44
+ # Returns the current user object.
45
+ # In the default implementation this is a {SampleUser}.
46
+ #
47
+ # @return [SampleUser]
48
+ def mat_views_current_user = SampleUser.new('sample-user@example.com')
49
+
50
+ # Authorizes an action on a record.
51
+ # Always returns true in the default implementation.
52
+ #
53
+ # @param _action [Symbol, String] the attempted action (ignored)
54
+ # @param _record [Object] the target record or symbol (ignored)
55
+ # @return [Boolean] true
56
+ # rubocop:disable Naming/PredicateMethod
57
+ def authorize_mat_views!(_action, _record) = true
58
+ # rubocop:enable Naming/PredicateMethod
59
+ end
60
+ end
61
+ end
@@ -17,6 +17,7 @@ module MatViews
17
17
  # MatViews.configure do |config|
18
18
  # config.job_adapter = :sidekiq
19
19
  # config.job_queue = :low_priority
20
+ # config.admin_ui = { row_count_strategy: :estimated }
20
21
  # end
21
22
  #
22
23
  # Supported job adapters:
@@ -37,6 +38,11 @@ module MatViews
37
38
  # @return [Symbol, String]
38
39
  attr_accessor :job_queue
39
40
 
41
+ ##
42
+ # admin_ui configuration
43
+ # @return [Hash]
44
+ attr_accessor :admin_ui
45
+
40
46
  ##
41
47
  # Initialize with defaults.
42
48
  #
@@ -44,6 +50,9 @@ module MatViews
44
50
  def initialize
45
51
  @job_adapter = :active_job
46
52
  @job_queue = :default
53
+ @admin_ui = {
54
+ row_count_strategy: :none
55
+ }
47
56
  end
48
57
  end
49
58
  end
@@ -5,13 +5,19 @@
5
5
  # This source code is licensed under the MIT license found in the
6
6
  # LICENSE file in the root directory of this source tree.
7
7
 
8
+ ##
9
+ # MatViews is a Rails engine that provides first-class support for
10
+ # PostgreSQL materialised views in Rails applications.
8
11
  module MatViews
12
+ class << self
13
+ attr_accessor :importmap
14
+ end
9
15
  ##
10
16
  # Rails Engine for MatViews.
11
17
  #
12
18
  # This engine encapsulates all functionality related to
13
- # materialized views, including:
14
- # - Defining materialized view definitions
19
+ # materialised views, including:
20
+ # - Defining materialised view definitions
15
21
  # - Creating and refreshing views
16
22
  # - Managing background jobs for refresh/create/delete
17
23
  #
@@ -30,5 +36,47 @@ module MatViews
30
36
  initializer 'mat_views.load_config' do
31
37
  MatViews.configuration ||= MatViews::Configuration.new
32
38
  end
39
+
40
+ initializer 'mat_views.javascript' do |app|
41
+ app.config.assets.paths << root.join('app/javascript')
42
+ end
43
+
44
+ initializer 'mat_views.importmap', before: 'importmap' do |_app|
45
+ next unless defined?(Importmap)
46
+
47
+ MatViews.importmap = Importmap::Map.new
48
+ MatViews.importmap.draw(root.join('config/importmap.rb'))
49
+ MatViews.importmap.cache_sweeper(watches: root.join('app/javascript'))
50
+
51
+ ActiveSupport.on_load(:action_controller_base) do
52
+ before_action { MatViews.importmap.cache_sweeper.execute_if_updated }
53
+ end
54
+ end
55
+
56
+ def self.locale_code_mapping
57
+ @locale_code_mapping ||= begin
58
+ mappings = Dir[root.join('config', 'locales', '*.yml')].map.to_h do |file|
59
+ code = File.basename(file, '.yml').to_sym
60
+ name = I18n.t('i18n.name', locale: code)
61
+ [code, name]
62
+ end
63
+ mappings.sort_by { |code, _name| code.to_s }.to_h
64
+ end
65
+ end
66
+
67
+ def self.available_locales
68
+ @available_locales ||= locale_code_mapping.keys.freeze
69
+ end
70
+
71
+ def self.default_locale = :en
72
+ def self.loaded_spec = Gem.loaded_specs['mat_views']
73
+ def self.project_name = loaded_spec&.name
74
+ def self.project_version = MatViews::VERSION
75
+ def self.project_homepage = loaded_spec&.homepage
76
+ def self.company_name = 'Codevedas Inc.'
77
+ def self.documentation_uri = loaded_spec&.metadata&.[]('documentation_uri')
78
+ def self.bug_tracker_uri = loaded_spec&.metadata&.[]('bug_tracker_uri')
79
+ def self.support_uri = loaded_spec&.metadata&.[]('support_uri')
80
+ def self.rubygems_uri = loaded_spec&.metadata&.[]('rubygems_uri')
33
81
  end
34
82
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright Codevedas Inc. 2025-present
4
+ #
5
+ # This source code is licensed under the MIT license found in the
6
+ # LICENSE file in the root directory of this source tree.
7
+
8
+ module MatViews
9
+ module Helpers
10
+ module UiTestIds
11
+ GEM_LINK = 'gem_link'
12
+ PROJECT_HOMEPAGE_LINK = 'project_homepage_link'
13
+ OPEN_ISSUE_LINK = 'open_issue_link'
14
+ DOCUMENTATION_LINK = 'documentation_link'
15
+ SUPPORT_LINK = 'support_link'
16
+
17
+ DRAWER_REFRESH_LINK = 'drawer_refresh_link'
18
+ DRAWER_CLOSE_LINK = 'drawer_close_link'
19
+
20
+ HEADER_LINK = 'header_link'
21
+ PREFERENCES_LINK = 'preferences_link'
22
+
23
+ DEFINITIONS_TAB_LINK = 'definitions_tab_link'
24
+ RUNS_TAB_LINK = 'runs_tab_link'
25
+
26
+ NEW_DEFINITION_LINK = 'new_definition_link'
27
+ SUBMIT_BUTTON = 'submit_button'
28
+ CANCEL_BUTTON = 'cancel_button'
29
+ VIEW_HISTORY_LINK = 'view_history_link'
30
+ VIEW_LINK = 'view_link'
31
+ EDIT_LINK = 'edit_link'
32
+ DELETE_LINK = 'delete_link'
33
+ REFRESH_LINK = 'refresh_link'
34
+ DROP_LINK = 'drop_link'
35
+ DROP_CASCADE_LINK = 'drop_cascade_link'
36
+ CREATE_MV_LINK = 'create_mv_link'
37
+ RESET_FILTERS_LINK = 'reset_filters_link'
38
+
39
+ PREFERENCES_SAVE_BUTTON = 'preferences_save_button'
40
+ PREFERENCES_CANCEL_BUTTON = 'preferences_cancel_button'
41
+ end
42
+ end
43
+ end
@@ -58,19 +58,22 @@ module MatViews
58
58
  # @raise [ArgumentError] if the configured adapter is not recognized.
59
59
  #
60
60
  def self.enqueue(job_class, queue:, args: [])
61
- case MatViews.configuration.job_adapter
61
+ queue_str = queue.to_s
62
+ job_adapter = MatViews.configuration.job_adapter
63
+
64
+ case job_adapter
62
65
  when :active_job
63
- job_class.set(queue: queue.to_s).perform_later(*args)
66
+ job_class.set(queue: queue_str).perform_later(*args)
64
67
  when :sidekiq
65
68
  Sidekiq::Client.push(
66
69
  'class' => job_class.name,
67
- 'queue' => queue.to_s,
70
+ 'queue' => queue_str,
68
71
  'args' => args
69
72
  )
70
73
  when :resque
71
- Resque.enqueue_to(queue.to_s, job_class, *args)
74
+ Resque.enqueue_to(queue_str, job_class, *args)
72
75
  else
73
- raise ArgumentError, "Unknown job adapter: #{MatViews.configuration.job_adapter.inspect}"
76
+ raise ArgumentError, "Unknown job adapter: #{job_adapter.inspect}"
74
77
  end
75
78
  end
76
79
  end
@@ -10,16 +10,19 @@ module MatViews
10
10
  # Encapsulates the result of a service operation within MatViews.
11
11
  #
12
12
  # Provides a consistent contract for all services by standardizing:
13
- # - `status`: Symbol representing outcome (`:ok`, `:created`, `:updated`, `:noop`,
13
+ # - `status`: Symbol representing outcome (`:ok`, `:created`, `:updated`,
14
14
  # `:skipped`, `:deleted`, `:error`)
15
- # - `payload`: Arbitrary structured data returned by the service
16
- # - `error`: Exception object or message if an error occurred
17
- # - `meta`: Additional metadata such as SQL statements, timing, or strategies
15
+ # - `request`: Request detailed that service was invoked with
16
+ # - `response`: Response detailed that service returned, nil with :error status
17
+ # - `error`: Exception or error message, with :error status
18
+ # - `message`: String description of the error
19
+ # - `class`: Exception class name
20
+ # - `backtrace`: Array of strings
18
21
  #
19
22
  # @example Successful response
20
23
  # MatViews::ServiceResponse.new(
21
24
  # status: :updated,
22
- # payload: { view: "public.users_mv" }
25
+ # response: { ... }
23
26
  # )
24
27
  #
25
28
  # @example Error response
@@ -29,32 +32,44 @@ module MatViews
29
32
  # )
30
33
  #
31
34
  class ServiceResponse
32
- attr_reader :status, :payload, :error, :meta
35
+ attr_reader :status, :request, :error, :response
36
+
37
+ # acceptable status values
38
+ ACCEPTABLE_STATES = %i[ok created updated skipped deleted error].freeze
39
+
40
+ # statuses indicating success
41
+ OK_STATES = %i[ok created updated skipped deleted].freeze
42
+
43
+ # statuses indicating error
44
+ ERROR_STATES = %i[error].freeze
33
45
 
34
46
  # @param status [Symbol] the outcome status
35
- # @param payload [Hash] optional data payload
47
+ # @param request [Hash] request details
48
+ # @param response [Hash] response details
36
49
  # @param error [Exception, String, nil] error details if applicable
37
- # @param meta [Hash] additional metadata
38
- def initialize(status:, payload: {}, error: nil, meta: {})
50
+ def initialize(status:, request: {}, response: {}, error: nil)
51
+ raise ArgumentError, 'status is required' unless ACCEPTABLE_STATES.include?(status&.to_sym)
52
+ raise ArgumentError, 'error must be an Exception object' if error && !error.is_a?(Exception)
53
+
39
54
  @status = status.to_sym
40
- @payload = payload
41
- @error = error
42
- @meta = meta
55
+ @request = request
56
+ @response = response
57
+ @error = error&.mv_serialize_error
43
58
  end
44
59
 
45
60
  # @return [Boolean] whether the response represents a success
46
61
  def success?
47
- !error? && %i[ok created updated noop skipped deleted].include?(status)
62
+ OK_STATES.include?(status)
48
63
  end
49
64
 
50
65
  # @return [Boolean] whether the response represents an error
51
66
  def error?
52
- !error.nil? || status == :error
67
+ ERROR_STATES.include?(status)
53
68
  end
54
69
 
55
70
  # @return [Hash] hash representation of the response
56
71
  def to_h
57
- { status:, payload:, error:, meta: }
72
+ { status:, request:, response:, error: }.compact
58
73
  end
59
74
  end
60
75
  end