storing structs in mnesia, is there an eaiser way?

853 views
Skip to first unread message

Alex Shneyderman

unread,
Nov 5, 2014, 8:57:34 PM11/5/14
to elixir-l...@googlegroups.com
mnesia does not like maps; it prefers records. So, if i want to use both elixir (and its structs) i'd need to do some converting between records and structs. is there an easy way to achieve this? Sounds like it should be straight forward and yet whatever partial solution I have seems like a hack. 

defmodule P do   
  defstruct id: :undefined,
                 name: :undefined,
                age: :undefined
 require Record
 m = Map.delete(%P{}, :__struct__)
 Record.defrecord :newrec, P, Keyword.new(m)
end

this generated macros to produce records but I am struggling with conversions.

Any suggestions?





Saša Jurić

unread,
Nov 6, 2014, 2:48:51 AM11/6/14
to elixir-l...@googlegroups.com
Records are just tuples. You can specify table fields manually and then provide tuples in the proper order:

iex(1)> :mnesia.create_schema([node()])
iex(2)> :mnesia.start
iex(3)> :mnesia.create_table(Person, [attributes: [:id, :name, :age]])
iex(4)> :mnesia.dirty_write({Person, 1, "Alice", 30})
iex(5)> :mnesia.dirty_read({Person, 1})
[{Person, 1, "Alice", 30}]

To automatically convert from the struct to the tuple, you could rely on Map.keys and Map.values to fetch fields and values of the struct at runtime, something along the line of:

List.to_tuple([
  person.__struct__ |
  person |> Map.delete(:__struct__) |> Map.keys
])

When converting the opposite way, you could use :mnesia.table_info(Person, :attributes) to fetch the list of Mnesia table fields, and dynamically populate the map. 


If you mean to query the table only using id field, than you could simplify your life by storing the complete map(struct) directly into second field, with the first field holding the id value. Then you don't need to convert anything and can simply wrap the struct in {Person, person.id, person} and store. 

Alex Shneyderman

unread,
Nov 6, 2014, 8:23:39 AM11/6/14
to elixir-l...@googlegroups.com


On Thursday, November 6, 2014 2:48:51 AM UTC-5, Saša Jurić wrote:
Records are just tuples. You can specify table fields manually and then provide tuples in the proper order:

iex(1)> :mnesia.create_schema([node()])
iex(2)> :mnesia.start
iex(3)> :mnesia.create_table(Person, [attributes: [:id, :name, :age]])
iex(4)> :mnesia.dirty_write({Person, 1, "Alice", 30})
iex(5)> :mnesia.dirty_read({Person, 1})
[{Person, 1, "Alice", 30}]

To automatically convert from the struct to the tuple, you could rely on Map.keys and Map.values to fetch fields and values of the struct at runtime, something along the line of:

List.to_tuple([
  person.__struct__ |
  person |> Map.delete(:__struct__) |> Map.keys
])

you probably meant 

List.to_tuple([
  person.__struct__ |
  person |> Map.delete(:__struct__) |> Map.values
])

the only problem I see with this is  that  Map.values does not guarantee the order, which is important in tuples.

Sasa Juric

unread,
Nov 6, 2014, 8:53:28 AM11/6/14
to elixir-l...@googlegroups.com

On 06 Nov 2014, at 14:23, Alex Shneyderman <a.shne...@gmail.com> wrote:

the only problem I see with this is  that  Map.values does not guarantee the order, which is important in tuples.

Yes, good point! I’m unsure to what extent is ordering preserved and on what it depends. Quick experimentation indicates that fields are sorted, but I didn’t pursue this further.

However, I’d guess that Map.keys(%Person{}) would always return keys in the same order, as long as the structure definition doesn’t change. So you could use this to fetch your values one by one in the consistent order.

If that doesn’t work, you could enforce explicit field ordering:

defmodule Person do
  @fields [id: nil, name: nil]
  defstruct @fields

  def fields, do: unquote(Keyword.keys(@fields))
end

And rely on Person.fields to perform consistent conversion.


All of this can fail when you change your structure definition, but I think the same issue holds with pure Erlang and records. See here: http://www.tamewildsystems.com/2010/05/mnesia-one-year-later-part-2.html section “Adopt the key-value approach”. Which is another reason to consider {table, key, generic_data} for storage, if you can live with it.

Eric Meadows-Jönsson

unread,
Nov 6, 2014, 9:29:28 AM11/6/14
to elixir-l...@googlegroups.com
According to [maps](http://www.erlang.org/doc/man/maps.html) docs ordering is arbitrary so I would not rely on it.

You can sort based on the map key and then return only the values.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Eric Meadows-Jönsson
Reply all
Reply to author
Forward
0 new messages