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
@@ -9,7 +9,7 @@
9
9
  # Top-level namespace for the mat_views engine.
10
10
  module MatViews
11
11
  ##
12
- # ActiveJob that handles *creation* of PostgreSQL materialized views for a
12
+ # ActiveJob that handles *creation* of PostgreSQL materialised views for a
13
13
  # given {MatViews::MatViewDefinition}.
14
14
  #
15
15
  # The job:
@@ -17,7 +17,7 @@ module MatViews
17
17
  # 2. Looks up the target {MatViews::MatViewDefinition}.
18
18
  # 3. Starts a {MatViews::MatViewRun} row to track lifecycle/timing, with `operation: :create`.
19
19
  # 4. Executes {MatViews::Services::CreateView}.
20
- # 5. Finalizes the run with success/failure, duration, and payload meta.
20
+ # 5. Finalizes the run with success/failure, duration, and meta.
21
21
  #
22
22
  # @see MatViews::Services::CreateView
23
23
  # @see MatViews::MatViewDefinition
@@ -29,7 +29,7 @@ module MatViews
29
29
  # @example Inline run (test/dev)
30
30
  # MatViews::CreateViewJob.new.perform(definition.id, false)
31
31
  #
32
- class CreateViewJob < ::ActiveJob::Base
32
+ class CreateViewJob < ApplicationJob
33
33
  ##
34
34
  # Queue name for the job.
35
35
  #
@@ -40,37 +40,30 @@ module MatViews
40
40
  queue_as { MatViews.configuration.job_queue || :default }
41
41
 
42
42
  ##
43
- # Perform the job for the given materialized view definition.
43
+ # Perform the create job for the given materialised view definition.
44
44
  #
45
45
  # @api public
46
46
  #
47
- # @param definition_id [Integer, String] ID of {MatViews::MatViewDefinition}.
47
+ # @param mat_view_definition_id [Integer, String] ID of {MatViews::MatViewDefinition}.
48
48
  # @param force_arg [Boolean, Hash, nil] Optional flag or hash (`{ force: true }`)
49
- # to force creation (drop/recreate) when supported by the service.
49
+ # @param row_count_strategy_arg [:Symbol, String] One of: `:estimated`, `:exact`, `:none` or `nil`.
50
50
  #
51
- # @return [Hash] A serialized {MatViews::ServiceResponse#to_h}:
52
- # - `:status` [Symbol] one of `:ok, :created, :updated, :noop, :error`
53
- # - `:payload` [Hash] service-specific payload (also stored in run.meta)
54
- # - `:error` [String, nil] error message if any
55
- # - `:duration_ms` [Integer, nil]
51
+ # @return [Hash] Serialized {MatViews::ServiceResponse#to_h}:
52
+ # - `:status` [Symbol]
53
+ # - `:error` [String, nil]
54
+ # - `:duration_ms` [Integer]
56
55
  # - `:meta` [Hash]
57
56
  #
58
57
  # @raise [StandardError] Re-raised on unexpected failure after marking the run failed.
59
58
  #
60
- # @see MatViews::Services::CreateView
61
- #
62
- def perform(definition_id, force_arg = nil)
63
- force = normalize_force(force_arg)
64
-
65
- definition = MatViews::MatViewDefinition.find(definition_id)
66
- run = start_run(definition)
59
+ def perform(mat_view_definition_id, force_arg = nil, row_count_strategy_arg = nil)
60
+ definition = MatViews::MatViewDefinition.find(mat_view_definition_id)
67
61
 
68
- response, duration_ms = execute(definition, force: force)
69
- finalize_run!(run, response, duration_ms)
70
- response.to_h
71
- rescue StandardError => e
72
- fail_run!(run, e) if run
73
- raise e
62
+ record_run(definition, :create) do
63
+ MatViews::Services::CreateView.new(definition,
64
+ force: force?(force_arg),
65
+ row_count_strategy: normalize_strategy(row_count_strategy_arg)).call
66
+ end
74
67
  end
75
68
 
76
69
  private
@@ -82,107 +75,13 @@ module MatViews
82
75
  #
83
76
  # @api private
84
77
  #
85
- # @param arg [Object] Raw argument; commonly `true/false`, `nil`, or `Hash`.
78
+ # @param arg [Object] Raw argument; commonly `true/false`, `nil`
86
79
  # @return [Boolean] Coerced force flag.
87
80
  #
88
- def normalize_force(arg)
89
- case arg
90
- when Hash
91
- arg[:force] || arg['force'] || false
92
- else
93
- !!arg
94
- end
95
- end
81
+ def force?(arg)
82
+ return false if arg.nil?
96
83
 
97
- ##
98
- # Execute the create service and measure duration.
99
- #
100
- # @api private
101
- #
102
- # @param definition [MatViews::MatViewDefinition]
103
- # @param force [Boolean]
104
- # @return [Array(MatViews::ServiceResponse, Integer)] response and elapsed ms.
105
- #
106
- def execute(definition, force:)
107
- started = monotime
108
- response = MatViews::Services::CreateView.new(definition, force: force).run
109
- [response, elapsed_ms(started)]
84
+ !!arg
110
85
  end
111
-
112
- ##
113
- # Begin a {MatViews::MatViewRun} row for lifecycle tracking.
114
- #
115
- # @api private
116
- #
117
- # @param definition [MatViews::MatViewDefinition]
118
- # @return [MatViews::MatViewRun] newly created run with `status: :running`
119
- #
120
- def start_run(definition)
121
- MatViews::MatViewRun.create!(
122
- mat_view_definition: definition,
123
- status: :running,
124
- started_at: Time.current,
125
- operation: :create
126
- )
127
- end
128
-
129
- ##
130
- # Finalize the run with success/failure, timing, and meta from the response payload.
131
- #
132
- # @api private
133
- #
134
- # @param run [MatViews::MatViewRun]
135
- # @param response [MatViews::ServiceResponse]
136
- # @param duration_ms [Integer]
137
- # @return [void]
138
- #
139
- def finalize_run!(run, response, duration_ms)
140
- base_attrs = {
141
- finished_at: Time.current,
142
- duration_ms: duration_ms,
143
- meta: response.payload || {}
144
- }
145
-
146
- if response.success?
147
- run.update!(base_attrs.merge(status: :success, error: nil))
148
- else
149
- run.update!(base_attrs.merge(status: :failed, error: response.error.to_s.presence))
150
- end
151
- end
152
-
153
- ##
154
- # Mark the run failed due to an exception.
155
- #
156
- # @api private
157
- #
158
- # @param run [MatViews::MatViewRun]
159
- # @param exception [Exception]
160
- # @return [void]
161
- #
162
- def fail_run!(run, exception)
163
- run.update!(
164
- finished_at: Time.current,
165
- duration_ms: run.duration_ms || 0,
166
- error: "#{exception.class}: #{exception.message}",
167
- status: :failed
168
- )
169
- end
170
-
171
- ##
172
- # Monotonic clock getter (for elapsed-time measurement).
173
- #
174
- # @api private
175
- # @return [Float] seconds from a monotonic source.
176
- #
177
- def monotime = Process.clock_gettime(Process::CLOCK_MONOTONIC)
178
-
179
- ##
180
- # Convert a monotonic start time to elapsed milliseconds.
181
- #
182
- # @api private
183
- # @param start [Float] monotonic seconds.
184
- # @return [Integer] elapsed milliseconds.
185
- #
186
- def elapsed_ms(start) = ((monotime - start) * 1000).round
187
86
  end
188
87
  end
@@ -9,7 +9,7 @@
9
9
  # Top-level namespace for the mat_views engine.
10
10
  module MatViews
11
11
  ##
12
- # ActiveJob that handles *deletion* of PostgreSQL materialized views via
12
+ # ActiveJob that handles *deletion* of PostgreSQL materialised views via
13
13
  # {MatViews::Services::DeleteView}.
14
14
  #
15
15
  # This job mirrors {MatViews::CreateViewJob} and {MatViews::RefreshViewJob}:
@@ -25,7 +25,12 @@ module MatViews
25
25
  # @example Inline run (test/dev)
26
26
  # MatViews::DeleteViewJob.new.perform(definition.id, false)
27
27
  #
28
- class DeleteViewJob < ::ActiveJob::Base
28
+ class DeleteViewJob < ApplicationJob
29
+ ###
30
+ # cascade flag for the service call
31
+ # @return [Boolean]
32
+ attr_reader :cascade
33
+
29
34
  ##
30
35
  # Queue name for the job.
31
36
  #
@@ -34,66 +39,41 @@ module MatViews
34
39
  queue_as { MatViews.configuration.job_queue || :default }
35
40
 
36
41
  ##
37
- # Perform the job for the given materialized view definition.
42
+ # Perform the job for the given materialised view definition.
38
43
  #
39
44
  # @api public
40
45
  #
41
- # @param definition_id [Integer, String] ID of {MatViews::MatViewDefinition}.
46
+ # @param mat_view_definition_id [Integer, String] ID of {MatViews::MatViewDefinition}.
42
47
  # @param cascade_arg [Boolean, String, Integer, Hash, nil] Cascade option.
43
- # Accepts:
44
- # - `true/false`
45
- # - `1` (treated as true)
46
- # - `"true"`, `"1"`, `"yes"` (case-insensitive)
47
- # - `{ cascade: true }` or `{ "cascade" => true }`
48
+ # @param row_count_strategy_arg [:Symbol, String] One of: `:estimated`, `:exact`, `:none` or `nil`.
48
49
  #
49
- # @return [Hash] A serialized {MatViews::ServiceResponse#to_h}:
50
- # - `:status` [Symbol] `:success`, `:failed`, etc.
51
- # - `:payload` [Hash] response payload (also stored in run.meta)
50
+ # @return [Hash] Serialized {MatViews::ServiceResponse#to_h}:
51
+ # - `:status` [Symbol]
52
52
  # - `:error` [String, nil]
53
53
  # - `:duration_ms` [Integer]
54
54
  # - `:meta` [Hash]
55
55
  #
56
56
  # @raise [StandardError] Re-raised on unexpected failure after marking the run failed.
57
57
  #
58
- def perform(definition_id, cascade_arg = nil)
59
- cascade = normalize_cascade?(cascade_arg)
60
- definition = MatViews::MatViewDefinition.find(definition_id)
61
- run = start_run(definition)
62
-
63
- response, duration_ms = execute(definition, cascade: cascade)
64
- finalize_run!(run, response, duration_ms)
65
- response.to_h
66
- rescue StandardError => e
67
- fail_run!(run, e) if run
68
- raise e
58
+ def perform(mat_view_definition_id, cascade_arg = nil, row_count_strategy_arg = nil)
59
+ definition = MatViews::MatViewDefinition.find(mat_view_definition_id)
60
+ record_run(definition, :drop) do
61
+ MatViews::Services::DeleteView.new(definition,
62
+ cascade: cascade?(cascade_arg),
63
+ row_count_strategy: normalize_strategy(row_count_strategy_arg)).call
64
+ end
69
65
  end
70
66
 
71
67
  private
72
68
 
73
69
  ##
74
- # Normalize cascade argument into a boolean.
75
- #
76
- # @api private
77
- # @param arg [Object] Raw cascade argument.
78
- # @return [Boolean] Whether to cascade drop.
79
- #
80
- def normalize_cascade?(arg)
81
- value = if arg.is_a?(Hash)
82
- arg[:cascade] || arg['cascade']
83
- else
84
- arg
85
- end
86
- cascade_value_trueish?(value)
87
- end
88
-
89
- ##
90
- # Evaluate if a value is "truthy" for cascade purposes.
70
+ # Evaluate if a value is "truthy" for cascade.
91
71
  #
92
72
  # @api private
93
- # @param value [Object]
73
+ # @param value [TrueClass, FalseClass, String, Integer, nil, Object]
94
74
  # @return [Boolean]
95
75
  #
96
- def cascade_value_trueish?(value)
76
+ def cascade?(value)
97
77
  case value
98
78
  when true
99
79
  true
@@ -105,92 +85,5 @@ module MatViews
105
85
  false
106
86
  end
107
87
  end
108
-
109
- ##
110
- # Execute the delete service and measure duration.
111
- #
112
- # @api private
113
- # @param definition [MatViews::MatViewDefinition]
114
- # @param cascade [Boolean]
115
- # @return [Array(MatViews::ServiceResponse, Integer)]
116
- #
117
- def execute(definition, cascade:)
118
- started = monotime
119
- response = MatViews::Services::DeleteView.new(definition, cascade: cascade, if_exists: true).run
120
- [response, elapsed_ms(started)]
121
- end
122
-
123
- ##
124
- # Begin a {MatViews::MatViewRun} row for lifecycle tracking.
125
- #
126
- # @api private
127
- # @param definition [MatViews::MatViewDefinition]
128
- # @return [MatViews::MatViewRun]
129
- #
130
- def start_run(definition)
131
- MatViews::MatViewRun.create!(
132
- mat_view_definition: definition,
133
- status: :running,
134
- started_at: Time.current,
135
- operation: :drop
136
- )
137
- end
138
-
139
- ##
140
- # Finalize the run with success/failure, timing, and meta from the response.
141
- #
142
- # @api private
143
- # @param run [MatViews::MatViewRun]
144
- # @param response [MatViews::ServiceResponse]
145
- # @param duration_ms [Integer]
146
- # @return [void]
147
- #
148
- def finalize_run!(run, response, duration_ms)
149
- base_attrs = {
150
- finished_at: Time.current,
151
- duration_ms: duration_ms,
152
- meta: response.payload || {}
153
- }
154
-
155
- if response.success?
156
- run.update!(base_attrs.merge(status: :success, error: nil))
157
- else
158
- run.update!(base_attrs.merge(status: :failed, error: response.error.to_s.presence))
159
- end
160
- end
161
-
162
- ##
163
- # Mark the run failed due to an exception.
164
- #
165
- # @api private
166
- # @param run [MatViews::MatViewRun]
167
- # @param exception [Exception]
168
- # @return [void]
169
- #
170
- def fail_run!(run, exception)
171
- run.update!(
172
- finished_at: Time.current,
173
- duration_ms: run.duration_ms || 0,
174
- error: "#{exception.class}: #{exception.message}",
175
- status: :failed
176
- )
177
- end
178
-
179
- ##
180
- # Monotonic clock getter (for elapsed-time measurement).
181
- #
182
- # @api private
183
- # @return [Float] seconds
184
- #
185
- def monotime = Process.clock_gettime(Process::CLOCK_MONOTONIC)
186
-
187
- ##
188
- # Convert monotonic start time to elapsed milliseconds.
189
- #
190
- # @api private
191
- # @param start [Float]
192
- # @return [Integer] elapsed ms
193
- #
194
- def elapsed_ms(start) = ((monotime - start) * 1000).round
195
88
  end
196
89
  end
@@ -23,9 +23,9 @@ module MatViews
23
23
  # - otherwise → {MatViews::Services::RegularRefresh}
24
24
  #
25
25
  # Row count reporting can be controlled via `row_count_strategy`:
26
- # - `:estimated` (default) fast, approximate via reltuples
27
- # - `:exact` accurate `COUNT(*)`
28
- # - `nil` skip counting
26
+ # - `:estimated` (default) - fast, approximate via reltuples
27
+ # - `:exact` - accurate `COUNT(*)`
28
+ # - `nil` - skip counting
29
29
  #
30
30
  # @see MatViews::MatViewDefinition
31
31
  # @see MatViews::MatViewRun
@@ -39,7 +39,7 @@ module MatViews
39
39
  # @example Enqueue using keyword-hash form
40
40
  # MatViews::RefreshViewJob.perform_later(definition.id, row_count_strategy: :estimated)
41
41
  #
42
- class RefreshViewJob < ::ActiveJob::Base
42
+ class RefreshViewJob < ApplicationJob
43
43
  ##
44
44
  # Queue name for the job.
45
45
  #
@@ -48,74 +48,29 @@ module MatViews
48
48
  queue_as { MatViews.configuration.job_queue || :default }
49
49
 
50
50
  ##
51
- # Perform the job for the given materialized view definition.
52
- #
53
- # Accepts either a symbol/string (`:estimated`, `:exact`) or a hash
54
- # (`{ row_count_strategy: :exact }`) for `strategy_arg`.
51
+ # Perform the refresh job for the given materialised view definition.
55
52
  #
56
53
  # @api public
57
54
  #
58
- # @param definition_id [Integer, String] ID of {MatViews::MatViewDefinition}.
59
- # @param strategy_arg [Symbol, String, Hash, nil] Row count strategy override.
60
- # When a Hash, looks for `:row_count_strategy` / `"row_count_strategy"`.
55
+ # @param mat_view_definition_id [Integer, String] ID of {MatViews::MatViewDefinition}.
56
+ # @param row_count_strategy_arg [:Symbol, String] One of: `:estimated`, `:exact`, `:none` or `nil`.
61
57
  #
62
58
  # @return [Hash] Serialized {MatViews::ServiceResponse#to_h}:
63
59
  # - `:status` [Symbol]
64
- # - `:payload` [Hash]
65
60
  # - `:error` [String, nil]
66
61
  # - `:duration_ms` [Integer]
67
62
  # - `:meta` [Hash]
68
63
  #
69
64
  # @raise [StandardError] Re-raised on unexpected failure after marking the run failed.
70
65
  #
71
- def perform(definition_id, strategy_arg = nil)
72
- row_count_strategy = normalize_strategy(strategy_arg)
73
- definition = MatViews::MatViewDefinition.find(definition_id)
74
- run = start_run(definition)
75
-
76
- response, duration_ms = execute(definition, row_count_strategy: row_count_strategy)
77
- finalize_run!(run, response, duration_ms)
78
- response.to_h
79
- rescue StandardError => e
80
- fail_run!(run, e) if run
81
- raise e
82
- end
83
-
84
- private
85
-
86
- ##
87
- # Normalize the strategy argument into a symbol or default.
88
- #
89
- # @api private
90
- #
91
- # @param arg [Symbol, String, Hash, nil]
92
- # @return [Symbol] One of `:estimated`, `:exact`, or `:estimated` by default.
93
- #
94
- def normalize_strategy(arg)
95
- case arg
96
- when Hash
97
- (arg[:row_count_strategy] || arg['row_count_strategy'] || :estimated).to_sym
98
- when String, Symbol
99
- arg.to_sym
100
- else
101
- :estimated
66
+ def perform(mat_view_definition_id, row_count_strategy_arg = nil)
67
+ definition = MatViews::MatViewDefinition.find(mat_view_definition_id)
68
+ record_run(definition, :refresh) do
69
+ service(definition).new(definition, row_count_strategy: normalize_strategy(row_count_strategy_arg)).call
102
70
  end
103
71
  end
104
72
 
105
- ##
106
- # Execute the appropriate refresh service and measure duration.
107
- #
108
- # @api private
109
- #
110
- # @param definition [MatViews::MatViewDefinition]
111
- # @param row_count_strategy [Symbol, nil]
112
- # @return [Array(MatViews::ServiceResponse, Integer)] response and elapsed ms.
113
- #
114
- def execute(definition, row_count_strategy:)
115
- started = monotime
116
- response = service(definition).new(definition, row_count_strategy: row_count_strategy).run
117
- [response, elapsed_ms(started)]
118
- end
73
+ private
119
74
 
120
75
  ##
121
76
  # Select the refresh service class based on the definition's strategy.
@@ -135,81 +90,5 @@ module MatViews
135
90
  MatViews::Services::RegularRefresh
136
91
  end
137
92
  end
138
-
139
- ##
140
- # Begin a {MatViews::MatViewRun} row for lifecycle tracking.
141
- #
142
- # @api private
143
- #
144
- # @param definition [MatViews::MatViewDefinition]
145
- # @return [MatViews::MatViewRun]
146
- #
147
- def start_run(definition)
148
- MatViews::MatViewRun.create!(
149
- mat_view_definition: definition,
150
- status: :running,
151
- started_at: Time.current,
152
- operation: :refresh
153
- )
154
- end
155
-
156
- ##
157
- # Finalize the run with success/failure, timing, and meta from the response.
158
- #
159
- # @api private
160
- #
161
- # @param run [MatViews::MatViewRun]
162
- # @param response [MatViews::ServiceResponse]
163
- # @param duration_ms [Integer]
164
- # @return [void]
165
- #
166
- def finalize_run!(run, response, duration_ms)
167
- base_attrs = {
168
- finished_at: Time.current,
169
- duration_ms: duration_ms,
170
- meta: response.payload || {}
171
- }
172
-
173
- if response.success?
174
- run.update!(base_attrs.merge(status: :success, error: nil))
175
- else
176
- run.update!(base_attrs.merge(status: :failed, error: response.error.to_s.presence))
177
- end
178
- end
179
-
180
- ##
181
- # Mark the run failed due to an exception.
182
- #
183
- # @api private
184
- #
185
- # @param run [MatViews::MatViewRun]
186
- # @param exception [Exception]
187
- # @return [void]
188
- #
189
- def fail_run!(run, exception)
190
- run.update!(
191
- finished_at: Time.current,
192
- duration_ms: run.duration_ms || 0,
193
- error: "#{exception.class}: #{exception.message}",
194
- status: :failed
195
- )
196
- end
197
-
198
- ##
199
- # Monotonic clock getter (for elapsed-time measurement).
200
- #
201
- # @api private
202
- # @return [Float] seconds
203
- #
204
- def monotime = Process.clock_gettime(Process::CLOCK_MONOTONIC)
205
-
206
- ##
207
- # Convert monotonic start time to elapsed milliseconds.
208
- #
209
- # @api private
210
- # @param start [Float]
211
- # @return [Integer] elapsed ms
212
- #
213
- def elapsed_ms(start) = ((monotime - start) * 1000).round
214
93
  end
215
94
  end