Rails 5 form to submit array passed as string
I have a serialized Array field in my model called :cords
and part of my form looks like this:
<%= form_for @group do |f| %>
....
<p>
<%= f.label :cords %><br>
<%= f.text_field :cords, name: "group[cords][]" %>
</p>
....
Then, inside my controller, I try to use it like this
@group = Group.new(params.require(:group).permit(:name, :members, cords: [] ))
if @group.save
redirect_to @group
else
render 'new'
end
This doesn't seem to work because when I enter some array like [[1,2],[3,4]]
I see that the SQL insert
INSERT INTO "groups" ("cords", "name", "members", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["cords", "---\n- \"[[1,2],[3,4],[5.5,6]]\"\n"], ["name", "GG"], ["members", 55], ["created_at", "2017-06-12 02:13:37.462355"], ["updated_at", "2017-06-12 02:13:37.462355"]]
Why cords
is it presented as ["cords", "---\n- \"[[1,2],[3,4],[5.5,6]]\"\n"]
? I believe I am doing something wrong with my actual form setup
source to share
Explanation of the problem:
Rails' segmented column does allow storing complex data types like array, but they do it in YAML format by default. What you do is pass the text as group[cords][]
param. So, what you should get into params
is this {group: {cords: [ "[[1,2],[3,4],[5.5,6]]" ]}}
. Note , that cords
is an array of one element, which is the string you pass to it :"[[1,2],[3,4],[5.5,6]]"
This array is serialized according to the scalar syntax, the YAML: "---\n- \"[[1,2],[3,4],[5.5,6]]\"\n"
. Let's take a closer look at this, first replacing newlines:
---
- "[[1,2],[3,4],[5.5,6]]"
#here also newline
Three hyphens indicate the beginning of a YAML document.
The array is packed like this:
- "first_element"
- "second_element"
- "third_element"
Is equal ["first_element", "second_element", "third_element"]
The backslash before quotation marks simply removes those from SQL that indicate that quotation marks are part of the string.
So, as you can see, rails does exactly what it is supposed to do / you told it to: it gets one string as one array element and stores one array element containing that string.
Decision:
What to do if you still want user input as the array will convert it manually.
- Cast
[]
intext_field
:f.text_field :cords, name: "group[cords]"
- In your controller, convert this text to a ruby array, for example:
def cords_to_a
# get the params[:group][:cords], if it empty use empty array string
cords = params.dig(:group, :cords).presence || "[]"
# parse it, possibly you also want to handle parse exceptions
# and transform parsed array as something else
# also, user will be able to save any json object
# to your serialized field, and it will be parsed
# accordingly: either to array or to hash
JSON.parse cords
end
def group_params
params.require(:group).permit(:name, :members).merge({cords: cords_to_a})
end
and after that use Group.new(group_params)
Note that you will still end up with a "strange" string in the statement INSERT
because it will contain a YAML encoded array: essentially a string.
Further reading:
source to share