Skip to content

Commit 2267360

Browse files
authored
Check for pending migrations when watcher is fired (#504)
Check for pending migrations, notify the user and offer to run them. The idea is that if any modifications are made to the `db/migrate` directory, we check if there are pending migrations. If there are, we show a dialog and offers to run them. If the user decides to do it, we then fire a request to the server to run the migrations. Note: we only show the dialog if a migration is deleted or created, otherwise it can become really annoying when you are editing the migration you just created.
1 parent 2a3558b commit 2267360

File tree

3 files changed

+146
-6
lines changed

3 files changed

+146
-6
lines changed

lib/ruby_lsp/ruby_lsp_rails/addon.rb

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ module Rails
2020
class Addon < ::RubyLsp::Addon
2121
extend T::Sig
2222

23+
RUN_MIGRATIONS_TITLE = "Run Migrations"
24+
2325
sig { void }
2426
def initialize
2527
super
@@ -37,6 +39,7 @@ def initialize
3739
@addon_mutex.synchronize do
3840
# We need to ensure the Rails client is fully loaded before we activate the server addons
3941
@client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue)) }
42+
offer_to_run_pending_migrations
4043
end
4144
end
4245
end
@@ -119,11 +122,80 @@ def create_definition_listener(response_builder, uri, node_context, dispatcher)
119122

120123
sig { params(changes: T::Array[{ uri: String, type: Integer }]).void }
121124
def workspace_did_change_watched_files(changes)
122-
if changes.any? do |change|
123-
change[:uri].end_with?("db/schema.rb") || change[:uri].end_with?("structure.sql")
124-
end
125+
if changes.any? { |c| c[:uri].end_with?("db/schema.rb") || c[:uri].end_with?("structure.sql") }
125126
@rails_runner_client.trigger_reload
126127
end
128+
129+
if changes.any? do |c|
130+
%r{db/migrate/.*\.rb}.match?(c[:uri]) && c[:type] != Constant::FileChangeType::CHANGED
131+
end
132+
133+
offer_to_run_pending_migrations
134+
end
135+
end
136+
137+
sig { override.returns(String) }
138+
def name
139+
"Ruby LSP Rails"
140+
end
141+
142+
sig { override.params(title: String).void }
143+
def handle_window_show_message_response(title)
144+
if title == RUN_MIGRATIONS_TITLE
145+
146+
begin_progress("run-migrations", "Running Migrations")
147+
response = @rails_runner_client.run_migrations
148+
149+
if response && @outgoing_queue
150+
if response[:status] == 0
151+
# Both log the message and show it as part of progress because sometimes running migrations is so fast you
152+
# can't see the progress notification
153+
@outgoing_queue << Notification.window_log_message(response[:message])
154+
report_progress("run-migrations", message: response[:message])
155+
else
156+
@outgoing_queue << Notification.window_show_message(
157+
"Migrations failed to run\n\n#{response[:message]}",
158+
type: Constant::MessageType::ERROR,
159+
)
160+
end
161+
end
162+
163+
end_progress("run-migrations")
164+
end
165+
end
166+
167+
private
168+
169+
sig { params(id: String, title: String, percentage: T.nilable(Integer), message: T.nilable(String)).void }
170+
def begin_progress(id, title, percentage: nil, message: nil)
171+
return unless @global_state&.client_capabilities&.supports_progress && @outgoing_queue
172+
173+
@outgoing_queue << Request.new(
174+
id: "progress-request-#{id}",
175+
method: "window/workDoneProgress/create",
176+
params: Interface::WorkDoneProgressCreateParams.new(token: id),
177+
)
178+
179+
@outgoing_queue << Notification.progress_begin(
180+
id,
181+
title,
182+
percentage: percentage,
183+
message: "#{percentage}% completed",
184+
)
185+
end
186+
187+
sig { params(id: String, percentage: T.nilable(Integer), message: T.nilable(String)).void }
188+
def report_progress(id, percentage: nil, message: nil)
189+
return unless @global_state&.client_capabilities&.supports_progress && @outgoing_queue
190+
191+
@outgoing_queue << Notification.progress_report(id, percentage: percentage, message: message)
192+
end
193+
194+
sig { params(id: String).void }
195+
def end_progress(id)
196+
return unless @global_state&.client_capabilities&.supports_progress && @outgoing_queue
197+
198+
@outgoing_queue << Notification.progress_end(id)
127199
end
128200

129201
sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
@@ -152,9 +224,26 @@ def register_additional_file_watchers(global_state:, outgoing_queue:)
152224
)
153225
end
154226

155-
sig { override.returns(String) }
156-
def name
157-
"Ruby LSP Rails"
227+
sig { void }
228+
def offer_to_run_pending_migrations
229+
return unless @outgoing_queue
230+
return unless @global_state&.client_capabilities&.window_show_message_supports_extra_properties
231+
232+
migration_message = @rails_runner_client.pending_migrations_message
233+
return unless migration_message
234+
235+
@outgoing_queue << Request.new(
236+
id: "rails-pending-migrations",
237+
method: "window/showMessageRequest",
238+
params: {
239+
type: Constant::MessageType::INFO,
240+
message: migration_message,
241+
actions: [
242+
{ title: RUN_MIGRATIONS_TITLE, addon_name: name, method: "window/showMessageRequest" },
243+
{ title: "Cancel", addon_name: name, method: "window/showMessageRequest" },
244+
],
245+
},
246+
)
158247
end
159248
end
160249
end

test/ruby_lsp_rails/addon_test.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,49 @@ class AddonTest < ActiveSupport::TestCase
4949
addon = Addon.new
5050
addon.workspace_did_change_watched_files(changes)
5151
end
52+
53+
test "handling window show message response to run migrations" do
54+
RunnerClient.any_instance.expects(:run_migrations).once.returns({ message: "Ran migrations!", status: 0 })
55+
outgoing_queue = Thread::Queue.new
56+
global_state = GlobalState.new
57+
global_state.apply_options({ capabilities: { window: { workDoneProgress: true } } })
58+
59+
addon = Addon.new
60+
addon.activate(global_state, outgoing_queue)
61+
62+
# Wait until activation is done
63+
Thread.new do
64+
addon.rails_runner_client
65+
end.join
66+
67+
addon.handle_window_show_message_response("Run Migrations")
68+
69+
progress_request = pop_message(outgoing_queue) { |message| message.is_a?(Request) }
70+
assert_instance_of(Request, progress_request)
71+
72+
progress_begin = pop_message(outgoing_queue) do |message|
73+
message.is_a?(Notification) && message.method == "$/progress"
74+
end
75+
assert_equal("begin", progress_begin.params.value.kind)
76+
77+
report_log = pop_message(outgoing_queue) do |message|
78+
message.is_a?(Notification) && message.method == "window/logMessage"
79+
end
80+
assert_equal("Ran migrations!", report_log.params.message)
81+
82+
progress_report = pop_message(outgoing_queue) do |message|
83+
message.is_a?(Notification) && message.method == "$/progress"
84+
end
85+
assert_equal("report", progress_report.params.value.kind)
86+
assert_equal("Ran migrations!", progress_report.params.value.message)
87+
88+
progress_end = pop_message(outgoing_queue) do |message|
89+
message.is_a?(Notification) && message.method == "$/progress"
90+
end
91+
assert_equal("end", progress_end.params.value.kind)
92+
ensure
93+
T.must(outgoing_queue).close
94+
end
5295
end
5396
end
5497
end

test/test_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,13 @@ def pop_log_notification(message_queue, type)
5656
log = message_queue.pop until log.params.type == type
5757
log
5858
end
59+
60+
def pop_message(outgoing_queue, &block)
61+
message = outgoing_queue.pop
62+
return message if block.call(message)
63+
64+
message = outgoing_queue.pop until block.call(message)
65+
message
66+
end
5967
end
6068
end

0 commit comments

Comments
 (0)