socialgekon.com
  • Principal
  • Inovação
  • Pesquisar
  • Mundo
  • Nutrição
Processo Interno

Conheça Ecto, Wrapper de banco de dados sem compromisso para aplicativos Elixir simultâneos

Ecto é uma linguagem específica de domínio para escrever consultas e interagir com bancos de dados no Linguagem elixir . A versão mais recente (2.0) oferece suporte a PostgreSQL e MySQL. (o suporte para MSSQL, SQLite e MongoDB estará disponível no futuro). Caso você seja novo em Elixir ou tenha pouca experiência com isso, eu recomendo que você leia o de Kleber Virgilio Correia Introdução à linguagem de programação Elixir .

Cansado de todos os dialetos SQL? Fale com o seu banco de dados através do Ecto. Tweet

Ecto é composto por quatro componentes principais:



  • Ecto.Repo. Define repositórios que são wrappers em torno de um armazenamento de dados. Usando-o, podemos inserir, criar, excluir e consultar um repo. Um adaptador e credenciais são necessários para se comunicar com o banco de dados.
  • Ecto.Schema. Os esquemas são usados ​​para mapear qualquer fonte de dados em uma estrutura Elixir.
  • Ecto.Changeset. Os conjuntos de alterações fornecem uma maneira para os desenvolvedores filtrarem e lançar parâmetros externos, bem como um mecanismo para rastrear e validar as alterações antes de serem aplicadas aos dados.
  • Ecto.Query. Fornece uma consulta SQL semelhante a DSL para recuperar informações de um repositório. As consultas no Ecto são seguras, evitando problemas comuns como injeção de SQL, ao mesmo tempo que podem ser combinadas, permitindo que os desenvolvedores criem consultas peça por peça em vez de todas de uma vez.

Para este tutorial, você precisará de:

  • Elixir instalado ( Guia de instalação para 1.2 ou posterior)
  • PostgreSQL instalado
  • Um usuário definido com permissão para criar um banco de dados (Observação: usaremos o usuário “postgres” com a senha “postgres” como exemplo ao longo deste tutorial.)

Instalação e configuração

Para começar, vamos criar um novo aplicativo com um supervisor usando o Mix. Misturar é uma ferramenta de construção fornecida com o Elixir que fornece tarefas para criar, compilar, testar seu aplicativo, gerenciar suas dependências e muito mais.

mix new cart --sup

Isso criará um carrinho de diretório com os arquivos do projeto inicial:

* creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ecto_tut.ex * creating test * creating test/test_helper.exs * creating test/ecto_tut_test.exs

Estamos usando o --sup uma vez que precisamos de uma árvore de supervisor que manterá a conexão com o banco de dados. Em seguida, vamos para o cart diretório com cd cart e abra o arquivo mix.exs e substitua seu conteúdo:

defmodule Cart.Mixfile do use Mix.Project def project do [app: :cart, version: '0.0.1', elixir: '~> 1.2', build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, deps: deps] end def application do [applications: [:logger, :ecto, :postgrex], mod: {Cart, []}] end # Type 'mix help deps' for more examples and options defp deps do [{:postgrex, '>= 0.11.1'}, {:ecto, '~> 2.0'}] end end

Em def application do temos que adicionar como aplicativos :postgrex, :ecto para que possam ser usados ​​dentro de nosso aplicativo. Também precisamos adicioná-los como dependências adicionando defp deps do postgrex (que é o adaptador de banco de dados) e ecto . Depois de editar o arquivo, execute no console:

mix deps.get

Isso instalará todas as dependências e criará um arquivo mix.lock que armazena todas as dependências e subdependências dos pacotes instalados (semelhante a Gemfile.lock no bundler).

Ecto.Repo

Agora veremos como definir um repo em nosso aplicativo. Podemos ter mais de um repo, o que significa que podemos nos conectar a mais de um banco de dados. Precisamos configurar o banco de dados no arquivo config/config.exs:

use Mix.Config config :cart, ecto_repos: [Cart.Repo]

Estamos apenas definindo o mínimo, para que possamos executar o próximo comando. Com a linha :cart, cart_repos: [Cart.Repo] estamos dizendo ao Ecto quais repositórios estamos usando. Este é um recurso interessante, pois nos permite ter muitos repositórios, ou seja, podemos nos conectar a vários bancos de dados.

Agora execute o seguinte comando:

mix ecto.gen.repo ==> connection Compiling 1 file (.ex) Generated connection app ==> poolboy (compile) Compiled src/poolboy_worker.erl Compiled src/poolboy_sup.erl Compiled src/poolboy.erl ==> decimal Compiling 1 file (.ex) Generated decimal app ==> db_connection Compiling 23 files (.ex) Generated db_connection app ==> postgrex Compiling 43 files (.ex) Generated postgrex app ==> ecto Compiling 68 files (.ex) Generated ecto app ==> cart * creating lib/cart * creating lib/cart/repo.ex * updating config/config.exs Don't forget to add your new repo to your supervision tree (typically in lib/cart.ex): supervisor(Cart.Repo, []) And to add it to the list of ecto repositories in your configuration files (so Ecto tasks work as expected): config :cart, ecto_repos: [Cart.Repo]

Este comando gera o repo. Se você ler a saída, ela lhe dirá para adicionar um supervisor e um repo em seu aplicativo. Vamos começar com o supervisor. Vamos editar lib/cart.ex:

defmodule Cart do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ supervisor(Cart.Repo, []) ] opts = [strategy: :one_for_one, name: Cart.Supervisor] Supervisor.start_link(children, opts) end end

Neste arquivo, estamos definindo o supervisor supervisor(Cart.Repo, []) e adicioná-lo à lista de filhos (no Elixir, as listas são semelhantes a matrizes). Definimos as crianças supervisionadas com a estratégia strategy: :one_for_one o que significa que, se um dos processos supervisionados falhar, o supervisor irá reiniciar apenas aquele processo em seu estado padrão. Você pode aprender mais sobre supervisores Aqui . Se você olhar para lib/cart/repo.ex você verá que este arquivo já foi criado, o que significa que temos um Repo para nosso aplicativo.

defmodule Cart.Repo do use Ecto.Repo, otp_app: :cart end

Agora vamos editar o arquivo de configuração config/config.exs:

use Mix.Config config :cart, ecto_repos: [Cart.Repo] config :cart, Cart.Repo, adapter: Ecto.Adapters.Postgres, database: 'cart_dev', username: 'postgres', password: 'postgres', hostname: 'localhost'

Tendo definido todas as configurações para nosso banco de dados, podemos agora gerá-lo executando:

mix ecto.create

Este comando cria o banco de dados e, com isso, basicamente finalizamos a configuração. Agora estamos prontos para começar a codificar, mas vamos definir o escopo de nosso aplicativo primeiro.

Criação de uma fatura com itens embutidos

Para nosso aplicativo de demonstração, construiremos uma ferramenta de faturamento simples. Para changesets (modelos), teremos Fatura , Item e InvoiceItem . InvoiceItem pertence a Fatura e Item . Este diagrama representa como nossos modelos estarão relacionados entre si:

O diagrama é muito simples. Temos uma mesa faturas que tem muitos invoice_items onde guardamos todos os detalhes e também uma mesa Itens que tem muitos invoice_items . Você pode ver que o tipo de invoice_id e item_id dentro invoice_items tabela é UUID. Estamos usando UUID porque ajuda a ofuscar as rotas, caso você queira expor o aplicativo por meio de uma API e torna mais simples a sincronização, já que você não depende de um número sequencial. Agora vamos criar as tabelas usando tarefas Mix.

Ecto.Migration

Migrações são arquivos usados ​​para modificar o esquema do banco de dados. Ecto.Migration oferece um conjunto de métodos para criar tabelas, adicionar índices, criar restrições e outras coisas relacionadas ao esquema. As migrações realmente ajudam a manter o aplicativo sincronizado com o banco de dados. Vamos criar um script de migração para nossa primeira tabela:

mix ecto.gen.migration create_invoices

Isso irá gerar um arquivo semelhante a priv/repo/migrations/20160614115844_create_invoices.exs onde definiremos nossa migração. Abra o arquivo gerado e modifique seu conteúdo para que fique da seguinte maneira:

defmodule Cart.Repo.Migrations.CreateInvoices do use Ecto.Migration def change do create table(:invoices, primary_key: false) do add :id, :uuid, primary_key: true add :customer, :text add :amount, :decimal, precision: 12, scale: 2 add :balance, :decimal, precision: 12, scale: 2 add :date, :date timestamps end end end

Método interno def change do definimos o esquema que irá gerar o SQL para o banco de dados. create table(:invoices, primary_key: false) do irá criar a mesa faturas . Definimos primary_key: false mas vamos adicionar um campo de ID do tipo UUID , campo de cliente do tipo texto, campo de data do tipo data. O timestamps método irá gerar os campos inserted_at e updated_at que o Ecto preenche automaticamente com a hora em que o registro foi inserido e a hora em que foi atualizado, respectivamente. Agora vá para o console e execute a migração:

mix ecto.migrate

Criamos a tabela invoice s com todos os campos definidos. Vamos criar o Itens mesa:

mix ecto.gen.migration create_items

Agora edite o script de migração gerado:

defmodule Cart.Repo.Migrations.CreateItems do use Ecto.Migration def change do create table(:items, primary_key: false) do add :id, :uuid, primary_key: true add :name, :text add :price, :decimal, precision: 12, scale: 2 timestamps end end end

A novidade aqui é o campo decimal que permite números com 12 dígitos, 2 dos quais são para a parte decimal do número. Vamos executar a migração novamente:

mix ecto.migrate

Agora nós criamos Itens mesa e, finalmente, vamos criar o invoice_items mesa:

mix ecto.gen.migration create_invoice_items

Edite a migração:

defmodule Cart.Repo.Migrations.CreateInvoiceItems do use Ecto.Migration def change do create table(:invoice_items, primary_key: false) do add :id, :uuid, primary_key: true add :invoice_id, references(:invoices, type: :uuid, null: false) add :item_id, references(:items, type: :uuid, null: false) add :price, :decimal, precision: 12, scale: 2 add :quantity, :decimal, precision: 12, scale: 2 add :subtotal, :decimal, precision: 12, scale: 2 timestamps end create index(:invoice_items, [:invoice_id]) create index(:invoice_items, [:item_id]) end end

Como você pode ver, essa migração tem algumas partes novas. A primeira coisa que você notará é add :invoice_id, references(:invoices, type: :uuid, null: false). Isso cria o campo invoice_id com uma restrição no banco de dados que faz referência ao faturas mesa. Temos o mesmo padrão para item_id campo. Outra coisa diferente é a forma como criamos um índice: create index(:invoice_items, [:invoice_id]) cria o índice invoice_items_invoice_id_index .

Ecto.Schema e Ecto.Changeset

Em Ecto, Ecto.Model tornou-se obsoleto em favor do uso de Ecto.Schema, portanto, chamaremos os esquemas de módulos em vez de modelos. Vamos criar os changesets. Começaremos com o item do changeset mais simples e criaremos o arquivo lib/cart/item.ex:

defmodule Cart.Item do use Ecto.Schema import Ecto.Changeset alias Cart.InvoiceItem @primary_key {:id, :binary_id, autogenerate: true} schema 'items' do field :name, :string field :price, :decimal, precision: 12, scale: 2 has_many :invoice_items, InvoiceItem timestamps end @fields ~w(name price) def changeset(data, params \ %{}) do data |> cast(params, @fields) |> validate_required([:name, :price]) |> validate_number(:price, greater_than_or_equal_to: Decimal.new(0)) end end

Na parte superior, injetamos código no conjunto de alterações usando use Ecto.Schema. Também estamos usando import Ecto.Changeset para importar funcionalidade de Ecto.Changeset . Poderíamos ter especificado quais métodos específicos importar, mas vamos mantê-lo simples. O alias Cart.InvoiceItem nos permite escrever diretamente dentro do changeset InvoiceItem , como você verá em um momento.

Ecto.Schema

O @primary_key {:id, :binary_id, autogenerate: true} especifica que nossa chave primária será gerada automaticamente. Como estamos usando um tipo UUID, definimos o esquema com schema 'items' do e dentro do bloco definimos cada campo e relacionamento. Nós definimos nome como corda e preço como decimal, muito semelhante à migração. Em seguida, a macro has_many :invoice_items, InvoiceItem indica uma relação entre Item e InvoiceItem . Já que por convenção, chamamos o campo item_id no invoice_items tabela, não precisamos configurar a chave estrangeira. Finalmente, o timestamps método irá definir o inserido_em e updated_at Campos.

Ecto.Changeset

O def changeset(data, params \ %{}) do função recebe uma estrutura Elixir com parâmetros que iremos tubo através de diferentes funções. cast(params, @fields) converte os valores no tipo correto. Por exemplo, você pode passar apenas strings nos parâmetros e eles seriam convertidos para o tipo correto definido no esquema. validate_required([:name, :price]) valida que o nome e preço campos estão presentes, validate_number(:price, greater_than_or_equal_to: Decimal.new(0)) valida se o número é maior ou igual a 0 ou, neste caso, Decimal.new(0).

No Elixir, as operações decimais são feitas de maneira diferente, pois são implementadas como uma estrutura.

Foi muito para assimilar, então vamos dar uma olhada no console com exemplos para que você possa entender melhor os conceitos:

iex -S mix

Isso irá carregar o console. -S mix carrega o projeto atual no REPL iex.

iex(0)> item = Cart.Item.changeset(%Cart.Item{}, %{name: 'Paper', price: '2.5'}) #Ecto.Changeset

Isso retorna um Ecto.Changeset estrutura que é válida sem erros. Agora vamos salvá-lo:

iex(1)> item = Cart.Repo.insert!(item) %Cart.Item{__meta__: #Ecto.Schema.Metadata, id: '66ab2ab7-966d-4b11-b359-019a422328d7', inserted_at: #Ecto.DateTime, invoice_items: #Ecto.Association.NotLoaded, name: 'Paper', price: #Decimal, updated_at: #Ecto.DateTime}

Não mostramos o SQL por questões de brevidade. Neste caso, ele retorna o Cart.Item estrutura com todos os valores definidos, você pode ver que inserido_em e updated_at conter seus carimbos de data / hora e o Eu iria campo tem um valor UUID. Vejamos alguns outros casos:

iex(3)> item2 = Cart.Item.changeset(%Cart.Item{price: Decimal.new(20)}, %{name: 'Scissors'}) #Ecto.Changeset iex(4)> Cart.Repo.insert(item2)

Agora definimos o Scissors item de uma maneira diferente, definindo o preço diretamente %Cart.Item{price: Decimal.new(20)}. Precisamos definir seu tipo correto, ao contrário do primeiro item, onde passamos uma string como preço. Poderíamos ter passado um float e isso seria convertido em um tipo decimal. Se passarmos, por exemplo %Cart.Item{price: 12.5}, quando você inserir o item, ele lançará uma exceção informando que o tipo não corresponde.

iex(4)> invalid_item = Cart.Item.changeset(%Cart.Item{}, %{name: 'Scissors', price: -1.5}) #Ecto.Changeset

Para encerrar o console, pressione Ctrl + C duas vezes. Você pode ver que as validações estão funcionando e o preço deve ser maior ou igual a zero (0). Como você pode ver, nós definimos todo o esquema Ecto.Schema que é a parte relacionada a como a estrutura do módulo é definida e o changeset Ecto.Changeset que é todas as validações e elenco. Vamos continuar e criar o arquivo lib/cart/invoice_item.ex:

defmodule Cart.InvoiceItem do use Ecto.Schema import Ecto.Changeset @primary_key {:id, :binary_id, autogenerate: true} schema 'invoice_items' do belongs_to :invoice, Cart.Invoice, type: :binary_id belongs_to :item, Cart.Item, type: :binary_id field :quantity, :decimal, precision: 12, scale: 2 field :price, :decimal, precision: 12, scale: 2 field :subtotal, :decimal, precision: 12, scale: 2 timestamps end @fields ~w(item_id price quantity) @zero Decimal.new(0) def changeset(data, params \ %{}) do data |> cast(params, @fields) |> validate_required([:item_id, :price, :quantity]) |> validate_number(:price, greater_than_or_equal_to: @zero) |> validate_number(:quantity, greater_than_or_equal_to: @zero) |> foreign_key_constraint(:invoice_id, message: 'Select a valid invoice') |> foreign_key_constraint(:item_id, message: 'Select a valid item') |> set_subtotal end def set_subtotal(cs) do case cs.data.price), (cs.changes[:quantity] do {_price, nil} -> cs {nil, _quantity} -> cs {price, quantity} -> put_change(cs, :subtotal, Decimal.mult(price, quantity)) end end end

Este conjunto de alterações é maior, mas você já deve estar familiarizado com a maior parte dele. Aqui belongs_to :invoice, Cart.Invoice, type: :binary_id define a relação 'pertence a' com o Cart.Invoice conjunto de alterações que criaremos em breve. O próximo belongs_to :item cria um relacionamento com a tabela de itens. Definimos @zero Decimal.new(0). Nesse caso, @zero é como uma constante que pode ser acessada dentro do módulo. A função changeset tem novas partes, uma das quais é foreign_key_constraint(:invoice_id, message: 'Select a valid invoice'). Isso permitirá que uma mensagem de erro seja gerada em vez de gerar uma exceção quando a restrição não for cumprida. E finalmente, o método set_subtotal irá calcular o subtotal. Passamos o changeset e retornamos um novo changeset com o subtotal calculado se tivermos o preço e a quantidade.

Agora, vamos criar o Cart.Invoice . Portanto, crie e edite o arquivo lib/cart/invoice.ex para conter o seguinte:

defmodule Cart.Invoice do use Ecto.Schema import Ecto.Changeset alias Cart.{Invoice, InvoiceItem, Repo} @primary_key {:id, :binary_id, autogenerate: true} schema 'invoices' do field :customer, :string field :amount, :decimal, precision: 12, scale: 2 field :balance, :decimal, precision: 12, scale: 2 field :date, Ecto.Date has_many :invoice_items, InvoiceItem, on_delete: :delete_all timestamps end @fields ~w(customer amount balance date) def changeset(data, params \ %{}) do data |> cast(params, @fields) |> validate_required([:customer, :date]) end def create(params) do cs = changeset(%Invoice{}, params) |> validate_item_count(params) |> put_assoc(:invoice_items, get_items(params)) if cs.valid? do Repo.insert(cs) else cs end end defp get_items(params) do items = params[:invoice_items] || params['invoice_items'] Enum.map(items, fn(item)-> InvoiceItem.changeset(%InvoiceItem{}, item) end) end defp validate_item_count(cs, params) do items = params[:invoice_items] || params['invoice_items'] if Enum.count(items) <= 0 do add_error(cs, :invoice_items, 'Invalid number of items') else cs end end end

Cart.Invoice conjunto de mudanças tem algumas diferenças. O primeiro está dentro esquemas : has_many :invoice_items, InvoiceItem, on_delete: :delete_all significa que quando excluímos uma fatura, todos os associados invoice_items será deletado. Lembre-se, porém, de que essa não é uma restrição definida no banco de dados.

Vamos tentar o método de criação no console para entender melhor as coisas. Você pode ter criado os itens (“Papel”, “Tesouras”) que usaremos aqui:

iex(0)> item_ids = Enum.map(Cart.Repo.all(Cart.Item), fn(item)-> item.id end) iex(1)> {id1, id2} = {Enum.at(item_ids, 0), Enum.at(item_ids, 1) }

Buscamos todos os itens com Cart.Repo.all e com o Enum.map função acabamos de obter a item.id de cada item. Na segunda linha, apenas atribuímos id1 e id2 com o primeiro e o segundo item_ids, respectivamente:

iex(2)> inv_items = [%{item_id: id1, price: 2.5, quantity: 2}, %{item_id: id2, price: 20, quantity: 1}] iex(3)> {:ok, inv} = Cart.Invoice.create(%{customer: 'James Brown', date: Ecto.Date.utc, invoice_items: inv_items})

A fatura foi criada com seus invoice_items e podemos obter todas as faturas agora.

iex(4)> alias Cart.{Repo, Invoice} iex(5)> Repo.all(Invoice)

Você pode ver que ele retorna o Fatura mas gostaríamos de ver também o invoice_items :

iex(6)> Repo.all(Invoice) |> Repo.preload(:invoice_items)

Com o Repo.preload função, podemos obter o invoice_items. Observe que isso pode processar consultas simultaneamente. No meu caso, a consulta parecia assim:

iex(7)> Repo.get(Invoice, '5d573153-b3d6-46bc-a2c0-6681102dd3ab') |> Repo.preload(:invoice_items)

Ecto.Query

Até agora, mostramos como criar novos itens e novas faturas com relacionamentos. Mas e quanto a consultar? Bem, deixe-me apresentar-lhe Ecto.Query o que nos ajudará a fazer consultas ao banco de dados, mas primeiro precisamos de mais dados para explicar melhor.

iex(1)> alias Cart.{Repo, Item, Invoice, InvoiceItem} iex(2)> Repo.insert(%Item{name: 'Chocolates', price: Decimal.new('5')}) iex(3)> Repo.insert(%Item{name: 'Gum', price: Decimal.new('2.5')}) iex(4)> Repo.insert(%Item{name: 'Milk', price: Decimal.new('1.5')}) iex(5)> Repo.insert(%Item{name: 'Rice', price: Decimal.new('2')}) iex(6)> Repo.insert(%Item{name: 'Chocolates', price: Decimal.new('10')})

Devemos agora ter 8 itens e há um “Chocolate” repetido. Podemos querer saber quais itens são repetidos. Então, vamos tentar esta consulta:

iex(7)> import Ecto.Query iex(8)> q = from(i in Item, select: %{name: i.name, count: (i.name)}, group_by: i.name) iex(9)> Repo.all(q) 19:12:15.739 [debug] QUERY OK db=2.7ms SELECT i0.'name', count(i0.'name') FROM 'items' AS i0 GROUP BY i0.'name' [] [%{count: 1, name: 'Scissors'}, %{count: 1, name: 'Gum'}, %{count: 2, name: 'Chocolates'}, %{count: 1, name: 'Paper'}, %{count: 1, name: 'Milk'}, %{count: 1, name: 'Test'}, %{count: 1, name: 'Rice'}]

Você pode ver que na consulta queríamos retornar um mapa com o nome do item e o número de vezes que ele aparece na tabela de itens. Alternativamente, porém, podemos provavelmente estar interessados ​​em ver quais são os produtos mais vendidos. Então, para isso, vamos criar algumas faturas. Primeiro, vamos tornar nossas vidas mais fáceis criando um mapa para acessar um item_id:

iex(10)> l = Repo.all(from(i in Item, select: {i.name, i.id})) iex(11)> items = for {k, v} '8fde33d3-6e09-4926-baff-369b6d92013c', 'Gum' => 'cb1c5a93-ecbf-4e4b-8588-cc40f7d12364', 'Milk' => '7f9da795-4d57-4b46-9b57-a40cd09cf67f', 'Paper' => '66ab2ab7-966d-4b11-b359-019a422328d7', 'Rice' => 'ff0b14d2-1918-495e-9817-f3b08b3fa4a4', 'Scissors' => '397b0bb4-2b04-46df-84d6-d7b1360b6c72', 'Test' => '9f832a81-f477-4912-be2f-eac0ec4f8e8f'}

Como você pode ver, criamos um mapa usando um compreensão

iex(12)> line_items = [%{item_id: items['Chocolates'], quantity: 2}]

Precisamos adicionar o preço no invoice_items params para criar uma nota fiscal, mas seria melhor apenas passar o id do item e ter o preço preenchido automaticamente. Faremos alterações no Cart.Invoice módulo para fazer isso:

defmodule Cart.Invoice do use Ecto.Schema import Ecto.Changeset import Ecto.Query # We add to query # .... # schema, changeset and create functions don't change # The new function here is items_with_prices defp get_items(params) do items = items_with_prices(params[:invoice_items] || params['invoice_items']) Enum.map(items, fn(item)-> InvoiceItem.changeset(%InvoiceItem{}, item) end) end # new function to get item prices defp items_with_prices(items) do item_ids = Enum.map(items, fn(item) -> item[:item_id] || item['item_id'] end) q = from(i in Item, select: %{id: i.id, price: i.price}, where: i.id in ^item_ids) prices = Repo.all(q) Enum.map(items, fn(item) -> item_id = item[:item_id] || item['item_id'] % end) end

A primeira coisa que você notará é que adicionamos Ecto.Query , o que nos permitirá consultar o banco de dados. A nova função é defp items_with_prices(items) do que pesquisa os itens, localiza e define o preço de cada item.

Primeiro, defp items_with_prices(items) do recebe uma lista como argumento. Com item_ids = Enum.map(items, fn(item) -> item[:item_id] || item['item_id'] end), iteramos por todos os itens e obtemos apenas o item_id . Como você pode ver, acessamos qualquer um com atom :item_id ou string “item_id”, uma vez que os mapas podem ter qualquer um deles como chaves. A consulta q = from(i in Item, select: %{id: i.id, price: i.price}, where: i.id in ^item_ids) encontrará todos os itens que estão em item_ids e retornará um mapa com item.id e item.price. Podemos então executar a consulta prices = Repo.all(q) que retorna uma lista de mapas. Em seguida, precisamos iterar os itens e criar uma nova lista que adicionará o preço. O Enum.map(items, fn(item) -> itera em cada item, encontra o preço Enum.find(prices, fn(p) -> p[:id] == item_id end)[:price] || 0 e cria uma nova lista com item_id, quantidade e preço. E com isso, não é mais necessário adicionar o preço em cada um dos invoice_items.

Inserindo Mais Faturas

Como você se lembra, anteriormente criamos um mapa Itens que nos permite acessar o Eu iria usando o nome do item para, por exemplo, items['Gum'] “Cb1c5a93-ecbf-4e4b-8588-cc40f7d12364”. Isso torna mais simples criar invoice_items . Vamos criar mais faturas. Inicie o console novamente e execute:

Iex -S mix iex(1)> Repo.delete_all(InvoiceItem); Repo.delete_all(Invoice)

Nós apagamos tudo invoice_items e as faturas devem ter uma folha em branco:

iex(2)> li = [%{item_id: items['Gum'], quantity: 2}, %{item_id: items['Milk'], quantity: 1}] iex(3)> Invoice.create(%{customer: 'Mary Jane', date: Ecto.Date.utc, invoice_items: li}) iex(4)> li2 = [%{item_id: items['Chocolates'], quantity: 2}| li] iex(5)> Invoice.create(%{customer: 'Mary Jane', date: Ecto.Date.utc, invoice_items: li2}) iex(5)> li3 = li2 ++ [%{item_id: items['Paper'], quantity: 3 }, %{item_id: items['Rice'], quantity: 1}, %{item_id: items['Scissors'], quantity: 1}] iex(6)> Invoice.create(%{customer: 'Juan Perez', date: Ecto.Date.utc, invoice_items: li3})

Agora temos 3 notas fiscais; o primeiro com 2 itens, o segundo com 3 itens e o terceiro com 6 itens. Gostaríamos agora de saber quais são os produtos mais vendidos? Para responder a isso, vamos criar uma consulta para encontrar os itens mais vendidos por quantidade e por subtotal (preço x quantidade).

defmodule Cart.Item do use Ecto.Schema import Ecto.Changeset import Ecto.Query alias Cart.{InvoiceItem, Item, Repo} # schema and changeset don't change # ... def items_by_quantity, do: Repo.all items_by(:quantity) def items_by_subtotal, do: Repo.all items_by(:subtotal) defp items_by(type) do from i in Item, join: ii in InvoiceItem, on: ii.item_id == i.id, select: %{id: i.id, name: i.name, total: sum(field(ii, ^type))}, group_by: i.id, order_by: [desc: sum(field(ii, ^type))] end end

Nós importamos Ecto.Query e então alias Cart.{InvoiceItem, Item, Repo} portanto, não precisamos adicionar o carrinho no início de cada módulo. A primeira função items_by_quantity chama o items_by , passando a função :quantity parâmetro e chamando o Repo.all para executar a consulta. A função items_by_subtotal é semelhante à função anterior, mas passa o :subtotal parâmetro. Agora vamos explicar items_by :

  • from i in Item, esta macro seleciona o módulo Item
  • join: ii in InvoiceItem, on: ii.item_id == i.id, cria uma junção na condição “items.id = invoice_items.item_id”
  • select: %{id: i.id, name: i.name, total: sum(field(ii, ^type))}, estamos gerando um mapa com todos os campos que queremos, primeiro selecionamos o id e o nome do Item e fazemos uma soma de operadores. O campo (ii, ^ type) usa o campo macro para acessar dinamicamente um campo
  • group_by: i.id, Agrupamos por items.id
  • order_by: [desc: sum(field(ii, ^type))] e, finalmente, ordenar pela soma em ordem decrescente

Até agora, escrevemos a consulta no estilo de lista, mas podemos reescrevê-la no estilo macro:

defp items_by(type) do Item |> join(:inner, [i], ii in InvoiceItem, ii.item_id == i.id) |> select([i, ii], %{id: i.id, name: i.name, total: sum(field(ii, ^type))}) |> group_by([i, _], i.id) |> order_by([_, ii], [desc: sum(field(ii, ^type))]) end

Eu prefiro escrever consultas em forma de lista, pois acho mais legível.

Conclusão

Cobrimos uma boa parte do que você pode fazer em um aplicativo com o Ecto. Claro, há muito mais que você pode aprender com o Ecto docs . Com o Ecto, você pode criar aplicativos simultâneos e tolerantes a falhas com pouco esforço que podem ser escalonados facilmente graças à máquina virtual Erlang. Ecto fornece a base para o armazenamento em seus aplicativos Elixir e fornece funções e macros para gerenciar facilmente seus dados.

Neste tutorial, examinamos Ecto.Schema , Ecto.Changeset , Ecto.Migration , Ecto.Query e Ecto.Repo . Cada um desses módulos ajuda você em diferentes partes do seu aplicativo e torna o código mais explícito e fácil de manter e entender.

Se você quiser verificar o código do tutorial, pode encontrá-lo Aqui no GitHub.

Se você gostou deste tutorial e está interessado em mais informações, eu recomendo Fénix (para uma lista de projetos incríveis), Elixir incrível e esta conversa que compara ActiveRecord com Ecto.

Buscar e analisar o alto uso da CPU em aplicativos .NET

Processo Interno

Buscar e analisar o alto uso da CPU em aplicativos .NET
Conluio: Rede de Dispositivos Próximos com MultipeerConnectivity no iOS

Conluio: Rede de Dispositivos Próximos com MultipeerConnectivity no iOS

Móvel

Publicações Populares
Compreendendo as nuances da classificação de fontes
Compreendendo as nuances da classificação de fontes
Uma 'reforma da mamãe' pode trazer de volta seu corpo antes da gravidez? Descobrir
Uma 'reforma da mamãe' pode trazer de volta seu corpo antes da gravidez? Descobrir
AI Investment Primer: A Practical Guide to Appraising Artificial Intelligence Dealflow (Parte II)
AI Investment Primer: A Practical Guide to Appraising Artificial Intelligence Dealflow (Parte II)
O guia avançado para otimizar o desempenho do WordPress
O guia avançado para otimizar o desempenho do WordPress
Recrutador Júnior
Recrutador Júnior
 
Cuidado com o vento: opiniões sobre o boom de crescimento das energias renováveis
Cuidado com o vento: opiniões sobre o boom de crescimento das energias renováveis
Gandhis, BJP, nacionalismo divisivo: o que Barack Obama diz sobre a Índia nas memórias
Gandhis, BJP, nacionalismo divisivo: o que Barack Obama diz sobre a Índia nas memórias
Os segredos da fotografia de rua sincera no seu iPhone
Os segredos da fotografia de rua sincera no seu iPhone
Como obter o equilíbrio de branco adequado em suas fotos do iPhone
Como obter o equilíbrio de branco adequado em suas fotos do iPhone
Mulher de origem indiana presa em Cingapura por trapacear como agente de viagens
Mulher de origem indiana presa em Cingapura por trapacear como agente de viagens
Categorias
Dicas E FerramentasDesign De IuNoticias Do MundoProcessos FinanceirosEquipes DistribuídasFerramentas E TutoriaisDesign UxPlanejamento E PrevisãoBlogAméricas

© 2023 | Todos Os Direitos Reservados

socialgekon.com