Full text search on a given field in ash expression?

I have a table, which emits the map like %{question_text: "filter keyword that should exist"} I am composing queries like
filter_expr = Ash.Expr.expr(contains(^key , ^value))
Question
|> Ash.Query.sort(sort)
|> Ash.Query.filter(filter_expr)
|> Ash.read!(
filter_expr = Ash.Expr.expr(contains(^key , ^value))
Question
|> Ash.Query.sort(sort)
|> Ash.Query.filter(filter_expr)
|> Ash.read!(
Here are other things I have tried.
filter_expr = Ash.Expr.expr(contains(^key , ^value))
filter_expr = Ash.Expr.expr(fragment("? ILIKE ?", ^key, ^"%#{value}%"))
filter_expr = Ash.Expr.expr(contains(^key , ^value))
filter_expr = Ash.Expr.expr(fragment("? ILIKE ?", ^key, ^"%#{value}%"))
I need a simple ilike style stuff for now.
5 Replies
ZachDaniel
ZachDaniel4d ago
AshPostgres resources get an ilike function out of the box Ash.Query.filter(ilike(^ref(key), ^"%#{value}%")) for example
abeeshake456
abeeshake456OP4d ago
GitHub
ash_authentication/test/support/example_multi_tenant/only_marties_a...
The Ash Authentication framework. Contribute to team-alembic/ash_authentication development by creating an account on GitHub.
abeeshake456
abeeshake456OP4d ago
Just did github code search. one more question -- Can ilike be used outside filter? like, I want to compose Ash.Expr and then pass all of them in a filter at once.
should_add_filter_expr: %{q_text: "r"} <<<<<< This is from IO.inspect

[error] GenServer #PID<0.2156.0> terminating
** (FunctionClauseError) no function clause matching in Ash.Expr.ref/1
(ash 3.5.12) lib/ash/expr/expr.ex:94: Ash.Expr.ref("q_text")
should_add_filter_expr: %{q_text: "r"} <<<<<< This is from IO.inspect

[error] GenServer #PID<0.2156.0> terminating
** (FunctionClauseError) no function clause matching in Ash.Expr.ref/1
(ash 3.5.12) lib/ash/expr/expr.ex:94: Ash.Expr.ref("q_text")
Code related to this
def should_add_filter_expr(query, filter) do

IO.inspect(filter, label: "should_add_filter_expr")
# check if map is not empty
if map_size(filter) > 0 do
key = Map.keys(filter) |> hd() |> to_string()

value = Map.values(filter) |> hd()
if String.trim(value) == "" do
query
else
Ash.Query.filter(query, ilike(^ref(key), "#{value}"))
end
else
query
end

end
def should_add_filter_expr(query, filter) do

IO.inspect(filter, label: "should_add_filter_expr")
# check if map is not empty
if map_size(filter) > 0 do
key = Map.keys(filter) |> hd() |> to_string()

value = Map.values(filter) |> hd()
if String.trim(value) == "" do
query
else
Ash.Query.filter(query, ilike(^ref(key), "#{value}"))
end
else
query
end

end
if I use the key as atom, then the error is
[error] GenServer #PID<0.3962.0> terminating
** (Ash.Error.Unknown)
Unknown Error

* Invalid reference value
at filter
(ash 3.5.12) /home/......./deps/splode/lib/splode.ex:322: Ash.Error.to_error/2
[error] GenServer #PID<0.3962.0> terminating
** (Ash.Error.Unknown)
Unknown Error

* Invalid reference value
at filter
(ash 3.5.12) /home/......./deps/splode/lib/splode.ex:322: Ash.Error.to_error/2
Ah. I had to pin the value too!
mylanconnolly
mylanconnolly3d ago
when I'm building references, I often use a helper function, something like:
defp parse_field(field) do
case field do
{path, field} -> ref(path, field)
field -> ref(field)
end
end
defp parse_field(field) do
case field do
{path, field} -> ref(path, field)
field -> ref(field)
end
end
this way you can use a field that references a relationship or a field that belongs on the resource itself and have a single way to parse it out. in the example above, path would be a list of atoms that points to the target resource, so it could be something like {[:author], :email}; when done that way, you'll just need to pin the result of parse_field/1. something like:
field = parse_field(:something)
Ash.Query.filter(query, ilike(^field, ^"%#{value}%"))
field = parse_field(:something)
Ash.Query.filter(query, ilike(^field, ^"%#{value}%"))
abeeshake456
abeeshake456OP3d ago
Super useful.

Did you find this page helpful?