总览
昨天,在使用RESTful API时 ,我遇到了一个棘手的问题。
如何根据URL中传递的参数过滤Ecto结果?
在这个迷你教程中,我们将构建一个可重用的模块来处理过滤。
情况
我们构建了一个API端点来获取所有用户的待办事项。 该todos
表有一个state
栏和每个待办事项项目可以有状态done
, doing
或pending
。
我们正在努力实现的目标
我们希望基于请求URL的参数过滤查询结果。 例如,当用户调用此端点时,我们仅应包括具有doing或`done`状态的待办事项:
https://
样板
通常,要获取所有用户的待办事项,我们在上下文文件中包含以下内容:
Todos上下文样板
我们在控制器中这样调用此函数:
Todos控制器样板
解
由于过滤器参数将在URL中传递,因此我们需要将params
变量传递给上下文的list_todos/2
函数。 我们需要这样修改控制器:
通过这样做,我们还需要在上下文中调整list_todos
函数的list_todos
。 现在,我们还将使用稍后将要构建的filter模块来准备上下文。
注意 :FilterEx是我们稍后将构建的模块的名称。
在第7行中 ,我们只是检查filter参数是否存在。 如果存在,则以列表形式返回过滤器参数 ,如果不存在,则仅返回一个空列表。
现在,我们将构建FilterEx模块来处理过滤。
构建FilterEx模块
在构建此模块时,我们将利用Ecto.Query.dynamic / 2 。 如果您想了解更多信息,请参阅官方文档 。 基本上, Ecto.Query.dynamic/2
让我们一点一点地构建查询表达式,并稍后在主查询中进行插值。
“…稍后在我们的主要查询中插值 ”
您可能想知道为什么我们不像这样在上下文中添加对FilterEx的调用: where([t], t.user_id == ^user.id and ^FilterEx.filter(states, :state))
不过有一个陷阱。
dynamic
可在的根被内插where
,having
或join
的on
。
这就是为什么我们在第一个where
子句之后添加了FilterEx调用,而不仅仅是对其进行插值的原因。 必须在子句的根部进行插值。
现在让我们定义模块并导入必要的Ecto模块。
defmodule FilterEx do
import Ecto.Query
# ...
end
FilterEx.filter/3
期望第一个参数为工作查询,第二个参数为过滤器列表,第三个参数为列名。
defmodule FilterEx do
import Ecto.Query
@spec filter(Ecto.Query.t, list, atom) :: Ecto.Query.t
def filter(query, [head | tail], fieldname) do
...
end
def filter(query, [], _), do: query
end
在filter/3
函数内部,我们将构建我们的初始动态查询,该查询将传递给filter_field/3
函数以进行进一步的动态查询构建。 然后,我们将dynamic_query
插入到主查询中,并将其返回到管道中。
...
def filter(query, [head | tail], fieldname) do
dynamic_query =
dynamic([q], field(q, ^fieldname) == ^head)
|> filter_field(tail, field_name)
query |> where(^dynamic_query)
end
...
我们将在filter_field/3
函数中使用递归方法。
...
def filter_field(dynamic, [head | tail], fieldname) do
dynamic([q], field(q, ^fieldname) == ^head or ^dynamic)
|> filter_field(tail, fieldname)
end
...
现在,我们已经完成了FilterEx模块的构建。
这是完成的版本:
感谢您的阅读。 如有任何疑问,您随时可以在Twitter @VinceUrag上与我联系
学到一些有价值的东西? 你总可以给我买杯咖啡 。 ❤
文斯拉格(Vince Urag)
vinceurag有11个可用的存储库。
在GitHub上遵循他们的代码。
github.com