Ecto.Query.API based solution
Here goes an example script that shows how you can filter by associations. It should be the most optimal way.
Mix.install([:ecto_sql, :postgrex]) defmodule Repo do use Ecto.Repo, adapter: Ecto.Adapters.Postgres, otp_app: :my_app end defmodule Migration do use Ecto.Migration def change do create table("users") do add(:name, :string) timestamps() end create table("projects") do add(:creator_id, references(:users)) add(:name, :string) timestamps() end create table("user_projects", primary_key: false) do add(:project_id, references(:projects)) add(:user_id, references(:users)) end end end defmodule User do use Ecto.Schema schema "users" do field(:name) timestamps() end end defmodule Project do use Ecto.Schema schema "projects" do belongs_to(:creator, User) field(:name, :string) many_to_many(:contributors, User, join_through: "user_projects") timestamps() end end defmodule Example do alias Ecto.Query require Query def cleanup do Repo.stop() end def prepare do Application.put_env(:my_app, Repo, database: "example", password: "postgres", pool_size: 10, show_sensitive_data_on_connection_error: true, username: "postgres" ) Application.ensure_all_started(:ecto_sql) Application.ensure_all_started(:ecto_sqlite3) Repo.__adapter__().storage_down(Repo.config()) Repo.__adapter__().storage_up(Repo.config()) Repo.start_link() Ecto.Migrator.up(Repo, 1, Migration) end def sample do Project |> Query.from(as: :project) # prevent duplicates if creator is also a contributor |> Query.distinct([project: project], project.name) # join assocs |> Query.join(:inner, [project: project], assoc(project, :creator), as: :creator) |> Query.join(:inner, [project: project], assoc(project, :contributors), as: :contributors) # filter projects based on its assocs |> Query.where( [contributors: contributors, creator: creator], contributors.name == "Foo" or creator.name == "Foo" ) # limit number of projects |> Query.limit(2) |> Repo.all() |> IO.inspect() end def seed do foo = Repo.insert!(%User{name: "Foo"}) bar = Repo.insert!(%User{name: "Bar"}) Repo.insert(%Project{contributors: [foo, bar], creator: foo, name: "both"}) Repo.insert(%Project{contributors: [bar], creator: foo, name: "creator"}) Repo.insert(%Project{contributors: [foo, bar], creator: bar, name: "contributor"}) Repo.insert(%Project{contributors: [bar], creator: bar, name: "none"}) end end Example.prepare() Example.seed() Example.sample() Example.cleanup()
Please keep in mind that not all databases support distinct
like it was used in code above, for example SQLite
. I send a PostgreSQL
based example as this is a default choice for an Elixir/Phoenix
database.
Enum based solution
If for some reason you can’t use ecto
’s query API
like mentioned by others already then my example may be interesting for you:
defmodule Example do def sample(list, func \\ &Function.identity/1, max \\ :infinity) def sample(list, func, :infinity) when is_list(list) and is_function(func, 1) do Enum.filter(list, func) end def sample(list, func, max) when is_list(list) and is_function(func, 1) and is_integer(max) and max > 0 do list |> Enum.reduce_while({0, []}, fn element, {count, acc} -> case {count + 1, func.(element)} do {_count, result} when result in [nil, false] -> {:cont, {count, acc}} {^max, _result} -> {:halt, {count, [element | acc]}} {count, _result} -> {:cont, {count, [element | acc]}} end end) |> then(fn {_count, acc} -> Enum.reverse(acc) end) end end
The code above is a bit big, but that’s because it’s really flexible. In short it’s a combination of Enum.filter/2
+ Enum.take/2
in one function. With it you can filter only first n
matching elements which is especially useful when working with big lists.