Not having such good luck trying to store json via embedded_schema

In short I’m trying to store a subscription object

Which also looks something like.

{  
   "subscription":{  
      "endpoint":"https://fcm.googleapis.com/fcm/send/...",
      "expirationTime":null,
      "keys":{  
         "p256dh":"...",
         "auth":"..."
      }
   }
}

Ideally since I don’t really see much value in trying to normalize the json into a flat structor I’d like to just shovel this into a field called a subscription.

I was looking at embedded_schema but I’m not sure if I how I would deal with nodes ie:

"keys":{  
         "p256dh":"...",
         "auth":"..."
      }

I tried doing something like.

defmodule PolymorphicProductions.Messages.Subscription do
  use Ecto.Schema
  import Ecto.Changeset

  alias PolymorphicProductions.Messages.Subscription.Keys

  embedded_schema do
    field(:endpoint)
    field(:expirationTime)
    embeds_one(:keys, Keys)
  end

  def changeset(schema, params) do
    schema
    |> cast(params, [:endpoint, :expirationTime])
    |> put_keys(params)
  end

  def put_keys(cs, %{keys: keys} \\ %{keys: %{}}) do
    keys =
      %Keys{}
      |> Keys.changeset(keys)

    cs
    |> cast_embed(:keys, keys)
  end

  def put_keys(cs, _), do: cs
end

defmodule PolymorphicProductions.Messages.Subscription.Keys do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:p256dh)
    field(:auth)
  end

  def changeset(schema, params) do
    schema
    |> cast(params, [:p256dh, :auth])
  end
end

then in my main schema

  schema "push_subscribers" do
    embeds_one(:subscription, Subscription)
    timestamps()
  end

I don’t have this working well for me at the moment and it feels really wrong already. So I figured before I keep going down this path I’d thought I would check in with you all to see if I’m way off base.

PS: Also I’m using a real jsonb column not Array::Jsonb
ie:

  def up do
    alter table("push_subscribers") do
      add(:subscription, :jsonb, default: "[]")
    end

    execute("CREATE INDEX subscriptions_gin ON push_subscribers USING GIN (subscription);")
  end

  def down do
    alter table("push_subscribers") do
      remove(:subscription, :jsonb)
    end

    execute("DROP INDEX subscriptions_gin;")
  end

note the default: "[]"

YMMV

@derive {Poison.Encoder, except: [:__meta__]}
@primary_key false
embedded_schema do
   ....
end

My App.Schema includes defining a primary_key and had to turn it off for the embedded. I found I had to define the Poison.Encoder due to JSON encoding chocking on the __meta__ field Ecto creates.

I’ve recently had to store some additional structured data in one of my tables, but I simply took the path of encoding it to a JSON string and storing it as text :stuck_out_tongue:

Guess it depends on if you expect a structured search on the JSON contents in the database to be useful or not… in my case it’s just payload that needs to be sent back when I’m responding to the request that corresponds to the database record I stored.

Another option is to just store it as a :map field. That way you can store any arbitrary data. If you use an embedded schema then you need to store the same structure each time (and define it all upfront)