비약: Trabalhando com Associações no Ecto

Primeiramente, este artigo foi feito tendo como base o post em inglês do José Valim, criador da linguagem Elixir.

Resolvi fazer esse post, para traduzir o post citado acima para o português e adicionar mais detalhes, fazendo um projeto do zero, podendo assim, ajudar programadores iniciantes da linguagem.

O código do projeto você pode encontrar aqui: https://github.com/maiquitome/blog

Neste post, vamos aprender a trabalhar com associações no Ecto, como ler, inserir, atualizar e excluir associações e incorporações (embeds) .
  • Setup do Projeto
  • Associações
  • Inserindo Registros
  • Querying Associations
  • Manipulando Associações
  • Deletando Associações
  • Embeds (Campos Json)

  • 프로젝트 설정

    Criando o projeto e entrando na pasta do projeto:

    $ mix phx.new blog --binary-id && cd blog
    

    Criando o banco de dados:

    $ mix ecto.create
    

    Criando os schemas e migrations:

    $ mix phx.gen.schema Post posts title body:text 
    
    $ mix phx.gen.schema Comment comments post_id:references:posts body:text 
    

    협회

    As associações no Ecto são usadas quando duas fontes diferentes (tabelas) são ligadas através de chaves estrangeiras (foreign keys).



    Um exemplo clássico desta configuração é um post que tem muitos comentários:

    defmodule Blog.Post do
      use Ecto.Schema
      import Ecto.Changeset
    
      @primary_key {:id, :binary_id, autogenerate: true}
      @foreign_key_type :binary_id
      schema "posts" do
        field :body, :string
        field :title, :string
    
        # adicione essa linha
        has_many :comments, Blog.Comment
    
        timestamps()
      end
    
      @doc false
      def changeset(post, attrs) do
        post
        |> cast(attrs, [:title, :body])
        |> validate_required([:title, :body])
      end
    end
    


    다음 게시물에 대한 의견:

    defmodule Blog.Comment do
      use Ecto.Schema
      import Ecto.Changeset
    
      @primary_key {:id, :binary_id, autogenerate: true}
      @foreign_key_type :binary_id
      schema "comments" do
        field :body, :string
    
        # remova essa linha
        # field :post_id, :binary_id
    
        # adicione essa linha
        belongs_to :post, Blog.Post
    
        timestamps()
      end
    
      @doc false
      def changeset(comment, attrs) do
        comment
        # adicione o campo :post_id
        |> cast(attrs, [:body, :post_id])
        |> validate_required([:body])
      end
    end
    


    인세린도 레지스터

    Para gente testar as consultas a seguir, vamos inserir alguns registros.

    Criando as tabelas no banco (rodando as migrations):

    $ mix ecto.migrate 
    

    Vamos criar alguns posts e comentários. Adicione o conteúdo abaixo ao arquivo priv/repo/seeds.ex :

    # Primeiro Post
    {:ok, post1} = %Blog.Post{}
    |> Blog.Post.changeset(%{title: "Primeiro Post", body: "Conteúdo do primeiro post"})
    |> Blog.Repo.insert
    
    %Blog.Comment{}
    |> Blog.Comment.changeset(%{body: "comentário do Primeiro Post", post_id: post1.id})
    |> Blog.Repo.insert
    
    # Segundo Post
    {:ok, post2} = %Blog.Post{}
    |> Blog.Post.changeset(%{title: "Segundo Post", body: "Conteúdo do segundo post"})
    |> Blog.Repo.insert
    
    %Blog.Comment{}
    |> Blog.Comment.changeset(%{body: "comentário do Segundo Post", post_id: post2.id})
    |> Blog.Repo.insert
    
    # Terceiro Post
    {:ok, post3} = %Blog.Post{}
    |> Blog.Post.changeset(%{title: "Terceiro Post", body: "Conteúdo do terceiro post"})
    |> Blog.Repo.insert
    
    %Blog.Comment{}
    |> Blog.Comment.changeset(%{body: "comentário do Terceiro Post", post_id: post3.id})
    |> Blog.Repo.insert
    
    $ mix run priv/repo/seeds.exs
    

    Associações de Consulta(연결 쿼리)

    Uma das vantagens de definir associações é que elas podem ser usadas em consultas. Por exemplo:

    iex> import Ecto.Query
    
    iex> Blog.Repo.all(from p in Blog.Post, preload: [:comments])
    
    Resultado:


    모든 게시물이 게시되지 않은 경우에는 거래처에 의견을 남기지 않습니다. 예를 들어 acima realizará duas Consultas: uma para carregar todos os posts e outra para carregar todos os comentários. Esta éfrequentemente a forma eficiente de carregar associações do banco de dados (mesmo que duas Consultas sejam realizadas), pois precisamos receber e analisar apenas os resultados dos POSTS + COMMENTÁRIOS.

    Também é possível pré-carregar (preload) associações usando as uniões enquanto se realizam Consultas mais complexas. 예를 들어, Imagine que tanto os posts como os comentários têm votos e você quer apenas comentários com mais votos do que o próprio post:

    Blog.Repo.all from p in Blog.Post,
                join: c in assoc(p, :comments),
                where: c.votes > p.votes,
                preload: [comments: c]
    


    예를 들어 acima agora realizará uma única Consulta, encontrando todos osposts eosrespectivoscommentáriosquecorafamaoscritérios. Como esta Consulta realiza um JOIN, o número de resultados retornados pelo banco de dados é POSTS * COMMENTS, onde o Ecto então processa e associa todos os comentários no post apropriado.

    Finalmente, o Ecto também permite que os dados sejam pré-carregados em estruturas (structs) após terem sido carregados através da função Repo.preload/3:

    Blog.Repo.preload posts, :comments
    


    Isto é especialmente útil porque o Ecto não suporta carregamento preguiçoso(지연 로딩). Se você invocar post.comments e comentários posteriores não tiverem sido pré-carregados, vai retornar Ecto.Association.NotLoaded . O carregamento preguiçoso é frequencymente uma fonte de confusão e problemas de desempenho eo Ecto pressiona os desenvolvedores a fazerem o que é correto. Portanto, oRepo.preload/3는 qualquer lugar, qualquer momento에서 명시적 carregadas를 연관시키는 것으로 허용됩니다.

    Manipulando Associações

    Enquanto o Ecto 2.0 permite inserir um post com múltiplos comentários em uma única operação, por exemplo:

    Repo.insert!(%Post{
      title: "Hello",
      body: "world",
      comments: [
        %Comment{body: "Excellent!"}
      ]
    })
    

    Muitas vezes você pode querer dividi-lo em etapas diferentes para ter mais flexibilidade no gerenciamento dessas entradas. Por exemplo, você poderia usar conjuntos de mudanças (changesets) para construir seus posts e comentários ao longo do caminho:

    Preste atenção no Ecto.Changeset.put_assoc .

    post = Ecto.Changeset.change(%Post{}, title: "Hello", body: "world")
    
    comment = Ecto.Changeset.change(%Comment{}, body: "Excellent!")
    
    post_with_comments = Ecto.Changeset.put_assoc(post, :comments, [comment])
    
    Repo.insert!(post_with_comments)
    

    Ou manuseando cada entrada individualmente dentro de uma transação:

    Preste atenção no Ecto.build_assoc .

    Repo.transaction fn ->
      post = Repo.insert!(%Post{title: "Hello", body: "world"})
    
      # Build a comment from the post struct
      comment = Ecto.build_assoc(post, :comments, body: "Excellent!")
    
      Repo.insert!(comment)
    end
    

    Ecto.build_assoc/3 constrói o comentário utilizando a identificação atualmente definida na estrutura do post. É equivalente a:

    %Comment{post_id: post.id, body: "Excellent!"}
    

    A função Ecto.build_assoc/3 é especialmente útil nos controladores (controllers) do Phoenix. Por exemplo, poderíamos ter uma tabela de usuário...



    e ao criar um post, faríamos:

    Ecto.build_assoc(current_user, :post)
    


    Pois, provavelmente queremos associar o post ao usuário atualmente logado na aplicação.

    Em outro controlador, poderíamos construir um comentário para um existente:

    Ecto.build_assoc(post, :comments)
    


    O Ecto não fornece funções comopost.comments << comment que permite misturar dados persistidos com dados não-persistidos. O único mecanismo para mudar tanto o post como os comentários em simultâneo, é por changesets que iremos explorar quando falarmos sobre incorporações (embeds) e associações aninhadas (nested associations).

    Deletando Associações

    Quando definimos um has_many/3 ou has_one/3 , você também pode passar uma opção :on_delete que especifica qual ação deve ser executada nas associações quando o pai é excluído. Por exemplo, se um post for excluído, então os comentários associados a ele também serão excluídos:

    has_many :comments, Blog.Comment, on_delete: :delete_all
    

    Modificando o Schema do post:

    defmodule Blog.Post do
      use Ecto.Schema
      import Ecto.Changeset
    
      @primary_key {:id, :binary_id, autogenerate: true}
      @foreign_key_type :binary_id
      schema "posts" do
        field :body, :string
        field :title, :string
    
        # modifique aqui adicionando `on_delete: :delete_all`
        has_many :comments, Blog.Comment, on_delete: :delete_all
    
        timestamps()
      end
    
      @doc false
      def changeset(post, attrs) do
        post
        |> cast(attrs, [:title, :body])
        |> validate_required([:title, :body])
      end
    end
    
    Além disso, :nilify_all também é suportado, sendo que :nothing é o padrão. Verifique has_many/3 더 많은 정보를 제공하는 문서입니다.

    O uso desta opção é DESENCORAJADA para a maioria dos bancos de dados relacionais. Ao invés disso, em sua migração, defina references(:parent_id, on_delete: :delete_all) :

    defmodule Blog.Repo.Migrations.CreateComments do
      use Ecto.Migration
    
      def change do
        create table(:comments, primary_key: false) do
          add :id, :binary_id, primary_key: true
          add :body, :text
          # modifique aqui adicionando :delete_all
          add :post_id, references(:posts, on_delete: :delete_all, type: :binary_id)
    
          timestamps()
        end
    
        create index(:comments, [:post_id])
      end
    end
    


    퍼가기

    Acesse a continuação

    좋은 웹페이지 즐겨찾기