Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir How to convert a Map struct to a Record struct

Tags:

elixir

I have a Record struct and a Map struct like:

defmodule Foo.Bar do
  defstruct boo: nil, baz: nil
end

defmodule Foo do
  require Record
  Record.defrecord :bar, Foo.Bar, [boo: nil, baz: nil]
end

I can convert the Record to Map like this:

defp update_map({k, v}, map), do: Map.update!(map, k, fn(_) -> v end)
defp rd2map(rd) do
  Foo.bar(rd) |> Enum.reduce(%Foo.Bar{}, &update_map/2)
end

But how can I convert the Map to a Record?

like image 254
jsvisa Avatar asked Mar 06 '15 03:03

jsvisa


2 Answers

Elixir Records are deprecated. The Record module that now exists in Elixir is only used for two things:

  1. to work with short, internal data
  2. to interface with Erlang records

This means you should probably not be using them unless you are trying to extract record information from an Erlang file.

Regarding your original question, here's how I would convert Erlang Records and Elixir Structs back and forth. Once you realize that a struct is just a Map that contains __struct__: Foo.Bar, and that a Record ist just a tuple that starts with {Foo.Bar, ...} it's pretty straightforward. The only tricky bit is that the information about the record fields is only available at compile time. Therefore, there is no dynamic way of building a record by default. As far as I know, you can only work around this by storing the field definitions somewhere, and use it to generate the struct and record definition. Later, the same source is re-used to build an ordered tuple with default values (i.e. the record). Remember, you really shouldn't use records. So, be warned: ugly hacks ahead ;-)

defmodule Foo.Bar do
  @fields [boo: nil, baz: nil]
  def fields, do: @fields
  defstruct @fields
end

defmodule Foo do
  require Record
  Record.defrecord :bar, Foo.Bar, Foo.Bar.fields
end

defmodule Foo.Utils do
  require Foo

  def record_to_struct(record) do
    [{:__struct__, Foo.Bar} | Foo.bar(record)] |> Enum.into(%{})
  end

  def struct_to_record(struct) do
    map = Map.from_struct(struct)
    for {key, default} <- Foo.Bar.fields, into: [Foo.Bar] do
      Dict.get(map, key, default)
    end |> List.to_tuple
  end
end
like image 185
Patrick Oscity Avatar answered Sep 20 '22 19:09

Patrick Oscity


All the disclaimers and information by Patrick is correct. You can't solve the problem at runtime without annotating the fields.

You can, however, solve this issue at compile time if you are converting from an Erlang record (which is mostly the only reason for using them). We do it on Elixir source code to convert Erlang's #file_info{} into %File.Stat{}:

https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/file/stat.ex

like image 44
José Valim Avatar answered Sep 17 '22 19:09

José Valim