docs - ecto elixir
Ámbitos de mezcla y asociaciones en Phoenix/Ecto (3)
En Rails, si tengo la siguiente configuración:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
def self.approved
where(approved: true)
end
end
Entonces puedo hacer algo como esto:
post = Post.find(100)
comments = post.comments.approved
para obtener rápidamente todos los comentarios aprobados para el Post
dado.
¿Cómo puedo hacer algo similar en ecto?
defmodule MyApp.Post do
use Ecto.Model
schema "posts" do
#columns omitted
has_many :comments, MyApp.Comment
end
end
defmodule MyApp.Comment do
use Ecto.Model
schema "comments" do
#columns omitted
belongs_to :post, MyApp.Post
end
end
Tengo el post
con comments
precargados:
post = MyApp.Post
|> MyApp.Repo.get(100)
|> MyApp.Repo.preload(:comments)
Ni siquiera estoy seguro de por dónde empezar con el alcance approved
en MyApp.Comment
.
Las precargas tienen permitido recibir consultas. Así que puedes filtrar comentarios asociados como este.
post =
MyApp.Post
|> Ecto.Query.preload(comments: ^MyApp.Comment.approved(MyApp.Comment))
|> MyApp.Repo.get(100)
Y en tu modelo de Comment
def approved(query) do
from c in query,
where: c.approved == true
end
Llego tarde a esta fiesta, pero tengo tiempo libre y me parece que una respuesta podría ayudar a las personas nuevas en Elixir.
Si viene de Ruby / Rails, una cosa que debe recordar es que los datos no tienen estado en Elixir / Erlang porque los valores son inmutables. Así que no tenemos una forma de manipular la publicación y cargar el comentario en la estructura de datos. Podemos lograr el mismo resultado final de dos maneras:
# 1 Devuelve una nueva estructura / mapa con comentarios fusionados en ella
post_with_comments = %{post | comments: comments} # or Map.put(post, :comments, comments)
donde comentarios es algo como:
comments = MyApp.Repo.get_by(MyApp.Comment, where: post_id == ^post.id)
.
# 2 Precargue los datos en la estructura de datos de la publicación mediante la creación de una consulta para capturarlos todos a la vez. Podemos hacer esto pasando las consultas a las consultas, ver más abajo.
defmodule MyApp.Post.Query do
def approved_with_comments(id) do
get_post(id) |> with_approval(true) |> with_comments()
end
def get_post(id) do
from p in MyApp.Post, where: p.id == ^id
end
def with_approval(query, approval) do
from q in query, where: approved == ^approval
end
def with_comments(query) do
from q in query, preload: [:comments]
end
end
Por lo general, siempre querrá precargar las asociaciones ya que es más eficiente para la base de datos. Personalmente, me encanta este comportamiento en Ecto porque te obliga a no dispararte en el pie con consultas de N + 1 o los hace muy obvios para ver.
Puede hacer que la interfaz sea un poco más ergonómica para algo como el módulo de Query
anterior utilizando el mismo nombre de función con la coincidencia de patrones:
def query(query, :by_id, id), do: from q in query, where: q.id == ^id
def query(query, :by_approval, approval), do: # ....
Luego, asignará la reducción de lo que sean sus parámetros a un único objeto de consulta que finalmente cargue con Repo.one
o lo que sea más adecuado para su Repo.one
.
No creo que sea posible con la versión actual de Ecto. La precarga no permite el filtrado. Alternativa es obtener los comentarios con una consulta:
(from comment in MyApp.Comment,
where: comment.post_id == ^post_id
and comment.approved == true,
select: comment) |> Repo.all