How do I use the Postgres JSONB / Postgrex JSON extension?

Hi all,

I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage.

I’m trying to use Postgres’ JSONB via the :map datatype.

Here’s my migration to add it to my table:

defmodule MyApp.Repo.Migrations.AddMapFieldToUser do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :data, :map
    end
  end
end

Here’s my schema:

defmodule MyApp.User do
  use MyApp.Web, :model
  use Coherence.Schema

  schema "users" do
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :data, :map

    coherence_schema()

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:first_name, :last_name, :email] ++ coherence_fields)
    |> unique_constraint(:email)
    |> validate_required([:first_name, :last_name, :email])
    |> validate_coherence(params)
  end
end

I’ve been trying to set values into the data map using a changeset, but I haven’t had any luck. I’ve tried using both a JSON string and a map.

JSON string:

changeset = User.changeset(retrieved_user, %{data: "{test:'awesome'}"})
#Ecto.Changeset<action: nil, changes: %{}, errors: [],
 data: #MyApp.User<>, valid?: true>

Map:

changeset = User.changeset(john, %{data: %{test: "awesome"}})
#Ecto.Changeset<action: nil, changes: %{}, errors: [],
 data: #PodioBackup.User<>, valid?: true>

You’ll see that the changes map is empty.

Which leads me to believe that I am clearly missing something! :sweat_smile: And I’m thinking maybe I need to add this JSON extension that many websites are referring to: https://github.com/elixir-ecto/postgrex/blob/master/lib/postgrex/extensions/json.ex

How do I actually use that JSON extension? And, what am I doing wrong?

Thanks for the help in advance! :slight_smile:

5 Likes

You’re missing a cast of :data in your changeset.

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:first_name, :last_name, :data] ++ coherence_fields)
    |> ...
  end
7 Likes

Somehow I knew it was going to be something like that.

Thanks @alexgaribay. That was it!

In this

shouldn’t the “data” be of type “jsonb” instead of “map”?

The first argument (:data) in add :data, :map is the name of the table column. The type is the second argument (:map).

Here’s the documentation for Ecto.Migration.add.

yes, but my question is about “map” vs “jsonb”

david, your eyes are soooo green!

They are equivalent when using postgres. map is an abstract type and each database adapter can choose the actual representation - for postgres it’s jsonb.

7 Likes

So is a correct conclusion that using :map would be preferable over using :json, to allow you to switch database adapters, if desired, later on?

3 Likes