EctoSparkles (Bonfire v0.9.12-social-beta.68)
View SourceSome helpers to sparkle on top of Ecto
EctoSparkles.proload/3
andEctoSparkles.join_preload/2
to join and preload associations with less verbosityEctoSparkles.reusable_join/5
to avoid duplicating joinsEctoSparkles.Migrator
to run migrations, rollbacks, etc in a release andEctoSparkles.AutoMigrator
to automatically run them at startup.EctoSparkles.DataMigration
: a behaviour implemented for data migrations (generally backfills).EctoSparkles.Log
to log slow or possible N+1 queries with telemetry (showing stacktraces)EctoSparkles.Changesets.Errors
to generate readable errors for changesets
NOTE: you need to put something like config :ecto_sparkles, :otp_app, :your_otp_app_name
in your app's config.
proload
documentation
A macro which tells Ecto to perform a join and preload of associations.
By default, Ecto preloads associations using a separate query for each association, which can degrade performance.
You can make it run faster by using a combination of join/preload, but that requires a bit of boilerplate (see examples below).
Examples using standard Ecto
query
|> join(:left, [o, activity: activity], assoc(:object), as: :object)
|> preload([l, activity: activity, object: object], activity: {activity, [object: object]})
Ecto requires calling three different functions for this operation: Query.join/4
, Query.assoc/3
and Query.preload/2
.
Here's another example:
Invoice
|> join(:left, [i], assoc(i, :customer), as: :customer)
|> join(:left, [i], assoc(i, :lines), as: :lines)
|> preload([lines: v, customers: c], lines: v, customer: c)
Example using proload
With proload
, you can accomplish this with just one line of code:
proload(query, activity: [:object])
And for the other example:
proload(Invoice, [:customer, :lines])
As a bonus, it automatically makes use of reusable_join
so calling it multiple times with the same association has no ill effects.
Example using join_preload
join_preload
is proload
's sister macro with a slightly different syntax:
join_preload(query, [:activity, :object])
and:
Invoice
|> join_preload(:customer)
|> join_preload(:lines)
reusable_join
documentation
A macro similar to Ecto.Query.join/{4,5}
, but can be called multiple times
with the same alias.
Note that only the first join operation is performed, the subsequent ones that use the same alias are just ignored. Also note that because of this behaviour, its mandatory to specify an alias when using this function.
This is helpful when you need to perform a join while building queries one filter at a time,
because the same filter could be used multiple times or you could have multiple filters that
require the same join, which poses a problem with how the filter/3
callback work, as you
need to return a dynamic with the filtering, which means that the join must have an alias,
and by default Ecto raises an error when you add multiple joins with the same alias.
To solve this, it is recommended to use this macro instead of the default Ecto.Query.join/{4,5}
,
in which case there will be only one join in the query that can be reused by multiple filters.
Creating reusable joins
query
|> reusable_join(:left, [t1], t2 in "other_table", on: t1.id == t2.id, as: :other_a)
|> reusable_join(:left, [t1], t2 in "other_table", on: t1.id == t2.id, as: :other_b)
Copyright
Copyright (c) 2021 Bonfire developers
Copyright (c) 2020 Up Learn
Copyright (c) 2019 Joshua Nussbaum
join_preload
was originally forked from Ecto.Preloader, licensed under WTFPL)reusable_join
was originally forked from QueryElf, licensed under Apache License Version 2.0original code licensed under Apache License Version 2.0
Summary
Functions
Removes a specific named join from an Ecto query.
join_override
is similar to Ecto.Query.join/{4,5}
, but can be called multiple times with the same alias.
join_preload
is a helper for preloading associations using joins.
AKA join_preload++
. It's more powerful, but it does it with more (and different!) syntax.
Removes joins from an Ecto.Query that aren't referenced in other parts of the query.
reusable_join
is similar to Ecto.Query.join/{4,5}
, but can be called multiple times with the same alias.
Functions
Removes a specific named join from an Ecto query.
Parameters
query
: The Ecto query to modifybinding_name
: The named binding (atom) used in the join's:as
option
Examples
query = from p in Post,
join: c in assoc(p, :comments), as: :comments,
join: u in assoc(p, :user), as: :user
# Remove the comments join
drop_join(query, :comments)
Warning
As noted in the Ecto documentation, if a join is removed and its bindings were referenced elsewhere in the query (in where clauses, select statements, etc.), the bindings won't be removed, leading to a query that won't compile. Make sure to only remove join bindings that aren't used elsewhere.
join_override
is similar to Ecto.Query.join/{4,5}
, but can be called multiple times with the same alias.
Unlike reusable_join
, which skips subsequent joins with the same alias, join_override
will replace any existing join with the same alias with the new one.
This is useful when you need to join the same table multiple times with different conditions, while avoiding the "alias already exists" error from Ecto.
Note that because of this behaviour, it is mandatory to specify an alias when using this function.
Warning: this macro is a work-in-progress, and while the test suite passes it doesn't seem to produce valid queries, resulting in errors like (Postgrex.Error) ERROR 42P01 (undefined_table) missing FROM-clause entry for table "sb8"
when executed.
join_preload
is a helper for preloading associations using joins.
By default, Ecto preloads associations using a separate query for each association, which can degrade performance. You could make it run faster by using a combination of join/preload, but that requires a bit of boilerplate (see example below).
With EctoSparkles
, you can accomplish this with just one line of code.
Example using just Ecto
import Ecto.Query
Invoice
|> join(:left, [i], assoc(i, :customer), as: :customer)
|> join(:left, [i, c], assoc(c, :account), as: :account)
|> join(:left, [i], assoc(i, :lines), as: :lines)
|> preload([lines: v, customers: c, account: a], lines: v, customer: {c, [a: account]})
|> Repo.all()
Example using join_preload
import EctoSparkles
Invoice
|> join_preload([:customer, :account])
|> join_preload([:lines])
|> Repo.all()
AKA join_preload++
. It's more powerful, but it does it with more (and different!) syntax.
e.g.
proload(query, activity: [
:verb, :boost_count, :like_count, :replied,
# relations under object will have their aliases prefixed with object_, i.e.
# :object_message, :object_post, :object_post_content
# the original names will still be used for the associations.
object: {"object_", [:message, :post, :post_content]}
])
Removes joins from an Ecto.Query that aren't referenced in other parts of the query.
This function analyzes the query and removes any join whose binding is not used in select, where, order_by, group_by, having, limit, offset, or distinct clauses.
Parameters
- query: An Ecto.Query struct to optimize
Returns
- The optimized Ecto.Query with unused joins removed
Examples
iex> import Ecto.Query
iex> query = from u in User,
...> join: p in Post, on: p.user_id == u.id,
...> join: c in Comment, on: c.post_id == p.id,
...> where: p.published == true,
...> select: u
iex> remove_unused_joins(query)
#Ecto.Query<from u0 in User, join: p1 in Post, on: p1.user_id == u0.id, where: p1.published == true, select: u0>
reusable_join
is similar to Ecto.Query.join/{4,5}
, but can be called multiple times with the same alias.
Note that only the first join operation is performed, the subsequent ones that use the same alias are just ignored. Also note that because of this behaviour, it is mandatory to specify an alias when using this function.
This is helpful when you need to perform a join while building queries one filter at a time, because the same filter could be used multiple times or you could have multiple filters that require the same join, which poses a problem with how the filter/3
callback work, as you
need to return a dynamic with the filtering, which means that the join must have an alias, and by default Ecto raises an error when you add multiple joins with the same alias.
To solve this, it is recommended to use this macro instead of the default Ecto.Query.join/{4,5}
, in which case there will be only one join in the query that can be reused by multiple filters.