Enhance our schema hover link to jump to the right table
Currently, our schema link just points to the file. However, we can link to the specific table for the model, which is a richer experience.
One possibility of how we can do this is by
- Start returning the table name from our middleware
- During the
activatecall parseschema.rband create a hash of table names to locations - When executing hover, search the hash for the right line to jump to
Concerns
- If there are modifications in schema.rb, we would need to rebuild the hash. For that, we need file watching to be merged in the Ruby LSP and then we have to design a way in which extensions can ask the Ruby LSP to notify them when certain files are changed.
- This won't work for
structure.sql. We probably don't want a SQL parser, but we might be able to achieve something with regexes
I've done some similar work in a personal project and I would love to help out with this issue
That's awesome! Can you describe the approach to discover the tables and keep track of their location in the schema file?
Thanks! Although in this project I'm not storing the locations of the tables, I'm using rubocop-ast to parse the source. This gives you access to the path and location in the file for every node.
So in the schema.rb the approach would be to:
- Look for
create_tablemethod calls (lets call it theddl_node) - Constantize its first argument using ActiveSupport's string helpers and get the model name
- Get children
line,column,last_line,last_columnfromddl_node.location
I have a Visitor::Schema that I borrowed from rubocop-rails's SchemaLoader that basically does that, except for the location storing part.
I hope this makes sense.
Yeah, that makes sense. Although we'd use Prism and not rubocop-ast here. So you can probably do something like
class SchemaCollector < Prism::Visitor
def initialize
@tables = {}
end
def visit_call_node(node)
# check if it's a `create_table` call
# add it to the hash of tables
end
end
Feel free to put a PR up and thanks for the interest!
@vinistock Is it fine if I use the SyntaxTree Visitor? since the gem is installed anyway and the code becomes simpler
require 'syntax_tree'
require 'active_support/inflector'
class SchemaCollector < SyntaxTree::Visitor
attr_reader :tables
def initialize
@tables = {}
end
def visit_command(node)
# check if it's a `create_table` call
# add it to the hash of tables
case node
in {
message: { value: 'create_table' },
arguments: { parts: [{ parts: [table_name_literal] }, *ignored_args] }
}
@tables[table_name_literal.value.classify] = node.location
else
# No matching pattern
end
end
end
raw_schema = File.read('schema.rb')
visitor = SchemaCollector.new
visitor.visit(SyntaxTree.parse(raw_schema))
since the gem is installed anyway and the code becomes simpler
We don't have a dependency on Syntax Tree so it wouldn't be available on every project - and the new official parser is Prism.
For this case, we have to use Prism. I should also mention that for language servers performance is always really critical, so please don't use pattern matching in the visitor.
We can probably do it with a few early returns. This is not the exact code, but something like this.
class SchemaCollector < Prism::Visitor
def visit_call_node(node)
return unless node.message == "create_table"
arguments = node.arguments&.arguments
return unless arguments
first_arg = arguments.first
return unless first_arg.is_a?(Prism::StringNode) # can't remember if it's a string or a symbol
@table[first_arg.content] = node.location
end
end