资讯专栏INFORMATION COLUMN

Elixir Ecto: 模型的嵌入(Embed)

ningwang / 2017人阅读

摘要:及其以上版本可以存储类似这样的非结构化数据作为一个的数据封装器提供了这些非结构话的数据到原生数据类型的序列化和反序列化嵌入的记录具有所有常规模型所具有的东西比如结构化的字段生命周期回调变更集下面研究如何把结构嵌入到模型当中使用嵌入单个结构可

Postgres 9.4 及其以上版本可以存储类似 arrays, json, jsonb 这样的非结构化数据. Ecto 作为一个 Elixir 的数据封装器, 提供了这些非结构话的数据到 Elixir 原生数据类型的序列化和反序列化. 嵌入的记录具有所有常规模型所具有的东西, 比如结构化的字段, 生命周期回调, 变更集. 下面研究如何把结构嵌入到 Ecto 模型当中.

使用 embeds_one 嵌入单个结构

可以使用 embeds_one 嵌入单个结构到 Ecto 模型当中. 嵌入的字段必须是 :map 这种非结构化的的数据类型. 在 Postgres 中, Ecto 使用了 jsonb 作为底层数据库字段的数据类型.

20160430043543_create_table_account.exs

defmodule EctoTest.Repo.Migrations.CreateTableAccount do
  use Ecto.Migration
  def change do
    create table(:accounts) do
      add :name, :string
      add :settings, :map  # 迁移脚本里面数据类型需要设置为 :map,
                           # 这样 Postgrex 适配器才能正确的把它映射到 jsonb[] 数据类型
    end
  end
end

执行项目目录中的 ./migrate 脚本, 内容如下

#!/bin/bash

mix ecto.migrate -r EctoTest.Repo
➜  ecto_test git:(master) ✗ ./migrate

12:38:10.930 [info]  == Running EctoTest.Repo.Migrations.CreateTableAccount.change/0 forward

12:38:10.930 [info]  create table accounts

12:38:10.943 [info]  == Migrated in 0.0s

定义模型

defmodule EctoTest.Model.Account do
  use Ecto.Schema
  alias EctoTest.Model.Settings
  schema "accounts" do
    field :name
    embeds_one :settings, Settings
  end
end

定义嵌入到 EctoTest.Model.Account 模型中的字段:

defmodule EctoTest.Model.Settings do
  use Ecto.Model
  embedded_schema do
    field :email_signature
    field :send_emails, :boolean
  end
end

嵌入的记录行为上和电信的关联(associations)一样, 只是它只能够通过变更集(changeset)来更新和删除

插入数据测试

# 模拟 EctoTest.get/1 返回一个 %EctoTest.Model.Account() 结构
account = %EctoTest.Model.Account{name: "test"}

# 创建Settings结构
settings = %EctoTest.Model.Settings{email_signature: "developerworks", send_emails: true}

# 创建变更集
changeset = Ecto.Changeset.change(account)

# 嵌入
changeset = Ecto.Changeset.put_embed(changeset, :settings, settings)

# 插入数据
changeset |> EctoTest.Repo.insert!

当父记录保存时, 会自动地的调用嵌入模型(Settings模型)中的 changeset/2 函数.

嵌入的记录可以通过.符号来访问, 因此不必考虑在一般的关联关系中的链接JOIN和预加载(preload)的问题:

account = Repo.get!(EctoTest.Model.Account, 1)
account.settings #=> %Settings{...}
使用 embeds_many 嵌入多个结构

embeds_many 允许你嵌入一个 Ecto 结构数组到一个关系数据库字段中. 在数据库底层 Postgres 使用了 arrayjsonb 数据类型来实现 embeds_many.

defmodule EctoTest.Repo.Migrations.CreateTablePeople do
  use Ecto.Migration

  def up do
    create table(:people) do
      add :name, :string
      add :address, {:array, :map}, default: []
    end
  end

  def down do
    drop table(:people)
  end
end

定义 Person, Address 模型:

defmodule EctoTest.Model.Person do
  use Ecto.Schema
  alias EctoTest.Model.Address

  schema "people" do
    field :name
    embeds_many :addresses, Address
  end

  def test_insert do
    changeset = Ecto.Changeset.change(%__MODULE__{})
    addresses = [
      %Address{street_name: "20 Foobar Street",city: "Boston",state: "MA",zip_code: "02111"},
      %Address{street_name: "1 Finite Loop", city: "Cupertino",state: "CA",zip_code: "95014"}
    ]
    changeset = Ecto.Changeset.put_embed(changeset, :addresses, addresses)
    Repo.insert!(changeset)
  end
end


defmodule EctoTest.Model.Address do
  use Ecto.Schema

  embedded_schema do
    field :street_name
    field :city
    field :state
    field :zip_code
  end
end

设置多对多字段的值

iex(1)> EctoTest.Model.Person.test_insert

13:44:25.731 [debug] QUERY OK db=9.4ms
INSERT INTO "people" ("addresses") VALUES ($1) RETURNING "id" [[%{city: "Boston", id: "2de7fd44-a1cf-44cd-a060-d6260325ac90", state: "MA", street_name: "20 Foobar Street", zip_code: "02111"}, %{city: "Cupertino", id: "bb65fd96-f1b1-4f0d-a92e-712884e40a7c", state: "CA", street_name: "1 Finite Loop", zip_code: "95014"}]]
%EctoTest.Model.Person{__meta__: #Ecto.Schema.Metadata<:loaded>,
 addresses: [%EctoTest.Model.Address{city: "Boston", id: "2de7fd44-a1cf-44cd-a060-d6260325ac90", state: "MA",
   street_name: "20 Foobar Street", zip_code: "02111"},
  %EctoTest.Model.Address{city: "Cupertino", id: "bb65fd96-f1b1-4f0d-a92e-712884e40a7c", state: "CA",
   street_name: "1 Finite Loop", zip_code: "95014"}], id: 1, name: nil}

has_many 一样访问:

iex(2)> person = EctoTest.Repo.get!(EctoTest.Model.Person, 1)

13:45:04.634 [debug] QUERY OK db=1.4ms decode=9.5ms
SELECT p0."id", p0."name", p0."addresses" FROM "people" AS p0 WHERE (p0."id" = $1) [1]
%EctoTest.Model.Person{__meta__: #Ecto.Schema.Metadata<:loaded>,
 addresses: [%EctoTest.Model.Address{city: "Boston", id: "2de7fd44-a1cf-44cd-a060-d6260325ac90", state: "MA",
   street_name: "20 Foobar Street", zip_code: "02111"},
  %EctoTest.Model.Address{city: "Cupertino", id: "bb65fd96-f1b1-4f0d-a92e-712884e40a7c", state: "CA",
   street_name: "1 Finite Loop", zip_code: "95014"}], id: 1, name: nil}

iex(3)> person.addresses
[%EctoTest.Model.Address{city: "Boston", id: "2de7fd44-a1cf-44cd-a060-d6260325ac90", state: "MA",
  street_name: "20 Foobar Street", zip_code: "02111"},
 %EctoTest.Model.Address{city: "Cupertino", id: "bb65fd96-f1b1-4f0d-a92e-712884e40a7c", state: "CA",
  street_name: "1 Finite Loop", zip_code: "95014"}]
权衡

记录的嵌入是 Ecto 提供的多个强大的能之一. 很容易给嵌入的记录添加字段, 不需要运行时修改数据库结构. 这些都是优势, 但有的场景下也会带来一些问题, 因此需要权衡是否应该使用嵌入.

使用非结构化数据, 丢失了SQL数据库提供的关系特性, 例如, 一个记录只能嵌入到一个父表中, 因此不能使用嵌入建模多对多关系, 也不能使用数据库约束, 虽然可以在应用程序中检查数据的完整性, 但是更好的方式是在数据库级别进行验证, 以保障数据的完整性.

示例代码

https://github.com/developerworks/ecto_test

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38940.html

相关文章

  • Elixir Ecto: PostgreSQL大自增长主键设置

    摘要:要解决的问题数据库中存储的行数超过了类型所能容纳的数量因此需要采用类型的整数作为主键的取值范围为到的取值范围为到完整的字段的数据类型可以参考这里迁移脚本要点的参数要设置为通过宏指定主键列类型为模型的声明要点主键要声明为类型完 要解决的问题 数据库中存储的行数超过了 serial 类型所能容纳的数量, 因此需要采用 bigserial 类型的整数作为主键serial 的取值范围为: 1...

    yvonne 评论0 收藏0
  • Elixir Ecto: 使用Geo库操作空间数据(地理坐标)

    摘要:简介数据格式空间数据的文本标识空间数据的二进制标识基于对象表示法的地理空间信息数据交换格式的库提供了上述三种格式的相互转换函数配置添加依赖配置扩展创建删除扩展的移植脚本脚本内容执行移植插入数据轨迹点粤获取经纬度粤查询联系如何查询字段例 简介 数据格式 Abbr Fullname Description WKT Well Known Text 空间数据的文本标识 WKB ...

    Blackjun 评论0 收藏0
  • Elixir Ecto: 在Postgresql中插入二进制数据

    摘要:本文以用一个在数据库中保存图片信息为例展示如何向中插入二进制数据首先我们要知道中唯一一种二进制数据的类型为表示字节数组或字节序列对应于和中的下面是一个示例迁移脚本这里也可以为实际上在执行数据库中字段的类型就是当然除了图片文件以外你还 本文以用一个在数据库中保存图片信息为例, 展示如何向 Postgresql 中插入二进制数据. showImg(https://segmentfault....

    CarterLi 评论0 收藏0
  • Elixir Ecto: 在Postgres中使用UUID类型主键

    摘要:使用作为主键的目的降低类型这种自增线性特征作为随机生成的字符串让更离散增强系统的反爬虫能力至少避免通过的线性增加来爬取内容这种最简单的爬取方式使用主键的是那种方式各有优缺点可按实际需求自行权衡在应用程序中生成并插入到数据库通过使用扩展自动生 使用UUID作为主键的目的 降低Serial类型这种自增ID线性特征, UUID作为随机生成的字符串, 让ID更离散, 增强系统的反爬虫能力(至...

    OnlyLing 评论0 收藏0
  • 前端速查表大全,分享一些技术和工具简明教程

    摘要:这个速查表主要是分享互联网上一些比较常用的工具和技术常用内容,如编辑器的快捷键的命令行的选择器的属性等,这个列表简单收集了常用的工具,可以收藏用于平时的备忘录,需要用到的时候可以及时查阅。 这个速查表主要是分享互联网上一些比较常用的工具和技术常用内容,如编辑器的快捷键、git的命令行、jQuery的API选择器、CSS的flexbox属性等,这个列表简单收集了常用的工具,可以收藏用于平...

    xiaochao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<