I have two models:
defmodule TransactionApi.Messages.Event do
use Ecto.Schema
import Ecto.Changeset
alias TransactionApi.Messages.Event
alias TransactionApi.Messages.EventDetail
schema "events" do
field :city, :string
field :email, :string
field :ip, :string
field :sender, :string
field :status, :string
field :subject, :string
field :template, :string
field :ts, :utc_datetime
field :uniq_id, :string
field :user_agent, :string
has_many :event_details, EventDetail
timestamps()
end
@doc false
def changeset(%Event{} = event, attrs) do
event
|> cast(attrs, [:sender, :uniq_id, :ts, :template, :subject, :email, :status, :ip, :city, :user_agent])
|> cast_assoc(:event_details)
|> validate_required([:sender, :uniq_id, :ts, :subject, :email, :status])
end
end
defmodule TransactionApi.Messages.EventDetail do
use Ecto.Schema
import Ecto.Changeset
alias TransactionApi.Messages.EventDetail
alias TransactionApi.Messages.Event
schema "event_details" do
field :ts, :utc_datetime
field :url, :string
belongs_to :event, Event, foreign_key: :event_id
timestamps()
end
@doc false
def changeset(%EventDetail{} = event_detail, attrs) do
event_detail
|> cast(attrs, [:url, :ts, :event_id])
|> validate_required([:ts, :event_id])
end
end
I want to save the Event and it's associated EventDetail in my event controller:
def create(conn, %{"mandrill_events" => event_params}) do
params = parse_incoming event_params
with {:ok, %Event{} = event} <- Messages.create_event(params) do
conn
|> put_status(:created)
|> put_resp_header("location", event_path(conn, :show, event))
|> render("show.json", event: event)
end
end
This is how the params map I built looks like:
%{
city: "Oklahoma City",
email: "[email protected]",
event: "open",
event_details: [
%{"ts" => #DateTime<2013-04-04 21:31:51Z>, "url" => "http://mandrill.com"},
%{"ts" => #DateTime<2013-04-04 21:31:51Z>}
],
ip: "127.0.0.1",
sender: "[email protected]",
status: "sent",
subject: "This an example webhook message",
tags: ["webhook-example"],
template: nil,
ts: #DateTime<2018-02-12 12:33:48Z>,
uniq_id: "exampleaaaaaaaaaaaaaaaaaaaaaaaaa",
user_agent: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.8) Gecko/20100317 Postbox/1.1.3"
}
But my api returns an error : {"errors":{"event_details":[{"event_id":["can't be blank"]},{"event_id":["can't be blank"]}]}}
How do I make sure the associated EventDetail are correctly persisted with a foreign_key reference to the Event table, what's the "best practice" approach here?
Edit:
In Phoenix 1.3 it seems that they've added a create_[table_name] located in the context of the model and deals with changeset and insertion (I think that change came with wanting to seperate the web related part from the application, not sure though):
def create_event(attrs \\ %{}) do
%Event{}
|> Event.changeset(attrs)
|> Repo.insert()
end
I figured out a way to save a map containing nested association but since I'm new to Phoenix and Elixir (functional programming in general) I'm not sure this is the right/best practice approach.
%{
city: "Oklahoma City",
email: "[email protected]",
event: "open",
event_details: [
%{"ts" => #DateTime<2013-04-04 21:31:51Z>, "url" => "http://mandrill.com"},
%{"ts" => #DateTime<2013-04-04 21:31:51Z>}
],
ip: "127.0.0.1",
sender: "[email protected]",
status: "sent",
subject: "This an example webhook message",
tags: ["webhook-example"],
template: nil,
ts: #DateTime<2018-02-12 12:33:48Z>,
uniq_id: "exampleaaaaaaaaaaaaaaaaaaaaaaaaa",
user_agent: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.8) Gecko/20100317 Postbox/1.1.3"
}
event_controller.ex
def create(conn, %{"mandrill_events" => event_params}) do
params = parse_incoming event_params
with {:ok, %Event{} = event} <- Messages.create_event(params) do
event
|> Messages.add_event_details(params[:event_details])
conn
|> put_status(:created)
|> put_resp_header("location", event_path(conn, :show, event))
|> render("show.json", event: event)
end
end
Then in my Message context:
# persist an `event`
def create_event(%{event: event_params} \\ %{}) do
%Event{}
|> Event.changeset(event_params)
|> Repo.insert()
end
# persist a collection of `event_details`
def add_event_details(%Event{} = event, details) do
Enum.map(details, fn(event_detail) ->
event_detail
|> Map.put("event_id", event.id)
|> create_event_detail
end)
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With