S1r-Lanzelot
S1r-Lanzelot

Reputation: 2266

Cast certain changeset fields into an ecto :map field

I have a changeset that looks like this:

%{
  creator_id: "4",
  name: "a test with GraphQL",
  place_id: "13",
  entree_id: "8",
  base: "wheat",
  type: "food"
}

My ecto schema is as follows:

schema "items" do
  field :type, :string
  field :name, :string
  field :data, :map

  belongs_to :creator, Name.SubName.Creators
  belongs_to :place,   Name.SubName.Places
  belongs_to :entree,  Name.SubName.Entrees

  timestamps()
end

As you can see, base is not a field that is in the ecto schema, I want to cast base into the data field so that it is a map type, ie: {"base":"wheat"}. I am using some form a single table inheritance here.

I am trying to add a record to my items table. Right now when I read from the table, I cast the data field to an embedded schema (depending on the type) that contains my base field just fine. Where I struggle is getting this field back into the database.

I tried using something like Ecto.Changeset.cast(params[:base], Map.keys(%{data: :map})) but obviously that just gets the value which is clearly not a valid map. Additionally this would be painful to specify for all the unique fields that are associated with my other embedded schemas that represent each type.

embeds_one and embeds_many do not apply to this situation because the data ecto field represents different embedded schema types. So in this case my question is less about embedded schema use and more about picking out particular fields in a changeset and turning them into a map. Half of me just thinks I should make a distinct table for each type and then I don't have to worry about this.

Upvotes: 0

Views: 2198

Answers (1)

Sheharyar
Sheharyar

Reputation: 75740

You can use Map.split/2:

@required_fields [:name, :type, :creator_id, :place_id, :entree_id]
@all_fields [:data | @required_fields]

def changeset(struct, params) do
  {allowed, others} = Map.split(params, @required_fields)
  params = Map.put(allowed, :data, others)

  struct
  |> cast(params, @all_fields)
  |> # ...
end

Important thing to note here is that this example only works for maps with atom keys, you'll have to modify it a bit to work with both string and atom keys. You might also consider specifying an empty map as the default value for :data.

Upvotes: 2

Related Questions