kanishka

kanishka

Are there patterns similar to having a private constructor along with a public build/validate function to guide users of a struct module to build valid instances? I understand that you may not be able to completely stop a user from creating a struct with invalid field values, but just wonder if there are patterns for something along those lines. I understand that you can use Ecto validations with embedded schemas to express some constraints, but wondering if there some pattern for plain structs

Showing Posts 1 to 10

sodapopcan

sodapopcan

There is not. A common pattern is to define a new/1 for this and you just have to document that that is what should be used.

Edit: sorry “there is not” referring to not being able to mail a struct as private.

kartheek

kartheek

Elixir does have concept of constructors - they are from OOPS world. There is map and there is struct which is map with additional field __struct__. There are functions which operate on data. There are Modules which group functions together. I might be oversimplifying things..

I use @enforce_keys and init(opts) to initialise structs (not everywhere though).

@enforce_keys [keys_list]
defstruct [keys]

def init(opts \\ %{}) do 
  # do some validations, and throw if needed
  %__MODULE__{
    #set data
  }
end
Eiji

Eiji

There is no solution which covers all use cases, but a pure Elixir validation is pretty simple to write, for example with code below:

defmodule Example do
  @required_keys ~w[sample]a
  @enforce_keys @required_keys
  defstruct [:sample]

  def sample(data) when is_list(data) do
    @required_keys
    |> Enum.reject(&Keyword.has_key?(data, &1))
    |> sample(data)
  end

  defp sample([], data) do
    case validate(data) do
      {:error, reason} -> {:error, reason}
      data -> struct(__MODULE__, data)
    end
  end

  defp sample(missing_fields, _data) do
    {:error, "missing fields: " <> Enum.join(missing_fields, ", ")}
  end

  def sample!(data) when is_list(data) do
    case validate(data) do
      {:error, reason} -> raise reason
      data -> struct!(__MODULE__, data)
    end
  end

  defp validate(data) do
    Enum.reduce_while(data, %{}, fn {key, value}, acc ->
      case validate(key, value) do
        # when value is validated put it using key
        {:ok, value} -> {:cont, Map.put(acc, key, value)}
        # otherwise when validation fails return error 
        {:error, reason} -> {:halt, {:error, reason}}
      end
    end)
  end

  defp validate(:sample, value) when is_integer(value), do: {:ok, value}
  defp validate(:sample, _value), do: {:error, "sample is not an integer"}
  defp validate(_key, value), do: {:ok, value}
end

the struct would not be validated (except @enforce_keys when using it “by hand” like %Example{}. Officially there is no support for preventing others to use write struct by hand.

dimitarvp

dimitarvp

Or you can use the domo library. :person_shrugging:

kanishka

kanishka OP

I will probably use this or new, and return a Result for this quick and dirty case and keep domo in mind.

kanishka

kanishka OP

My current understanding is that Ecto is popular for validating DB persisted structs, and there is some momentum around libraries that validate data received from external services. It looks like domo has some users, but it’s not used everywhere, and the norm for structs that are not persisted are various patterns mentioned above? I am curious if you think domo will keep growing and start to become a library that people use in most projects, or if there is some resistance to introducing a library for logic that most people feel they can implement on their own quickly?

Eiji

Eiji

I would say that any library to achieve something like that would need to:

  1. be a part of some generator provided by Elixir core team or related (ecto, phoenix etc.)

  2. have guides about it everywhere (just like now it’s for phoenix for example)

  3. have a big community (which comes from points 1 and 2, but it may be required by some companies) - especially a big number of users who test every release

Look that ecto does not have it’s own generators, but support for it is important in phoenix generators. It’s widely adopted, well tested and especially every bigger/complete/serious Elixir/Phoenix tutorial adds lots of information about it.

On the other side if we have x number of libraries doing similar thing where:

  1. x > 1 and every of those libraries would have active support
  2. it’s not a part of any generator
  3. it’s not commonly mentioned “on every tutorial”
  4. it has at most a limited number of enthusiasts who simply likes it

there is no chance that one library would be used in most projects especially if it would not be needed in simplest cases. Developers for each project would use at most one library which:

  1. They knows/like the most
  2. The library features covers app-specific use cases
  3. They convince a leader that it needs to be used

Look that old Phoenix templates would be used almost only in unmaintained projects. Almost all projects already moved or would move to heex pretty soon not because it’s best for everything and there would never be a better solution, but because a Phoenix core team (of course they had a reason for it) would drop support for it in newest releases.

Personally I’m subscribing Phoenix.HTML goes slim · Issue #372 · phoenixframework/phoenix_html · GitHub issue. You can answer yourself … Do you know and use in most projects temple library? I have used it before introducing heex engine.

Of course I’m not saying that only Elixir/Ecto/Phoenix core teams are doing a great libraries, but the chances to adopt their libraries in most projects are much higher because of support (as above - supported in official generator), respect for their knowledge/skills and a huge communities around them.

dimitarvp

dimitarvp

It is. I only used domo once and liked it but a lot of people are not happy with compilation and macro hackery and I can understand and sympathize.

In your case, a plain old struct + @enforce_keys + a new function + a non-adversarial usage will do the trick just fine.

kartheek

kartheek

I have been using these for a long time:

You mention about something about stopping a user from creating a struct with invalid fields. User is another developer working on the project or user who will be using the application?

There is NimbleOptions NimbleOptions — NimbleOptions v1.1.1.

Elixir community is small, it does not have the volume/size like other languages:

  • in phoenix forum, there are around 30-40 posts with activity in past week. Look at site stats section About - Elixir Programming Language Forum for forum activity levels.
  • many projects being discussed in this forum are hobby or learning projects, they won’t be making to production.
  • one can see a thousand stars for a JS library on Github which does something trivial.
  • can’t really interpolate these numbers to python or js world.

This does not translate to a problem with language, framework or libraries. Adoption numbers, popularity and momentum numbers are on a different scale in elixir world.

json_xema - i have been using from around 2019/2020(i don’t remember exact date). It just works, it has 57 stars and 4 forks. On hex.pm it has around 200 downloads a day.

sabiwara

sabiwara

Elixir Core Team

In addition to other answers, if you are using typespecs and dialyzer it might be interesting to add a definition of an opaque type for your struct to achieve some kind of encapsulation.

defmodule MyStruct do
  @opaque t :: %__MODULE__{foo: integer()}
  @enforce_keys [:foo]
  defstruct [:foo]

  # functions in this module can create or manipulate internals of t()
  @spec new(integer()) :: t()
  def new(foo) when is_integer(foo), do: %__MODULE__{foo: foo}
end

defmodule OtherModule do
  # dialyzer will complain since you can't assume the internals of MyStruct.t() outside of MyStruct
  @spec create_struct() :: MyStruct.t()
  def create_struct() do
    %MyStruct{foo: 0}
  end
end

Dialyzer will report:

other_module.ex:3:contract_with_opaque
The @spec for OtherModule.create_struct/0 has an opaque
subtype MyStruct.t() which is violated by the success typing.

While this doesn’t offer strong guarantees, it can help detect cases that are by-passing your constructor in your codebase.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews