Skip to content

Commit

Permalink
Validate translated conditions in compile time
Browse files Browse the repository at this point in the history
The macro `Trans.QueryBuilder.translated/3` now validates the translated
fields before the macro expansion step. We will receive an error in
compilation time when adding conditions on untranslatable fields.
  • Loading branch information
crbelaus committed Apr 2, 2017
1 parent c1c333f commit cbb6ba1
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
25 changes: 20 additions & 5 deletions lib/trans/query_builder.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
if Code.ensure_loaded?(Ecto.Query) do
defmodule Trans.QueryBuilder do

defmacro translated(translatable, opts) do
generate_query(schema(translatable), field(translatable), locale(opts))
defmacro translated(module, translatable, opts) do
with field <- field(translatable) do
Module.eval_quoted __CALLER__, [
__validate_fields__(module, field)
]
generate_query(schema(translatable), field, locale(opts))
end
end

defp generate_query(schema, nil, locale) do
Expand All @@ -21,7 +26,7 @@ if Code.ensure_loaded?(Ecto.Query) do
case Keyword.fetch(opts, :locale) do
{:ok, locale} when is_atom(locale) -> to_string(locale)
{:ok, locale} when is_binary(locale) -> locale
_ -> error_unspecified_locale()
_ -> raise ArgumentError, mesage: "You must specify a locale for the query. For example `translated(x.field, locale: :en)`."
end
end

Expand All @@ -31,8 +36,18 @@ if Code.ensure_loaded?(Ecto.Query) do
defp field({{:., _, [_schema, field]}, _metadata, _args}), do: to_string(field)
defp field(_), do: nil

defp error_unspecified_locale do
raise ArgumentError, mesage: "You must specify a locale for the query. For example `translated(x.field, locale: :en)`."
@doc false
def __validate_fields__(module, field) do
quote do
with field <- unquote(field) do
cond do
is_nil(field) -> nil
not Trans.translatable?(unquote(module), unquote(field)) ->
raise ArgumentError, message: "'#{inspect(unquote(module))}' module must declare '#{inspect(unquote(field))}' as translatable"
true -> nil
end
end
end
end

end
Expand Down
27 changes: 20 additions & 7 deletions test/query_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,31 @@ defmodule QueryBuilderTest do

test "should find only one article translated to ES" do
count = Repo.one(from a in Article,
where: not is_nil(translated(a, locale: :es)),
where: not is_nil(translated(Article, a, locale: :es)),
select: count(a.id)
)
assert count == 1
end

test "should not find any article translated to DE" do
count = Repo.one(from a in Article,
where: not is_nil(translated(a, locale: :de)),
where: not is_nil(translated(Article, a, locale: :de)),
select: count(a.id))
assert count == 0
end

test "should find an article by its FR title",
%{translated_article: article} do
matches = Repo.all(from a in Article,
where: translated(a.title, locale: :fr) == ^article.translations["fr"]["title"])
where: translated(Article, a.title, locale: :fr) == ^article.translations["fr"]["title"])
assert Enum.count(matches) == 1
assert hd(matches).id == article.id
end

test "should not find an article by a non existant translation" do
count = Repo.one(from a in Article,
select: count(a.id),
where: translated(a.title, locale: :es) == "FAKE TITLE")
where: translated(Article, a.title, locale: :es) == "FAKE TITLE")
assert count == 0
end

Expand All @@ -54,7 +54,7 @@ defmodule QueryBuilderTest do
|> Enum.join(" ")
|> Kernel.<>("%")
matches = Repo.all(from a in Article,
where: ilike(translated(a.body, locale: :es), ^first_words))
where: ilike(translated(Article, a.body, locale: :es), ^first_words))
assert Enum.count(matches) == 1
assert hd(matches).id == article.id
end
Expand All @@ -70,7 +70,7 @@ defmodule QueryBuilderTest do
|> Kernel.<>("%")
count = Repo.one(from a in Article,
select: count(a.id),
where: like(translated(a.body, locale: :fr), ^first_words))
where: like(translated(Article, a.body, locale: :fr), ^first_words))
assert count == 0
end

Expand All @@ -84,8 +84,21 @@ defmodule QueryBuilderTest do
|> String.upcase
|> Kernel.<>("%")
matches = Repo.all(from a in Article,
where: ilike(translated(a.body, locale: :fr), ^first_words))
where: ilike(translated(Article, a.body, locale: :fr), ^first_words))
assert Enum.count(matches) == 1
assert hd(matches).id == article.id
end

test "should raise when adding conditions to an untranslatable field" do
# Since the QueryBuilder errors are emitted during compilation, we do a
# little trick to delay the compilation of the query until the test
# is running, so we can catch the raised error.
query = quote do
Repo.all(from a in Article,
where: not is_nil(translated(Article, a.translations, locale: :es)))
end
assert_raise ArgumentError, fn ->
Module.eval_quoted __ENV__, query
end
end
end

0 comments on commit cbb6ba1

Please sign in to comment.