Problem trying to pipe a embedded_schema value into a %__MODULE__{} struct. ** (ArgumentError) errors were found at the given arguments: * 1st argument: not an atom

Hi All,

I’m attempting to follow this multi-step form guide

When I attempt to pipe my field email into the defined struct I get the following runtime error;

[error] #PID<0.931.0> running CensusWeb.Endpoint (connection #PID<0.930.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /survey
** (exit) an exception was raised:
** (ArgumentError) errors were found at the given arguments:

  • 1st argument: not an atom

    :erlang.apply(%Census.Survey.Entry{__meta__: #Ecto.Schema.Metadata<:built, "entries">, age: 0, annual_income: 0, email: "n/a", ethnicity: "n/a", first_name: "n/a", household_income: 0, id: nil, inserted_at: nil, last_name: "n/a", marital_status: "n/a", updated_at: nil}, :email, [%CensusWeb.SurveyLive.EmailComponent{email: ""}])
    (census 0.1.0) lib/census_web/live/survey_live/email_component.ex:104: CensusWeb.SurveyLive.EmailComponent.from_entry/1
    (census 0.1.0) lib/census_web/live/survey_live/email_component.ex:24: CensusWeb.SurveyLive.EmailComponent.update/2
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/utils.ex:394: Phoenix.LiveView.Utils.maybe_call_update!/3
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/diff.ex:608: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
    (elixir 1.12.3) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (stdlib 3.17.2.1) maps.erl:410: :maps.fold_1/3
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/diff.ex:582: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/diff.ex:145: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/static.ex:244: Phoenix.LiveView.Static.to_rendered_content_tag/4
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/static.ex:126: Phoenix.LiveView.Static.render/3
    (phoenix_live_view 0.17.12) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
    (phoenix 1.6.13) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
    (census 0.1.0) lib/census_web/endpoint.ex:1: CensusWeb.Endpoint.plug_builder_call/2
    (census 0.1.0) lib/plug/debugger.ex:136: CensusWeb.Endpoint."call (overridable 3)"/2
    (census 0.1.0) lib/census_web/endpoint.ex:1: CensusWeb.Endpoint.call/2
    (phoenix 1.6.13) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
    (cowboy 2.9.0) /home/tafol20/misc/code_practice/phoenix/sample_projects/census/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
    (cowboy 2.9.0) /home/tafol20/misc/code_practice/phoenix/sample_projects/census/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
    (cowboy 2.9.0) /home/tafol20/misc/code_practice/phoenix/sample_projects/census/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
    

The code snippets I believe are relevant are the following:

 @primary_key false
  embedded_schema do
    field :email, :string, default: ""
  end

  @impl Phoenix.LiveComponent
  def update(assigns, socket) do
    email_event = from_entry(assigns.entry)
    params = %{}
    changeset = changeset(email_event, params)

    {:ok,
    socket
    |> assign(assigns)
    |> assign(:email, email_event)
    |> assign(:email_changeset, changeset)
    |> assign_computed(changeset)
  }
  end

  def from_entry(entry) do
    %__MODULE__{}
    |> entry.email
  end

the error it self occurs on the

|> entry.email

I assumed, it was an issue where I need to pass a atom like :ok or :email but I haven’t been successful implementing that. looking for suggestions, or help understanding what I am missing in my understanding. Also let me know if I should add more code, from the file. Thanks for your help.

Yes. But I do not understand what you expect from that line of code. What it should you according to your idea?

well I believe I am adding my entry.email value to the %_MODULE_{} struct that I defined. So that the struct will hold the value while I proceed with my form. That code snippet is a part of this;

  def from_entry(entry) do
    %__MODULE__{}
    |> entry.email
  end

I think it is meant to be kept in the %_MODULE_{}, but it is expecting a different set of values than just entry.email. That is what I am understanding/assuming.

That looks like meaningless code and I am a little surprised it even compiles. What is it that it should do? Should it put the value of entry.email inside the email field of the newly created %__MODULE__{} instance?

Honestly I am surprised too. It’s some kind of runtime error that happens when I load the page that contains this LiveComponent. Yea I think what I am trying to do is add the current form submitted values that are held in that entry struct, and pass them to the _MODULE_ struct somehow. This is the code from the guide that I am attempting to follow:

def from_event(event, profile_timezone) do
    %__MODULE__{timezone: event.start_at_tz || profile_timezone || "Etc/UTC"}
    |> put_start_at_date(to_date(event.start_at))
    |> put_start_at_time(to_time(event.start_at))
    |> put_end_at_date(to_date(event.end_at))
    |> put_end_at_time(to_time(event.end_at))
    |> put_end_at(event.end_at)
    |> put_start_at(event.start_at)
    |> put_duration(calc_duration(event.start_at, event.end_at))
  end

here you an see he uses some kind of helper function to pass a result, to the %_MODULE_ but I don’t know what any of these functions do, he also doesn’t include an example of any of them in the guide. I’m wondering what I should be passing here. I assumed they were cleaning and calculating functions and could be omitted. The guide I am following is Phoenix LiveView: Multi-step forms · Bernheisel.

Hmmm, still unclear.

Is there a module that has an email function? Because doing this:

%__MODULE__{}
|> entry.email

is equivalent to this:

entry.email(%__MODULE__{})

Which means that entry, whatever it points to, has a function named email. Is there a module in the project that has an email function accepting only one struct / map argument?

If not, then somebody misspelled / mistyped something.

Also, looking at the context where the function is called:

    email_event = from_entry(assigns.entry)

Makes me think that the function must be rewritten as:

  def from_entry(entry) do
-    %__MODULE__{}
-    |> entry.email
+    entry.email
  end

And if that’s the case then the (IMO) more ergonomic Elixir way would be to write it as:

def from_entry(%{email: email}), do: email

But please have in mind this is only a guess. You might end up e.g. returning only an email string whereas functions downstream might expect an entire struct (EmailEvent by the looks of it). So if you figure you’ll try to follow this advice, please check and make sure everything is working as it should.

1 Like

entry is a valid variable. Yes it points to a struct when called, but it could also point to an atom being a known module name, which would make the code work. So the compiler cannot know this is going to blow up.

1 Like

Sure. It’s just that such code would never pass my code review – or that of any other Elixir dev that I’ve worked with. Definitely was surprised to see it could even exist. :grin:

You can find this in a handful of places, e.g. with ecto multis:

Ecto.Multi.run(Ecto.Multi.new(), :step_name, fn repo, _changes -> 
  {:ok, repo.preload(struct, […])}
end)
1 Like

Again, sure, but look at entry.email – it doesn’t tell you much. With repo.preload that definitely gives some hints.

Might be a preference of part of the Elixir devs in the end and not an objective fact, maybe, but I’d always shoot for the clearest and most intuitive code (if that doesn’t come with a high cost).

Looking at the article, it seems he has misinterpreted the code.
The from_entry/1 in the article creates/fills the %__MODULE__{} with values from entry using a number of convenience functions, eg put_start_at_date(to_date(event.start_at)).
He doesn’t explain what those functions do, but the comment at the bottom says

  # I'll leave some of these helper functions out, but they're essentially
  # providing nil-safety, applying defaults, and calculating from other fields

So to_date/1 probable takes the date in string format and tries to convert it to a date, and put_start_at_date/2 takes the struct and that value and puts it into the struct if it is valid.
It’s a pity he did not at least include one of those functions in his article.

2 Likes

So taking some of the advice that was given, I was able to work through it and found that I could pass a value to the module struct like so;

def from_entry(entry) do
  %__MODULE__{email: entry.email}
end

This didn’t pass any errors down stream, and seems to work as expected. Thanks for your advice.

Yeah, figured that somebody misunderstood Elixir’s pipe syntax. Glad you deciphered it and made it work.

1 Like

It’s great gamification to mark your own post as the solution too!

1 Like