Skip to content

Commit

Permalink
Add support for checking for pending migrations and running them
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Nov 4, 2024
1 parent eb0c3a9 commit d9eb436
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 0 deletions.
23 changes: 23 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,29 @@ def delegate_notification(server_addon_name:, request_name:, **params)
)
end

sig { returns(T.nilable(String)) }
def pending_migrations_message
response = make_request("pending_migrations_message")
response[:pending_migrations_message] if response
rescue IncompleteMessageError
log_message(
"Ruby LSP Rails failed when checking for pending migrations",
type: RubyLsp::Constant::MessageType::ERROR,
)
nil
end

sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
def run_migrations
make_request("run_migrations")
rescue IncompleteMessageError
log_message(
"Ruby LSP Rails failed to run migrations",
type: RubyLsp::Constant::MessageType::ERROR,
)
nil
end

# Delegates a request to a server add-on
sig do
params(
Expand Down
25 changes: 25 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rails/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require "json"
require "open3"

# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe from the
# client, so it will become full and eventually hang or crash. Instead, return a response with an `error` key.
Expand Down Expand Up @@ -109,6 +110,10 @@ def execute(request, params)
send_message(resolve_database_info_from_model(params.fetch(:name)))
when "association_target_location"
send_message(resolve_association_target(params))
when "pending_migrations_message"
send_message({ result: { pending_migrations_message: pending_migrations_message } })
when "run_migrations"
send_message({ result: run_migrations })
when "reload"
::Rails.application.reloader.reload!
when "route_location"
Expand Down Expand Up @@ -252,6 +257,26 @@ def active_record_model?(const)
!const.abstract_class?
)
end

def pending_migrations_message
return unless defined?(ActiveRecord)

ActiveRecord::Migration.check_all_pending!
nil
rescue ActiveRecord::PendingMigrationError => e
e.message
end

def run_migrations
# Running migrations invokes `load` which will repeatedly load the same files. It's not designed to be invoked
# multiple times within the same process. To avoid any memory bloat, we run migrations in a separate process
stdout, status = Open3.capture2(
{ "VERBOSE" => "true" },
"bundle exec rails db:migrate",
)

{ message: stdout, status: status.exitstatus }
end
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions test/ruby_lsp_rails/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,28 @@ def resolve_route_info(requirements)
assert_equal("Hello\n", stderr)
end

test "checking for pending migrations" do
capture_subprocess_io do
system("bundle exec rails g migration CreateStudents name:string")
end

@server.execute("pending_migrations_message", {})
message = response.dig(:result, :pending_migrations_message)
assert_match("You have 1 pending migration", message)
assert_match(%r{db/migrate/[\d]+_create_students\.rb}, message)
ensure
FileUtils.rm_rf("db") if File.directory?("db")
end

test "running migrations happens in a child process" do
Open3.expects(:capture2)
.with({ "VERBOSE" => "true" }, "bundle exec rails db:migrate")
.returns(["Running migrations...", mock(exitstatus: 0)])

@server.execute("run_migrations", {})
assert_equal({ message: "Running migrations...", status: 0 }, response[:result])
end

private

def response
Expand Down

0 comments on commit d9eb436

Please sign in to comment.