作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
鲍里斯·巴罗佐的头像

鲍里斯·巴罗佐

作为一个企业家, Boris了解与客户和用户密切沟通的重要性,以便更好地根据实际需求塑造应用程序.

工作经验

14

分享

星质 是用于编写查询和与数据库交互的领域特定语言吗 灵丹妙药的语言. 最新版本(2.0)支持PostgreSQL和MySQL. (对MSSQL、SQLite和MongoDB的支持将在未来提供). 如果你是新来的 长生不老药 或者没有经验的人,我建议你们读读Kleber Virgilio Correia的书 开始使用长生不老药编程语言.

厌倦了所有SQL方言? 通过星质与数据库对话.

星质由四个主要部分组成:

  • 星质.回购. 定义存储库,这些存储库是数据存储的包装器. 使用它,我们可以插入、创建、删除和查询一个回购. 与数据库通信需要适配器和凭据.
  • 星质.模式. 模式用于将任何数据源映射到长生不老药结构.
  • 星质.变更集. 变更集为开发人员提供了一种过滤和强制转换外部参数的方法, 以及在将更改应用于数据之前跟踪和验证更改的机制.
  • 星质.查询。. 提供类似dsl的SQL查询,用于从存储库检索信息. 星质中的查询是安全的, 避免常见的问题,如SQL注入, 同时还是可组合的, 允许开发人员一块一块地构建查询,而不是一次构建所有查询.

对于本教程,您将需要:

  • 长生不老药(安装指南 1.2或以上)
  • PostgreSQL安装
  • 具有创建数据库权限的用户(注意:在本教程中,我们将使用用户“postgres”和密码“postgres”作为示例).)

安装与配置

首先,让我们使用混合创建一个带有监控器的新应用程序. 混合 是长生不老药附带的构建工具,提供创建任务, 编译, 测试应用程序, 管理它的依赖关系等等.

混合新购物车-sup

这将创建一个包含初始项目文件的目录购物车:

*创建自述文件.md
*创建 .gitignore
*创造混合.练习
*创建配置
*创建配置/配置.练习
*创建lib
*创建lib/星质_tut.ex
*创建测试
*创建test/test_helper.练习
*创建test/星质_tut_test.练习

我们正在使用 ——吃晚饭 选项,因为我们需要一个管理树来保持与数据库的连接. 接下来,我们去 目录 cd购物车 然后打开文件 混合.练习 并替换其内容:

defmodule购物车.混合file做
  使用混合.项目

  defproject do
    (应用程序::购物车,
     版本:“0.0.1",
     elixir: "~> 1.2",
     build_embedded:混合.Env ==:
     start_permanent:混合.Env ==:
     deps deps):
  结束

  defapplication do
    [applications: [:logger,:星质,:postgrex],
     mod:{购物车,[]}]
  结束

  输入“混合 help deps”查看更多示例和选项
  Defp deps可以
    [{:postgrex, ">= 0.11.1"},
     {:星质, "~> 2.0"}]
  结束
结束

In defapplication do 我们必须添加应用程序 : postgrex:星质 这些可以在我们的应用程序中使用. 我们还需要把它们作为依赖项添加进来 Defp deps可以 postgrex (它是数据库适配器)和 星质. 编辑完文件后,在控制台中运行:

混合deps.得到

这将安装所有依赖项并创建一个文件 混合.锁 它存储已安装包的所有依赖项和子依赖项(类似于 Gemfile.锁 打包机).

星质.回购

现在我们来看看如何在应用程序中定义一个回购. 我们可以有多个回购,这意味着我们可以连接到多个数据库. 我们需要在文件中配置数据库 配置/配置.练习:

使用混合.配置
config:购物车,星质_回购s:[购物车。.回购)

我们只是设置了最小值,所以我们可以运行下一个命令. 用线条 :购物车,车_回购s:[购物车.回购) 我们告诉星质我们正在使用哪个仓库. 这是一个很酷的功能,因为它允许我们有许多仓库.e. 我们可以连接到多个数据库.

现在运行以下命令:

混合星质.创.回购
==> connection
编译1文件(.ex)
生成连接应用程序
==> poolboy (compile)
src / poolboy_worker编译.话务量
src / poolboy_sup编译.话务量
src / poolboy编译.话务量
==> decimal
编译1文件(.ex)
生成十进制应用程序
==> db_connection
编译23个文件(.ex)
生成db_connection应用
==> postgrex
编译43个文件(.ex)
生成postgrex应用程序
==> 星质
编译68个文件(.ex)
生成星质应用程序
==> 车
*创建lib /车
*创建lib /车/回购.ex
*更新配置/配置.练习
不要忘记将新的回购添加到您的监督树中
(通常在lib /车中.ex):

    主管(车.回购,[])

并将其添加到配置文件中的星质存储库列表中(以便星质任务按预期工作):

    配置:购物车,
      星质_回购s(购物车:.回购)

该命令生成回购. 如果你读了输出,它会告诉你在你的应用中添加一个supervisor和回购. 让我们从主管开始. 我们将编辑 lib /车.ex:

defmodule购物车
  使用应用程序

  Def start(_type, _args
    进口主管.Spec, warn: false

    孩子= [
      主管(车.回购,[])
    ]

    opts = [strategy::one_for_one, 名字。: 车 ..主管)
    主管.start_link(孩子,选择)
  结束
结束

在这个文件中,我们定义了管理器 主管(车.回购,[]) 并将其添加到子列表中(在长生不老药中,列表类似于数组)。. 我们用这个策略来定义受监督的孩子 策略:one_for_one 也就是说, 如果其中一个监督过程失败, 管理器只会将该进程重启到默认状态. 你可以了解更多关于主管的信息 在这里. 如果你看看 lib /车/回购.ex 您将看到这个文件已经创建,这意味着我们有一个 回购 对于我们的应用程序.

defmodule购物车.回购做
  使用星质.回购,otp_app::购物车
结束

现在让我们编辑配置文件 配置/配置.练习:

使用混合.配置
config:购物车,星质_回购s:[购物车。.回购)

配置:购物车,购物车.回购,
  适配器:星质.适配器.Postgres、
  数据库:“车_dev”,
  用户名:“postgres”,
  密码:“postgres”,
  主机名:“localhost”

定义了数据库的所有配置后,我们现在可以通过运行:

混合星质.创建

该命令创建了数据库,至此,我们基本上完成了配置. 现在我们已经准备好开始编码,但让我们首先定义应用程序的范围.

用内联项目构建发票

对于我们的演示应用程序,我们将构建一个简单的发票工具. 对于变更集(模型),我们将拥有 发票, 发票项. 发票项 属于 发票. 下图显示了我们的模型是如何相互关联的:

这个图很简单. 我们有一张桌子 发票 有很多 发票_项目 我们在哪里存储所有的细节和一个表 项目 有很多 发票_项目. 你可以看到for的类型 发票_id项_id in 发票_项目 表是UUID. 我们使用UUID是因为它有助于混淆路由, 如果你想通过API公开应用程序,并使其更容易同步,因为你不依赖于序列号. 现在让我们使用混合任务创建表.

星质.迁移

迁移是用于修改数据库模式的文件. 星质.迁移 提供一组创建表的方法, 添加索引, 创建约束, 以及其他与图式相关的东西. 迁移确实有助于保持应用程序与数据库的同步. 让我们为第一个表创建一个迁移脚本:

混合星质.创.迁移创建_发票

这将生成一个类似于 priv /回购/迁移/ 20160614115844 _创建_发票.练习 我们将在哪里定义迁移. 打开生成的文件,将其内容修改如下:

defmodule购物车.回购.迁移.Create发票s做
  使用星质.迁移

  Def change do
    创建表(:发票,primary_key:假
      添加:id,:uuid, primary_key: true
      添加:客户,:文本
      添加:数量,:小数,精度:12,比例:2
      添加:平衡,:小数,精度:12,比例:2
      添加:date,:date

      时间戳
    结束
  结束
结束

内部方法 Def change do 我们定义将为数据库生成SQL的模式. 创建表(:发票,primary_key:假 将创建表格 发票. 我们有 primary_key:假 但我们将添加一个ID字段的类型 UUID,客户字段为文本类型,日期字段为日期类型. 的 时间戳 方法将生成字段 插入ed_atupdated_at 星质会自动填充插入记录的时间和更新记录的时间, 分别. 现在转到控制台并运行迁移:

混合星质.迁移

我们已经创建了表 发票S和所有已定义的字段. 让我们创建 项目 表:

混合星质.创.迁移创建_项目

现在编辑生成的迁移脚本:

defmodule购物车.回购.迁移.创建项做
  使用星质.迁移

  Def change do
    创建表(:项目, primary_key:假
      添加:id,:uuid, primary_key: true
      添加:姓名,:文字
      添加:价格,:小数,精度:12,比例:2

      时间戳
    结束
  结束
结束

这里的新东西是十进制字段,它允许12位数的数字, 其中2个是小数部分. 让我们再次运行迁移:

混合星质.迁移

现在我们创建了 项目 表,最后创建 发票_项目 表:

混合星质.创.迁移创建_发票_项目

编辑迁移:

defmodule购物车.回购.迁移.Create发票项s做
  使用星质.迁移

  Def change do
    创建表(:发票_项目, primary_key:假
      添加:id,:uuid, primary_key: true
      添加:发票_id,引用(:发票,类型::uuid, null: false)
      添加: 项_id, references(:项目, type::uuid, null: false)
      添加:价格,:小数,精度:12,比例:2
      添加:数量,小数,精度:12,比例:2
      添加:小计,:小数,精度:12,比例:2

      时间戳
    结束

    创建索引(:发票_项目, [:发票_id])
    创建索引(:发票_项目, [: 项_id])
  结束
结束

正如您所看到的,这次迁移有一些新的部分. 首先你会注意到的是 添加:发票_id,引用(:发票,类型::uuid, null: false). 这将创建字段 发票_id 的数据库中的约束 发票 table. 我们有相同的模式 项_id 场. 另一个不同之处在于我们创建索引的方式: 创建索引(:发票_项目, [:发票_id]) 创建索引 发票_项目_发票_id_index.

星质.模式和星质.变更集

在星质, 星质.模型 已被弃用,改为使用 星质.模式,所以我们将这些模块称为模式而不是模型. 让我们创建变更集. 我们将从最简单的变更集项开始,并创建该文件 lib /车/项.ex:

defmodule购物车.项目做
  使用星质.模式
  进口星质.变更集

  别名购物车.发票项

  @primary_key {:id,:binary_id, auto创erate: true}
  模式“项”可以
    字段:名字。,:string
    字段:价格,:十进制,精度:12,比例:2
    has_many:发票_项目, 发票项

    时间戳
  结束

  @字段 ~w(名称价格)

  Def changesset (data, params \\ %{}
    data
    |> 铸造(params @字段)
    |> 有效的ate_required([:名称、价格)
    |> 有效的ate_number(:价格,greater_than_or_equal_to:十进制.新(0))
  结束
结束

在顶部,我们使用 使用星质.模式. 我们也在使用 进口星质.变更集星质.变更集. 我们可以指定要导入哪些特定的方法,但让我们保持简单. 的 别名购物车.发票项 允许我们直接在变更集中进行编写 发票项,你马上就会看到.

星质.模式

@primary_key {:id,:binary_id, auto创erate: true} 指定主键将自动生成. 因为我们使用的是UUID类型,所以我们用 模式“项”可以 在块中,我们定义每个字段和关系. 我们定义 名字。 作为字符串和 价格 作为小数,非常类似于迁移. 接下来是宏 has_many:发票_项目, 发票项 之间的关系 发票项. 根据惯例,我们给这个字段命名 项_id发票_项目 表中,我们不需要配置外键. 最后, 时间戳 方法将设置 插入ed_atupdated_at 字段.

星质.变更集

Def changesset (data, params \\ %{} 函数接收带有参数的长生不老药结构体 通过不同的功能. 铸造(params @字段) 将值转换为正确的类型. 例如, 您只能在参数中传递字符串,这些字符串将被转换为模式中定义的正确类型. 有效的ate_required([:名称、价格) 验证 名字。价格 字段是存在的, 有效的ate_number(:价格,greater_than_or_equal_to:十进制.新(0)) 验证数字是否大于或等于0,在本例中为 小数.新(0).

在长生不老药中,小数操作的执行方式不同,因为它们是作为结构体实现的.

这让我难以接受, 那么,让我们在控制台中通过示例来看看这个,以便您更好地掌握概念:

ix -S混合

这将加载控制台. - s混合 将当前项目加载到iex REPL中.

iex(0)> 项 = 车.项.变更集(%购物车.项目{},%{名称:"纸张",价格:.5"})
#星质.变更集},
 errors: [], 数据:#车.项<>, 有效的?: true>

这将返回 星质.变更集 结构,该结构有效且没有错误. 现在保存它:

iex(1)> 项 = 车.回购.插入!(项)
%购物车.{__meta__: #星质物品.模式.Metadata<:loaded, "项目">,
 id:“66 ab2ab7 - 966 d - 4 - b11 - b359 - 019 - a422328d7”,
 插入ed_at: #星质.日期Time<2016-06-18 16:54:54>,
 发票_项目: #星质.协会.NotLoaded,
 名字。: "Paper", 价格: #小数<2.5>,
 updated_at: #星质.日期Time<2016-06-18 16:54:54>}

为简洁起见,我们不展示SQL. 在本例中,它返回 车.项 struct的所有值都设置好了,你可以看到 插入ed_atupdated_at 包含它们的时间戳和 id 字段有一个UUID值. 让我们看一些其他的例子:

iex(3)> 项2 = 车.项.变更集(%购物车.项{价格:小数.新(20)}, %{名字。: "剪刀"})         
#星质.变更集, 有效的?: true>
iex(4)> 车.回购.插入(第二条)

现在我们已经设置了 剪刀 项目以不同的方式,直接设置价格 %购物车.项{价格:小数.新(20)}. 我们需要设置它的正确类型,不像在第一个项目中,我们只是传递了一个字符串作为价格. 我们可以传递一个浮点数,然后将其转换为小数类型. 例如,如果我们通过了 %购物车.项{价格:12.5},当插入项时,它会抛出一个异常,指出类型不匹配.

iex(4)>  in有效的_项 = 车.项.变更集(%购物车.商品{},%{名称:"剪刀",价格:-1.5})
#星质.变更集},
 错误:[价格:{"必须大于或等于%{number}",
   [number: #小数<0>]}], 数据:#车.项<>, 有效的?: false>

如果要终止控制台,请按两次Ctrl+C. 可以看到验证在起作用价格必须大于等于0 (0). 如您所见,我们已经定义了所有模式 星质.模式 哪个部分与如何定义模块结构和更改集相关 星质.变更集 哪一个是所有的验证和强制转换. 让我们继续创建文件 lib /车/ 发票_项.ex:

defmodule购物车.发票项做
  使用星质.模式
  进口星质.变更集

  @primary_key {:id,:binary_id, auto创erate: true}
  模式“发票_项目”可以
    belongs_to:发票、购物车.发票,类型::binary_id
    belongs_to:商品,购物车.项,类型::binary_id
    字段数量:,:decimal, precision: 12, scale: 2
    字段:价格,:十进制,精度:12,比例:2
    字段:小计,:小数,精度:12,比例:2

    时间戳
  结束

  @字段 ~w(项_id 价格 quantity)
  @zero小数.新(0)

  Def changesset (data, params \\ %{}
    data
    |> 铸造(params @字段)
    |> 有效的ate_required([: 项_id, :价格, 数量:])
    |> 有效的ate_number(:价格, greater_than_or_equal_to: @zero)
    |> 有效的ate_number(数量:, greater_than_or_equal_to: @zero)
    |> foreign_key_constraint(:发票_id,消息:"选择一个有效的发票")
    |> foreign_key_constraint(: 项_id, message: "Select a 有效的 项")
    |> set_subtotal
  结束

  Def set_subtotal(cs)
    {(cs.变化[:价格]|| cs.data.价格),(cs.变化[:数量]|| cs.data.量)}
      {_价格, nil} -> cs
      {nil, _quantity} -> cs
      {价格, quantity} ->
        put_change(cs,:小计, 小数.乘(价格、数量)
    结束
  结束
结束

这个更改集更大,但您应该已经熟悉了其中的大部分. 在这里 belongs_to:发票、购物车.发票,类型::binary_id 定义“属于”的关系 车.发票 我们将很快创建的变更集. 下一个 belongs_to:条目 创建与项目表的关系. 我们已经定义了 @zero小数.新(0). 在这种情况下, @zero 是否像一个可以在模块内部访问的常量. 变更集函数有新的部分,其中之一是 foreign_key_constraint(:发票_id,消息:"选择一个有效的发票"). 这将允许在约束未实现时生成错误消息,而不是生成异常. 最后是方法 set_subtotal 会计算小计吗. 如果同时拥有价格和数量,则传递更改集并返回一个新的更改集,其中包含已计算的小计.

现在,我们来创建 车.发票. 因此,创建并编辑该文件 lib /车/发票.ex 包含以下内容:

defmodule购物车.发票做
  使用星质.模式
  进口星质.变更集

  别名购物车.{发票, 发票项, 回购}

  @primary_key {:id,:binary_id, auto创erate: true}
  模式“发票”可以
    字段:customer,字符串
    字段:amount,:decimal,精度:12,刻度:2
    字段:balance,:decimal, precision: 12, scale: 2
    字段:日期,日期.日期
    has_many:发票_项目, 发票项, on_delete::delete_所有

    时间戳
  结束

  @字段 ~w(客户金额余额日期)

  Def changesset (data, params \\ %{}
    data
    |> 铸造(params @字段)
    |> 有效的ate_required([:customer, :date])
  结束

  Def 创建(params
    cs = changesset(%发票{},参数)
    |> 有效的ate_项_count(params)
    |> put_assoc(:发票_项目, 得到_项目(params))

    如果计算机科学.有效的? do
      回购.插入(cs)
    其他的
      cs
    结束
  结束

  执行得到_项目(params)
    项s = params[:发票_项目] || params["发票_项目"]
    枚举.map(项目, fn(项)-> 发票项.变更集(%发票项{}, 项)结束
  结束

  执行有效的ate_项_count(cs, params)
    项s = params[:发票_项目] || params["发票_项目"]

    如果枚举.count(项目) <= 0 do
      add_error(cs,:发票_项目, "项目数量无效")
    其他的
      cs
    结束
  结束

结束

车.发票 变更集有一些不同. 第一个在里面 模式: has_many:发票_项目, 发票项, on_delete::delete_所有 意味着当我们删除发票时,所有相关的 发票_项目 将被删除. 但是请记住,这不是在数据库中定义的约束.

让我们尝试在控制台中使用创建方法来更好地理解. 你可能已经创建了项目(“纸”,“剪刀”),我们将在这里使用:

iex(0)> 项_id = 枚举.地图(车.回购.(车.项), fn(项)-> 项.id结束)
iex(1)> {id1, id2} = {枚举.at(项_id, 0), 枚举.At (项_id, 1)}

我们取走了所有的物品 车.回购.所有枚举.map 函数,我们得到 项.id 每个项目. 在第二行,我们只是赋值 id1id2 分别使用第一个和第二个项_id:

iex(2)> inv_项目 = [%{项_id: id1, 价格: 2.5、数量:2};
 %{项_id: id2,价格:20,数量:1}]
iex(3)> {:ok, inv} = 车.发票.创建(%{customer: "James Brown",日期:10月1日.日期.Utc, 发票_项目: inv_项目})

已经用它的发票_项目创建了发票,现在我们可以获取所有发票了.

iex(4)> 别名购物车.{回购、发票}
iex(5)> 回购.(发票)

你可以看到它返回 发票 但是我们也想看看 发票_项目:

iex(6)> 回购.(发票) |> 回购.预加载(发票_项目):

回购.预加载 函数,我们可以得到 发票_项目. 注意,这可以并发地处理查询. 在我的例子中,查询看起来像这样:

iex(7)> 回购.得到(发票, "5d573153-b3d6-46bc-a2c0-6681102dd3ab") |> 回购.预加载(发票_项目):

星质.查询。

到目前为止,我们已经展示了如何创建带有关系的新项目和新发票. 但是查询呢?? 让我给你介绍一下 星质.查询。 这将有助于我们对数据库进行查询,但首先我们需要更多的数据来更好地解释.

iex(1)> 别名购物车.{回购, 项, 发票, 发票项}
iex(2)> 回购.插入%商品{名称:“巧克力”,价格:十进制.新(“5”)})
iex(3)> 回购.插入%商品{名称:“口香糖”,价格:十进制.新(“2.5")})
iex(4)> 回购.插入(%商品{名称:"牛奶",价格:十进制.新(“1.5")})
iex(5)> 回购.插入(%商品{名称:"大米",价格:十进制.新(2)})
iex(6)> 回购.插入%商品{名称:“巧克力”,价格:十进制.新(10)})

我们现在应该有8件物品,并且有一个重复的“巧克力”. 我们可能想知道哪些项目是重复的. 那么让我们试试这个查询:

iex(7)> 进口星质.查询。
iex(8)> q = from(i in 项, select: %{名字。: i.姓名、计数.Name)}, group_by.名称)
iex(9)> 回购.(问)
19:12:15.739[调试]QUERY OK db=2.7ms
选择钱数.“名称”,计数(钱数.“名称”)从“项目”作为i0 GROUP BY i0.“名称”[]
[%{数:1,名字:“剪刀”},%{数:1,名字:“口香糖”},
 %{数:2,名字:“巧克力”},%{数:1,名字:“纸”},
 %{数:1,名字:“牛奶”},%{数:1,名字:“测试”},
 %{count: 1, 名字。: "Rice"}]

您可以看到,在查询中,我们希望返回一个映射,其中包含项目名称及其在项目表中出现的次数. 另外, 虽然, 我们可能更感兴趣的是哪些是最畅销的产品. 因此,让我们创建一些发票. 首先,让我们通过创建一个访问an的地图来简化我们的工作 项_id:

iex(10)> l =  回购.所有(from(i) in 项, select: {i.名字,我.id}))
iex(11)> 项目 = for {k, v} <- l, into: %{}, do: {k, v}
%{"Chocolates" => "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",
  "剪刀" => "397b0bb4-2b04-46df-84d6-d7b1360b6c72",
  "Test" => "9f832a81-f477-4912-be2f-eac0ec4f8e8f"}

如您所见,我们已经使用 理解

iex(12)> line_项目 = [%{项_id: 项目["Chocolates"], quantity: 2}]

我们需要把价格加进去 发票_项目 参数创建发票, 但是最好只传递商品的id,然后自动填充价格. 我们会对 车.发票 模块来完成此操作:

defmodule购物车.发票做
  使用星质.模式
  进口星质.变更集
  进口星质.查询#我们添加到查询

  # ....
  # schema, changeset和创建函数不会改变

  这里的新函数是项目_with_价格s
  执行得到_项目(params)
    项s = 项目_with_价格s(params[:发票_项目] || params["发票_项目"])
    枚举.map(项目, fn(项)-> 发票项.变更集(%发票项{}, 项)结束
  结束
  #新的功能来获取物品的价格
  删除项目_with_价格s(项目)
    项_id = 枚举.map(项目, fn(项) -> 项[: 项_id] || 项["项_id"] 结束)
    q = from(i in 项, select: %{id: 1.Id, 价格: I.Price},其中.^项_id中的Id)
    价格=回购.(问)

    枚举.map(项目, fn(项) ->
      项_id = 项[: 项_id] || 项[" 项_id "]
      %{
         项_id: 项_id,
         数量:项[: Quantity] || 项[" Quantity "],
         价格:枚举.find(价格s, fn(p) -> p[:id] == 项_id结束)[:价格] || 0
       }
    结束)
  结束

您将注意到的第一件事是我们添加了 星质.查询。,这将允许我们查询数据库. 新函数是 删除项目_with_价格s(项目) 哪一个搜索项目,找到并设置每个项目的价格.

首先, 删除项目_with_价格s(项目) 接收一个列表作为参数. 与 项_id = 枚举.map(项目, fn(项) -> 项[: 项_id] || 项["项_id"] 结束),迭代所有项,只得到 项_id. 如您所见,我们使用atom访问它们 : 项_id 或者字符串“项_id”,因为映射可以使用其中任何一个作为键. 查询 q = from(i in 项, select: %{id: 1.Id, 价格: I.Price},其中.^项_id中的Id) 会找到所有在里面的项目吗 项_id 并返回一个地图 项.id项.价格. 然后我们可以运行查询 价格=回购.(问) 哪个返回映射列表. 然后,我们需要遍历项目并创建一个将添加价格的新列表. 的 枚举.map(项目, fn(项) -> 遍历每个项目,找到价格 枚举.find(价格s, fn(p) -> p[:id] == 项_id结束)[:价格] || 0,并创建一个新的列表 项_id数量和价格. 这样,就不再需要在每个 发票_项目.

插入更多发票

大家记得,前面我们创建了一个地图 项目 这使我们能够访问 id 使用I的项目名称.e 项目(“口香糖”) “cb1c5a93 ecbf - 4 e4b - 8588 cc40f7d12364”. 这使得它易于创建 发票_项目. 让我们创建更多的发票. 再次启动控制台并运行:

ix -S混合
iex(1)> 回购.delete_所有(发票项); 回购.delete_所有(发票)

我们删除所有 发票_项目 和发票有一个空白的石板;

iex(2)> li = [%{项_id: 项目(“口香糖”), quantity: 2}, %{项_id: 项目["Milk"], 数量:1}]
iex(3)> 发票.创建(%{customer: "Mary Jane",日期:10月1日.日期.{{{}}}
iex(4)> li2 = [%{项_id: 项目["Chocolates"], quantity: 2}| li]
iex(5)> 发票.创建(%{customer: "Mary Jane",日期:10月1日.日期.Utc, 发票_项目: li2})
iex(5)> li3 = li2 ++ [%{项_id: 项目["Paper"], 数量:3}, % {项_id:项目(“大米”), 数量:1}, % {项_id:项目(“剪刀”), 数量:1}]
iex(6)> 发票.创建(%{customer: "Juan Perez",日期:10月1日.日期.Utc, 发票_项目: li3})

Now we have 3 发票; the first one with 2 项目, 第二个有3个项目, 第三个有6个项目. 我们现在想知道哪些产品是最畅销的? 为了回答这个问题, 我们将创建一个查询,根据数量和小计(价格x数量)查找最畅销的商品。.

defmodule购物车.项目做
  使用星质.模式
  进口星质.变更集
  进口星质.查询。

  别名购物车.{发票项, 项, 回购}

  # schema和changesset不变 
  # ...

  def 项目_by_quantity, do: 回购.所有项目_by(数量):

  def 项目_by_subtotal, do: 回购.所有项目_by(小计)

  删除项目_by(type)
    从项中的i,
    join: ii in 发票项, on: ii.项_id == I.id,
    选择:%{id: 1.Id, 名字。: I.Name, total: sum(场(ii, ^type))},
    group_by:我.id,
    Order_by: [desc: sum(场(ii, ^type))]]
  结束
结束

我们进口 星质.查询。 然后我们 别名购物车.{发票项, 项, 回购} 因此我们不需要在每个模块的开头添加车. 第一个函数 项目_by_quantity 调用 项目_by 函数,将 数量: 参数,并调用 回购.所有 执行查询. 这个函数 项目_by_subtotal 类似于前一个函数,但传递 :小计 参数. 现在我们来解释一下 项目_by:

  • from i in 项,此宏选择项模块
  • join: ii in 发票项, on: ii.项_id == I.id,在条件“项目”上创建连接.Id = 发票_项目.项_id”
  • 选择:%{id: 1.Id, 名字。: I.Name, total: sum(场(ii, ^type))}, 我们正在生成一个包含我们想要的所有字段的映射,首先我们从项中选择id和名称,然后进行运算符求和. 字段(ii, ^type)使用宏字段来动态访问字段
  • group_by:我.id我们按项目分组.id
  • Order_by: [desc: sum(场(ii, ^type))]] 最后按降序求和

到目前为止,我们已经用列表样式编写了查询,但我们可以用宏样式重写它:

删除项目_by(type)
  项
  |> join(:inner, [i], ii in 发票项, ii.项_id == I.id)
  |> select([i, ii], %{id: i.Id, 名字。: I.Name, total: sum(场(ii, ^type))})
  |> group_by([i, _], i.id)
  |> order_by([_, ii], [desc: sum(场(ii, ^type))])
结束

我更喜欢用列表形式编写查询,因为我发现它更容易读懂.

结论

我们已经介绍了使用星质可以在应用程序中做什么. 当然,还有很多东西你可以从 星质文档. 与星质, 你可以创建并发, 由于Erlang虚拟机,可以轻松扩展的容错应用程序. 星质为长生不老药应用程序中的存储提供了基础,并提供了函数和宏来轻松管理数据.

在本教程中,我们研究了 星质.模式, 星质.变更集, 星质.迁移, 星质.查询。, 星质.回购. 这些模块中的每一个都在应用程序的不同部分为您提供帮助,并使代码更显式,更易于维护和理解.

如果您想查看教程的代码,您可以找到它 在这里 GitHub上.

如果您喜欢本教程并对更多信息感兴趣,我建议您 凤凰城 (这里有一系列很棒的项目), 很棒的灵丹妙药, 这个演讲 比较ActiveRecord和星质.

就这一主题咨询作者或专家.
预约电话
鲍里斯·巴罗佐的头像
鲍里斯·巴罗佐

位于 拉巴斯,玻利维亚拉巴斯省

成员自 2013年8月28日

作者简介

作为一个企业家, Boris了解与客户和用户密切沟通的重要性,以便更好地根据实际需求塑造应用程序.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

工作经验

14

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.