I'm quite new to Ruby and RoR.
Ruby version: 2.4 / Rails version: 5.1 / DB: Mongo
Here's the thing: I'm attempting to build a survey manager with a similar behaviour of the Google Form. So I can type a title, a description and then dynamically add various types of fields and, if needed, options.
The issue
Graphically everything works but at the submit i'm facing this error, and after a ton of tries i still can't solve it.
undefined method `_id' for {"text"=>"QUESTION 1"}:ActiveSupport::HashWithIndifferentAccess
below all the details and the code related to it!
I have 3 interested models: Survey, Question and Option.
Survey: Is the main element which contains title, description, relations
and other minor stuff.
class Survey
# fields
field :title, type: String
field :description, type: String
# relations
has_many :question
accepts_nested_attributes_for :question
Question: it contains the question (text), typology (text, textarea, radio, checkbox, etc...) so i can handle various view and behaviours, and a boolean for the mandatory cases.
class Question
# fields
field :text, type: String
field :typology, type: String
field :mandatory, type: Boolean
# Relations
belongs_to :survey
has_many :option
accepts_nested_attributes_for :option
Option: Option is related to radio/checkbox questions which needs key value entries.
class Option
# fields
field :key, type: String
field :value, type: String
# Relations
embedded_in :question
Flow
Here's a link to the page view, so you can have an idea of how it looks graphically. However: When i create/edit a survey i have to insert title, description and then manage the fields with the system i have created using js/jquery. Banally, i append pieces of code (which contains the field i want to insert) at the end of the div container. When i submit the form it should save the survey data, questions and options to db.
What actually happens
Graphically everything works but at the submit i'm facing this error, and after a ton of tries i still can't solve it.
undefined method `_id' for {"text"=>"QUESTION 1"}:ActiveSupport::HashWithIndifferentAccess
The fun part
Sorry for the long intro but i want you all to clearly understand what i'm trying to achieve, what i've done and what happens now. So, here's the code.
Survey controller
class Admin::SurveysController < AdminController
def create
@survey = Survey.new(survey_params)
if @survey.save
redirect_to admin_surveys_path
else
@errors = @survey.errors
Rails.logger.error "Error"\
" '#{@survey.title}': #{@errors.full_messages}"
render 'new'
end
private
def survey_params
params.require(:survey).permit( :title, :description, :event_ids => [], :user_ids => [], question: [ :text, :typology, :mandatory, option: [:key, :value]] )
end
end
Survey Main form (view)
<div class="row">
<div class="col-sm-12">
<%= render "shared/inputs/text", args: [ form, :title ] %>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<%= render "shared/inputs/textarea", args: [ form, :description ] %>
</div>
</div>
<div class="well well-lg">
<%= form.fields_for :questions do |form| %>
<%= render 'admin/surveys/builder', form: form %>
<% end %>
</div>
Survey builder (view)
Note: suppressed some code cause it was not necessary to this explanation.
<div id="form_generated_fields"></div>
// Let's skip the code about the button
// is a dropdown and allows to choose between
// text,textarea,radio and checkbox
// Js to dynamically add stuff
<script>
function addField() {
jQuery('#add_field_btn > ul > li').on('click',function(e){
e.preventDefault();
var formContainer = jQuery('#form_generated_fields');
chosenType = $(this).attr('ftype');
switch(chosenType){
case 'radio':
formContainer.append(`<%= render 'admin/surveys/shared/radio', form: form %>`);
break;
}
});
}
function addOption(){
jQuery(document).on('click','.add_option',function(e){
e.preventDefault();
type = jQuery(this).attr('type');
optionList = jQuery(this).closest('.field_item').find('.row').find('.option_list');
console.log(optionList);
switch(type){
case 'radio':
optionList.append("<%= escape_javascript render 'admin/surveys/shared/option', form: form, type: "radio" %>");
break;
}
});
}
function init() {
addField();
deleteField();
addOption();
deleteOption();
}
$( document ).ready(function() {
init();
});
</script>
View: Question with radio options
<label class="control-label">Question text</label>
<%= render "shared/inputs/text", args: [ 'survey[question][][text]' ] %>
<div class="col-sm-8 option_list">
<!-- here the options are appended -->
</div>
View: Option (radio)
<%= render "shared/inputs/text", args: [ 'survey[question][][option][]' ] %>
<span class="material-input"></span>
Note:
<%= render "shared/inputs/text", args: [ 'survey[question][][option][]' ] %>
This is a really simple view which contains the input field with all the html and css for graphic purposes (styles, error visualization etc...) i just have to pass it the name of the input in the args array.
This is what arrives in the request after submit: (from stack trace and logs inspect)
{"utf8"=>"✓",
"_method"=>"patch",
"survey"=>
{"title"=>"title",
"description"=>"description",
"question"=>[{"text"=>"QUESTION 1"}, {"text"=>"QUESTION 2", "mandatory"=>"on", "option"=>["OPTION 1", "OPTION 2"]}],
"update"=>"aggiorna",
"id"=>"5ad610b4578f8c2f0ebefd81"}
Ok, i think that's all. Really i can't figure out what's wrong and where i have to fix stuff. Any help or suggestion is greatly appreciated. If you have better practices to achieve what i want i'm happy to discuss about it!