Hi there. I need to enable CRUD for a PostgreSQL array column and am hoping someone can advise if the approach I have in mind is suitable, and if so help with how to go about it. I’m relatively new to non-trivial programming and Elixir and am struggling to see how to achieve it though I think it should be relatively simple for someone who’s used to parsing and modifying structured data.
Bacground
XML data like the following is accepted by the HTTP API:
<data>
<SingleTemperatureOffset>
<Temperature>10</Temperature>
<Offset>0</Offset>
</SingleTemperatureOffset>
<SingleTemperatureOffset>
<Temperature>100</Temperature>
<Offset>10</Offset>
</SingleTemperatureOffset>
</data>
The temperature offsets are stored in a PostgreSQL integer[] array column called offsets
. Example value:
{{10,0},{100,10}}
There may be a varying number of inner arrays, and each should contain two integers.
The database table creation migration’s column:
add :offsets, {:array, :smallint}, [null: false]
The model’s schema field:
field :offsets, {:array, {:array, :integer}}
As of yesterday, the storage of the temperature offsets as above is working. (Yay!)
Enabling CRUD
I’m now trying to provide for creation and editing of such temperature offsets in the HTML admin area. (Other fields such as text and simple numbers are working fine.)
As part of the admin flow, API submissions such as the above are listed with a link to a “new” action/page (which saves a different but similar model) in which the form inputs’ values are populated with the submitted data. In the case of the offsets input, this results in, for example:
�d
In the new action in the controller:
IO.inspect(changeset.changes.offsets)
IO.inspect(changeset.changes.offsets, char_lists: :as_lists)
This shows:
[[10, 0], 'd\n']
[[10, 0], [100, 10]]
I’m thinking that a text input accepting (UTF-8 string?) values such as either {{10,0},{100,10}}
or [[10,0],[100,10]]
would be fine, and hopefully relatively simple to achieve as opposed to other approaches involving multiple form inputs.
To obtain a value such as IO.inspect
's output (when it’s set to output as lists), could I use Erlang’s io_lib:format
(which I haven’t got my head round yet)? If so, how could I get the resultant string from the controller to the form input value? My “new” action/function as it stands (ToSubmission
is the model containing user submissions and To
is the model for admin-created or approved entries):
def new(conn, %{"from_sub" => from_sub_id}) do
to_submission = Repo.get(ToSubmission, from_sub_id)
changeset = To.changeset(%To{}, to_submission |> Map.from_struct)
render(conn, "new.html", [changeset: changeset, new_from_to_sub: to_submission])
end
When an admin user submits a form containing such an offsets value, how could I parse and check for conformance to the “any number of lists each containing two integers” requirement?
Edit: I’ve found out that there’s a Kernel.inspect/2 function which can be used to create a string including the array’s square brackets. In the controller:
offsets = changeset.changes.offsets
IO.inspect(offsets, [char_lists: :as_lists, label: "list"])
offsets_string = ~s(#{inspect(offsets, [char_lists: :as_lists])})
IO.inspect(offsets_string, [label: "string"])
Gives:
list: [[10, 0], [100, 10]]
string: "[[10, 0], [100, 10]]"
So I think that that part of the problem is solved, and I now need to work out how to get this string to be used as the form input’s default value in the ‘new’ page.
Regarding parsing the string when creating/updating, I think that Code.eval_string/3 could then be used to create the nested list from such a string? The admin user can be trusted not to be malicious, but I suppose not evaluating user-submitted data as code would be better! Perhaps some recursive pattern matching? I’ll have a play.