Skip to content

Commit 5169712

Browse files
author
Dmitry Aleksandrov(Russ)
committed
Merge pull request #11 from 0xAX/taggable-support
[ecto_migrate] Tags support
2 parents e5679e3 + 58dfad4 commit 5169712

File tree

4 files changed

+132
-51
lines changed

4 files changed

+132
-51
lines changed

README.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,34 @@ Ecto.Migration.Auto.migrate(Repo, Comment)
8484

8585
```
8686

87-
`ecto_migrate` also supports indexes
87+
`ecto_migrate` also provides additional `migrate/3` API. For example use it with [ecto_taggable](https://github.com/xerions/ecto_taggable). For example we have model:
88+
89+
```elixir
90+
defmodule Weather do # is for later at now
91+
use Ecto.Model
92+
93+
schema "weather" do
94+
field :city
95+
field :temp_lo, :integer
96+
field :temp_hi, :integer
97+
field :prcp, :float, default: 0.0
98+
has_many :weather_tags, {"weather_tags", Ecto.Taggable}, [foreign_key: :tag_id] # foreign_key `tag_id` is mandatory.
99+
end
100+
end
101+
```
102+
103+
Now we can migrate `weather_tag` table with:
104+
105+
```elixir
106+
Ecto.Migration.Auto.migrate(Repo, Ecto.Taggable, [for: Weather])
107+
```
108+
109+
It will generate and migrate `weather_tags` table to the database which will be associated with `weather` table.
110+
111+
Indexes
112+
--------------------
113+
114+
`ecto_migrate` has support of indexes:
88115

89116
```elixir
90117
defmodule Weather do # is for later at now

lib/ecto/migration/auto.ex

+58-47
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,52 @@ defmodule Ecto.Migration.Auto do
33
alias Ecto.Migration.Index
44
alias Ecto.Migration.SystemTable
55

6-
def migrate(repo, module) do
7-
table_name = module.__schema__(:source)
6+
def migrate(repo, module, opts \\ []) do
7+
{tag_field, tablename} = case opts do
8+
[] ->
9+
{:nothing, module.__schema__(:source)}
10+
[for: mod] ->
11+
[{field, _, tablename}] = get_associations(mod)
12+
{field, tablename}
13+
end
814
# already stored fields of the model in the system table
9-
all_system_fields = get_existings_fields(repo, table_name)
15+
all_system_fields = get_existings_fields(repo, tablename)
1016
# already stored indexes of the model in the system table
11-
all_system_indexes = get_existings_indexes(repo, table_name)
12-
if execute(module, all_system_fields, all_system_indexes, repo) do
13-
Ecto.Migrator.up(repo, random, extend_module_name(module, ".Migration"))
14-
end
17+
all_system_indexes = get_existings_indexes(repo, tablename)
18+
# execute migration
19+
if execute(tablename |> to_string, module, all_system_fields, all_system_indexes, tag_field, repo, opts) do
20+
Ecto.Migrator.up(repo, random, migration_module_name(module, opts))
21+
end
1522
end
1623

17-
defp execute(module, all_system_fields, all_system_indexes, repo) do
18-
metainfo = all_system_fields[:metainfo] |> transform_existing_keys()
24+
defp execute(tablename, module, all_system_fields, all_system_indexes, tag_field, repo, opts) do
1925
assocs = get_associations(module)
26+
metainfo = all_system_fields[:metainfo] |> transform_existing_keys()
2027
all_fields = module.__schema__(:fields)
21-
add_fields = add_fields(module, all_fields, metainfo, assocs)
28+
add_fields = [assoc_field(tag_field, tablename, all_system_fields, opts) | add_fields(module, all_fields, metainfo, assocs)] |> :lists.flatten
2229
remove_fields = remove_fields(all_fields, metainfo)
2330
all_indexes = Index.get_all(module)
2431
update_index = Index.updated?(all_indexes, all_system_indexes)
2532
all_changes = remove_fields ++ add_fields
26-
update_metainfo(module, all_fields, assocs, repo)
27-
do_execute(module, all_changes, metainfo, update_index, all_indexes, all_system_indexes, repo)
33+
update_metainfo(module, tablename, all_fields, assocs, repo)
34+
update_index_info(tablename, module, repo)
35+
index_info = {update_index, all_indexes, all_system_indexes}
36+
do_execute(repo, tablename, module, all_changes, metainfo, index_info, opts)
2837
end
2938

30-
defp do_execute(_module, [], _fields_in_db, false, _, _, _repo), do: nil
31-
32-
defp do_execute(module, all_changes, fields_in_db, update_index, all_indexes, all_system_indexes, repo) do
33-
table_name = table_name(module)
34-
module_name = extend_module_name(module, ".Migration")
35-
updsl = gen_up_dsl(repo, module, table_name, all_changes, fields_in_db, all_indexes, all_system_indexes, update_index)
39+
defp do_execute(_repo, _, _module, [], _fields_in_db, {false, _, _}, _), do: nil
40+
defp do_execute(repo, tablename, module, all_changes, fields_in_db, index_info, opts) do
41+
{update_index, all_indexes, all_system_indexes} = index_info
42+
updsl = gen_up_dsl(repo, module, tablename |> String.to_atom, all_changes, fields_in_db, all_indexes, all_system_indexes, update_index)
43+
migration_module_name = migration_module_name(module, opts)
3644
res = quote do
37-
defmodule unquote(module_name) do
45+
defmodule unquote(migration_module_name) do
3846
use Ecto.Migration
3947
def up do
4048
unquote(updsl)
4149
end
4250
def down do
43-
drop table(unquote table_name)
51+
drop table(unquote tablename)
4452
end
4553
end
4654
end
@@ -49,22 +57,22 @@ defmodule Ecto.Migration.Auto do
4957
end
5058

5159
# create new table
52-
defp gen_up_dsl(_repo, module, table_name, all_changes, [], all_indexes, _, update_index) do
60+
defp gen_up_dsl(_repo, module, tablename, all_changes, [], all_indexes, _, update_index) do
5361
key? = module.__schema__(:primary_key) == [:id]
54-
index_creation = Index.create(module, all_indexes, update_index)
62+
index_creation = Index.create(tablename, all_indexes, update_index)
5563
quote do
56-
create table(unquote(table_name), primary_key: unquote(key?)) do
64+
create table(unquote(tablename), primary_key: unquote(key?)) do
5765
unquote(all_changes)
5866
end
5967
unquote(index_creation)
6068
end
6169
end
6270

6371
# updated or table or index or both
64-
defp gen_up_dsl(repo, module, table_name, all_changes, _, all_indexes, all_system_indexes, update_index) do
65-
index_deletion = Index.delete(update_index, table_name |> Atom.to_string, module, repo, all_system_indexes)
66-
index_creation = Index.create(module, all_indexes, update_index)
67-
alter = alter_table(all_changes, table_name)
72+
defp gen_up_dsl(repo, module, tablename, all_changes, _, all_indexes, all_system_indexes, update_index) do
73+
index_deletion = Index.delete(update_index, tablename |> Atom.to_string, module, repo, all_system_indexes)
74+
index_creation = Index.create(tablename, all_indexes, update_index)
75+
alter = alter_table(all_changes, tablename)
6876
quote do
6977
unquote(alter)
7078
unquote(index_deletion)
@@ -81,25 +89,26 @@ defmodule Ecto.Migration.Auto do
8189
end
8290
end
8391

84-
defp update_metainfo(module, all_fields, assocs, repo) do
85-
table_name = module.__schema__(:source)
92+
defp update_metainfo(module, tablename, all_fields, assocs, repo) do
8693
metainfo = system_table_meta(module, all_fields, assocs)
87-
case repo.get(SystemTable, table_name) do
94+
case repo.get(SystemTable, tablename) do
8895
nil ->
89-
repo.insert(%SystemTable{tablename: table_name, metainfo: metainfo})
90-
table ->
96+
repo.insert(%SystemTable{tablename: tablename, metainfo: metainfo})
97+
table ->
9198
repo.update(%SystemTable{table | metainfo: metainfo})
9299
end
100+
end
93101

94-
query = from s in SystemTable.Index, where: s.tablename == ^table_name, select: s
95-
# insert index info if need
102+
defp update_index_info(tablename, module, repo) do
103+
query = from s in SystemTable.Index, where: s.tablename == ^tablename, select: s
104+
# insert index info into system table, if need
96105
case repo.all(query) do
97106
[] ->
98-
# we have no anything with 'table_name' record in the SystemTable.Index
107+
# we have no anything with 'tablename' record in the SystemTable.Index
99108
# table, let's insert records about it
100109
all_indexes = Index.get_all(module)
101110
for {fields, opts} <- all_indexes do
102-
repo.insert(Map.merge(%SystemTable.Index{tablename: table_name, index: Enum.join(fields, ",")}, :maps.from_list(opts)))
111+
repo.insert(Map.merge(%SystemTable.Index{tablename: tablename, index: Enum.join(fields, ",")}, :maps.from_list(opts)))
103112
end
104113
_ ->
105114
# we can't update index information here, because table is not empty and
@@ -108,19 +117,19 @@ defmodule Ecto.Migration.Auto do
108117
end
109118
end
110119

111-
def get_existings_indexes(repo, table_name) do
120+
def get_existings_indexes(repo, tablename) do
112121
try do
113-
repo.all(from s in SystemTable.Index, where: ^table_name == s.tablename)
122+
repo.all(from s in SystemTable.Index, where: ^tablename == s.tablename)
114123
catch
115124
_x, _y ->
116125
Ecto.Migrator.up(repo, random, SystemTable.Index.Migration) # we have no system table - 'ecto_migration_auto_index', let's create it
117126
nil
118127
end
119128
end
120129

121-
def get_existings_fields(repo, table_name) do
130+
def get_existings_fields(repo, tablename) do
122131
try do
123-
repo.get(SystemTable, table_name)
132+
repo.get(SystemTable, tablename)
124133
catch
125134
_x, _y ->
126135
Ecto.Migrator.up(repo, random, SystemTable.Migration) # we have no system table - 'ecto_migration_auto', let's create it
@@ -132,7 +141,9 @@ defmodule Ecto.Migration.Auto do
132141
module.__schema__(:associations) |> Enum.flat_map(fn(association) ->
133142
case module.__schema__(:association, association) do
134143
%Ecto.Association.BelongsTo{owner_key: field, assoc: assoc_module} ->
135-
[{field, table_name(assoc_module), assoc_module}]
144+
[{field, assoc_module.__schema__(:source) |> String.to_atom, assoc_module}]
145+
%Ecto.Association.Has{field: field, assoc: assoc, queryable: {tablename, _}} ->
146+
[{field, assoc, tablename}]
136147
_ ->
137148
[]
138149
end
@@ -203,11 +214,11 @@ defmodule Ecto.Migration.Auto do
203214

204215
defp random, do: :crypto.rand_uniform(0, 1099511627775)
205216

206-
defp extend_module_name(module, str) do
207-
((module |> to_string) <> str) |> String.to_atom
208-
end
217+
defp assoc_field(_, _, system_fields, _)
218+
when system_fields != nil, do: []
219+
defp assoc_field(_, _, _, []), do: []
220+
defp assoc_field(tag_field, tablename, _, _opts), do: quote do: (add unquote(tag_field), references(unquote(tablename) |> String.to_atom))
209221

210-
defp table_name(module) do
211-
module.__schema__(:source) |> String.to_atom
212-
end
222+
defp migration_module_name(module, []), do: ((module |> Atom.to_string) <> ".Migration") |> String.to_atom
223+
defp migration_module_name(module, [for: mod]), do: ((module |> Atom.to_string) <> "." <> (mod |> Atom.to_string) <> ".Migration") |> String.to_atom
213224
end

lib/ecto/migration/index.ex

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ defmodule Ecto.Migration.Index do
2222
end
2323
end
2424

25-
def create(module, all_indexes, true) when length(all_indexes) > 0 do
25+
def create(tablename, all_indexes, true) when length(all_indexes) > 0 do
2626
for {fields, opts} <- all_indexes do
27-
tablename = module.__schema__(:source) |> String.to_atom
2827
quote do
2928
create index(unquote(tablename), unquote(fields), unquote(opts))
3029
end

test/ecto_migrate_test.exs

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
defmodule TestModel do
22
use Ecto.Schema
33
use Ecto.Migration.Index
4+
45
index(:l, using: "hash")
56
index(:f)
67
schema "ecto_migrate_test_table" do
@@ -10,16 +11,59 @@ defmodule TestModel do
1011
end
1112
end
1213

14+
defmodule Ecto.Taggable do
15+
use Ecto.Model
16+
use Ecto.Migration.Index
17+
18+
index(:tag_id, using: "hash")
19+
schema "this is not a valid schema name and it will never be used" do
20+
field :name, :string
21+
field :model, :string
22+
field :tag_id, :integer
23+
end
24+
end
25+
26+
defmodule MyModel do
27+
use Ecto.Model
28+
schema "my_model" do
29+
field :a, :string
30+
field :b, :integer
31+
has_many :my_model_tags, {"my_model_tags", Ecto.Taggable}, [foreign_key: :tag_id]
32+
end
33+
end
34+
1335
defmodule EctoMigrateTest do
1436
use ExUnit.Case
1537
import Ecto.Query
16-
test "ecto_migrate test" do
38+
39+
test "ecto_migrate with tags test" do
1740
:ok = :application.start(:ecto_it)
41+
1842
Ecto.Migration.Auto.migrate(EctoIt.Repo, TestModel)
1943
query = from t in Ecto.Migration.SystemTable, select: t
2044
[result] = EctoIt.Repo.all(query)
2145
assert result.metainfo == "f:string,i:integer,l:boolean"
2246
assert result.tablename == "ecto_migrate_test_table"
47+
48+
Ecto.Migration.Auto.migrate(EctoIt.Repo, MyModel)
49+
Ecto.Migration.Auto.migrate(EctoIt.Repo, Ecto.Taggable, [for: MyModel])
50+
51+
EctoIt.Repo.insert(%MyModel{a: "foo"})
52+
EctoIt.Repo.insert(%MyModel{a: "bar"})
53+
54+
model = %MyModel{}
55+
new_tag = Ecto.Model.build(model, :my_model_tags)
56+
new_tag = %{new_tag | tag_id: 2, name: "test_tag", model: MyModel |> to_string}
57+
EctoIt.Repo.insert(new_tag)
58+
59+
query = from c in MyModel, where: c.id == 2, preload: [:my_model_tags]
60+
[result] = EctoIt.Repo.all(query)
61+
[tags] = result.my_model_tags
62+
63+
assert tags.id == 1
64+
assert tags.model == "Elixir.MyModel"
65+
assert tags.name == "test_tag"
66+
2367
:ok = :application.stop(:ecto_it)
2468
end
2569
end

0 commit comments

Comments
 (0)