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: "example.webhook@mandrillapp.com",
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: "example.sender@mandrillapp.com",
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"]}]}}
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 separate the web related part from the application, not sure though):
def create_event(%{event: event_params, event_details: event_details_params} \\ %{}) do
event_changeset = %Event{} |> Event.changeset(event_params)
events =
Multi.new
|> Multi.insert(:event, event_changeset)
|> Multi.run(:event_details, fn %{event: event} ->
event_details_changeset =
%EventDetail{event_id: event.id}
|> EventDetail.changeset(event_details_params)
Repo.insert(event_details_changeset)
end)
Repo.transaction events
end
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?
Right now I’m getting this error
Request: POST /api/events
** (exit) an exception was raised:
** (MatchError) no match of right hand side value: #Ecto.Changeset<action: nil, changes: %{city: "Oklahoma City", email: "example.webhook@mandrillapp.com", ip: "127.0.0.1", sender: "example.sender@mandrillapp.com", status: "sent", subject: "This an example webhook message", ts: #DateTime<2018-02-12 19:29:18Z>, 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"}, errors: [], data: #TransactionApi.Messages.Event<>, valid?: true>
(transaction_api) lib/transaction_api/messages/event.ex:30: TransactionApi.Messages.Event.changeset/2
(transaction_api) lib/transaction_api/messages/messages.ex:55: TransactionApi.Messages.create_event/1
(transaction_api) lib/transaction_api_web/controllers/event_controller.ex:27: TransactionApiWeb.EventController.create/2
(transaction_api) lib/transaction_api_web/controllers/event_controller.ex:1: TransactionApiWeb.EventController.action/2
(transaction_api) lib/transaction_api_web/controllers/event_controller.ex:1: TransactionApiWeb.EventController.phoenix_controller_pipeline/2
(transaction_api) lib/transaction_api_web/endpoint.ex:1: TransactionApiWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
(transaction_api) lib/transaction_api_web/endpoint.ex:1: TransactionApiWeb.Endpoint.plug_builder_call/2
(transaction_api) lib/plug/debugger.ex:99: TransactionApiWeb.Endpoint."call (overridable 3)"/2
(transaction_api) lib/transaction_api_web/endpoint.ex:1: TransactionApiWeb.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /Users/cyrusghazanfar/Desktop/elixir/transaction_api/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4