Skip to content

Commit d58efec

Browse files
haulethjbodah
authored andcommitted
vim-dadbod integration for applications using Ecto (#481)
* feat: vim-dadbod integration for applications using Ecto [vim-dadbod][1] is useful library for interacting with databases, this PR adds support for interacting with applications that are using Ecto with `Ecto.Adapter.Postgres` and `Ecto.Adapter.MySQL`, thanks to that user can run: :DB ecto:MyApp.Repo SELECT * FROM foo And it will open new preview window containing result of the query or even one can use :DB ecto:MyApp.Repo To open interactive shell for DB assigned to given repository. [1]: https://github.com/tpope/vim-dadbod * docs: include informations about integration in README
1 parent 9724749 commit d58efec

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Features:
1111
* Syntax highlighting for Elixir and EEx files
1212
* Filetype detection for `.ex`, `.exs` and `.eex` files
1313
* Automatic indentation
14+
* Integration between Ecto projects and [vim-dadbod][] for running SQL queries
15+
on defined Ecto repositories
1416

1517
## Installation
1618

@@ -54,3 +56,5 @@ We've decided not to include `mix format` integration into `vim-elixir`. If you'
5456

5557
Run the tests: `bundle exec parallel_rspec spec`
5658
Spawn a vim instance with dev configs: `bin/spawn_vim`
59+
60+
[vim-dadbod]: https://github.com/tpope/vim-dadbod

autoload/db/adapter/ecto.vim

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
let s:path = expand('<sfile>:h')
2+
let s:cmd = join(['mix', 'run', '--no-start', '--no-compile', shellescape(s:path.'/get_repos.exs')])
3+
4+
function! s:repo_list() abort
5+
return map(systemlist(s:cmd), 'split(v:val)')
6+
endfunction
7+
8+
function! db#adapter#ecto#canonicalize(url) abort
9+
for l:item in s:repo_list()
10+
let l:name = get(l:item, 0)
11+
let l:url = get(l:item, 1)
12+
if !empty(l:name) && 'ecto:'.l:name ==# a:url
13+
return l:url
14+
endif
15+
endfor
16+
endfunction
17+
18+
function! db#adapter#ecto#complete_opaque(url) abort
19+
return map(s:repo_list(), 'v:val[0]')
20+
endfunction

autoload/db/adapter/get_repos.exs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
defmodule LoadRepos do
2+
defp load_apps do
3+
:code.get_path()
4+
|> Enum.flat_map(fn app_dir ->
5+
Path.join(app_dir, "*.app") |> Path.wildcard()
6+
end)
7+
|> Enum.map(fn app_file ->
8+
app_file |> Path.basename() |> Path.rootname(".app") |> String.to_atom()
9+
end)
10+
|> Enum.map(&Application.load/1)
11+
end
12+
13+
defp configs do
14+
for {app, _, _} <- Application.loaded_applications(),
15+
repos = Application.get_env(app, :ecto_repos),
16+
is_list(repos) and repos != [],
17+
repo <- repos,
18+
do: {repo, Map.new(Application.get_env(app, repo))}
19+
end
20+
21+
defp config_to_url(_, %{url: url}), do: url
22+
23+
defp config_to_url(repo, %{
24+
hostname: hostname,
25+
username: username,
26+
password: password,
27+
database: database
28+
}) do
29+
%URI{
30+
scheme: adapter_to_string(repo.__adapter__),
31+
userinfo: "#{username}:#{password}",
32+
host: hostname,
33+
path: Path.join("/", database)
34+
}
35+
|> URI.to_string()
36+
end
37+
38+
defp adapter_to_string(Ecto.Adapters.Postgres), do: "postgres"
39+
defp adapter_to_string(Ecto.Adapters.MySQL), do: "mysql"
40+
defp adapter_to_string(mod), do: raise("Unknown adapter #{inspect(mod)}")
41+
42+
def main do
43+
load_apps()
44+
45+
configs()
46+
|> Enum.map(fn {repo, config} ->
47+
[inspect(repo), ?\s, config_to_url(repo, config)]
48+
end)
49+
|> Enum.intersperse(?\n)
50+
|> IO.puts()
51+
end
52+
end
53+
54+
LoadRepos.main()

0 commit comments

Comments
 (0)