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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews