Rails 5 Survey client side answers - ruby-on-rails

I have a model MyQuiz that belongs to Quiz through Questions and also Belongs to User
Quizzes are created on models: Quiz and Question. So the model MyQuiz is the one that records the answers
I'm using SimpleForm so I came up with this form bellow for MyQuiz
= simple_form_for(#my_quiz) do |f|
= f.error_notification
- #questions.each do |question|
.form-group
%p= question.title
= f.input :answer, as: :radio_buttons
= f.input :question_id, as: :hidden, :input_html => { :value => question.id }
= f.input :user_id, as: :hidden, :input_html => { :value => current_user.id }
.form-group
= f.button :submit, class: "btn btn-primary pull-right"
my_quizzes_controller.rb
def new
#quiz = Quiz.find(params[:quiz_id])
#questions = #quiz.questions.all
#my_quiz = MyQuiz.new
end
def create
#my_quiz = MyQuiz.new(my_quiz_params)
if #my_quiz.save
redirect_to user_path(#user), notice: "Thank you taking the quiz!"
else
render "new"
end
end
private
def my_quiz_params
params.require(:my_quiz).permit(:question_id, :user_id, :answer)
end
The problem with this form is that it is not set properly to serialize the answers. So it acts as if all the answers are the same. Even the radio buttons are behaving the same way
I'm not quite sure on what I should do here. I also tried a hacky solution with simple_fields_for but it did not change the outcome.
Thanks in advance
UPDATE: With Suggestion and Another try
So the suggestion was to MyQuiz form as a nested form
something like this:
= simple_form_for #user do |g|
= g.error_notification
- g.simple_fields_for #my_quiz do |f|
- #questions.each do |question|
.form-group
%p= question.title
= f.input :answer, as: :radio_buttons
= f.input :question_id, as: :hidden, :input_html => { :value => question.id }
= f.input :user_id, as: :hidden, :input_html => { :value => current_user.id }
.form-group
= g.button :submit, class: "btn btn-primary pull-right"
I tried and still had the same problem with the radio button.
I'm thinking that it might be an unecessary call into UserModel create/update.
I've been playing around with an option like this:
= form_tag my_quizzes_path do
- #questions.each do |question|
%p= question.title
= radio_button_tag "my_quizzes[#{question}][answer]", '0'
= radio_button_tag "my_quizzes[#{question}][answer]", '1'
= hidden_field_tag "my_quizzes[][question_id]"
= hidden_field_tag "my_quizzes[][user_id]"
= submit_tag "Submit"

Related

How do you create partially pre-filled forms with Rails, Simple_Form, and Cocoon?

I have a form to collect CV information from Users. In it, I need to gather what languages they speak and their proficiency level with each. I want to have 3 languages that are required, already present on the form, and have the option to allow the User to add additional languages. Cocoon works great for allowing them to add languages on their own, but, how can I get 3 language instances already in the form?
cvs/_form.html.haml
= simple_form_for(#cv, url: user_cvs_path) do |f|
...
%hr
.row
%strong Please indicate known languages and ability level in each:
#languages
= f.simple_fields_for :languages do |language|
= render 'language_fields', f: language
.links
= link_to_add_association 'Add Language', f, :languages, class: 'btn btn-success btn-sm'
...
cvs/_language_fields.html.haml
.row.nested-fields
= f.input :name, wrapper_html: { class: 'col-sm-4' }
= f.input :read, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= f.input :write, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= f.input :speak, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= f.input :listen, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= link_to_remove_association "Remove Language", f
In it's present state, this section of the form looks like this:
But what I want is this:
Based on Pablo's answer, I've added this to my controller:
def new
#cv = #user.build_cv
#cv.languages.build(name: 'English')
#cv.languages.build(name: 'Cantonese')
#cv.languages.build(name: 'Mandarin')
end
That get's me this far:
Now I just need to figure out how to make the languages 'Fixed' so they can't be changed and I need to get rid of the "Remove Language" links that Cocoon supplies.
You must create three languages in the controller:
def new
#cv = #user.build_cv #or current_user.build_cv
3.times do { #cv.languages.build }
end
To get 3 specific languages, use this in the controller instead:
def new
#cv = #user.build_cv
#cv.languages.build(name: 'English')
#cv.languages.build(name: 'Cantonese')
#cv.languages.build(name: 'Mandarin')
end
Then to display those three in the form, make them non-editable and non-removable, change the form to this:
= f.simple_fields_for :languages do |language|
.row
= language.input :name, disabled: true, wrapper_html: { class: 'col-sm-4' }
= language.input :read, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= language.input :write, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= language.input :speak, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
= language.input :listen, collection: #language_levels, wrapper_html: { class: 'col-sm-2' }
.links
= link_to_add_association 'Add Language', f, :languages, class: 'btn btn-success btn-sm'

boolean input_filed calls immediately the method on Rails

The project which I am working in, is developed on Rails using haml markup to views. There is a view with a simple form like this:
= simple_form_for #message, url: [:admin, #request, #message], html: { class: 'form vertical-form} do |f|
= f.input :text, as: :text, input_html: { class: 'form-control', rows: 5 }
%br
= f.input :link_url, input_html: { class: 'form-control'}
%br
- if #message.has_picture_image?
= f.label :image
=link_to #message.picture_image, target: "_blank" do
= image_tag #message.picture_image(:thumb)
= f.file_field :image, class:'imagen-button'
= f.input_field :remove_picture, as: :boolean, inline_label: 'Remove'
%br
.form-actions
= f.submit(t('accept'), class: 'btn btn-large btn-primary')
= link_to(t('cancel'), [:admin, #message.request, #message], class: 'btn btn-large btn-danger')
and in Message model there is the bellow method:
def remove_picture
self.picture.destroy
end
The input_field is used to check if I want to remove the message image if it exists. I understood that input_filed gives me the option to check it so that when I click on accept button, it call the method remove_picture in the Message model. But, before the browser deploys the form, it rise the next error:
undefined method `to_i' for #<Picture:0x007f7675162b58>
Extracted source (around line #39):
37: = image_tag #message.picture_image(:thumb)
38: = f.file_field :image, class:'imagen-button'
39: = f.input_field :remove_picture, as: :boolean, inline_label: 'Remove'
40: %br
41: .form-actions
42: = f.submit(t('accept'), class: 'btn btn-large btn-primary')
and if I reload the page, this time the form is deployed. I guess this is because in the first time, as the picture exists then immediatly the remove_picture is called and the picture removed, and when I reload the form, as the picture already does not exist, the form is shown.
Obviously I am undestanding wrongly the input_field usage.
SimpleForms input_field is a helper which binds an input to a model attribute. It does not create a box which calls your method when the box is ticked! But rather it will call your remove_picture method when it rendering the form.
In some cases like checkboxes you will want to bind inputs to attributes that are not saved in the database. We call these virtual attributes. They are just like any old Ruby attributes:
class Message < ActiveRecord::Base
attr_accessor :remove_picture
# since this method is destructive it should have a bang (!)
def remove_picture!
self.picture.destroy
end
end
You could use it like this:
class MessagesController < ApplicationController
def update
#message.update(update_params)
#message.remove_picture! if message.remove_picture
# ...
end
def update_params
params.require(:message).allow(:remove_picture)
end
end
But there is a better way:
class Message < ActiveRecord::Base
has_one :picture_image
accepts_nested_attributes_for :picture_image, allow_destroy: true
end
accepts_nested_attributes_for lets us create an image with picture_image_attributes and destroy an image with:
#picture.update(picture_image_attributes: { _destroy: true })
This is how we would set up the form:
= simple_form_for #message, url: [:admin, #request, #message], html: { class: 'form vertical-form} do |f|
= f.input :text, as: :text, input_html: { class: 'form-control', rows: 5 }
%br
= f.input :link_url, input_html: { class: 'form-control'}
%br
- if #message.has_picture_image?
f.simple_fields_for :picture_image do |pif|
= pif.label :image
= link_to #message.picture_image, target: "_blank" do
= image_tag #message.picture_image(:thumb)
= pif.file_field :image, class:'imagen-button'
= pif.input_field :_destroy, as: :boolean, inline_label: 'Remove'
%br
.form-actions
= f.submit(t('accept'), class: 'btn btn-large btn-primary')
= link_to(t('cancel'), [:admin, #message.request, #message], class: 'btn btn-large btn-danger')
And your strong parameters whitelist:
def update_attributes
params.require(:message).allow(
:text,
:link_url,
picture_image_attributes: [:image, :_destroy]
)
end

Extract search functionality to Form Object in Rails

In my Rails application I have simple search functionality. I want to extract to Form Object but don't know how to do. I have search form which looks like this:
.row
= horizontal_simple_form_for :cars, {url: cars_path, method: :get} do |f|
.col-md-4
.row
.col-md-12
= f.input :handover_location, label: I18n.t('.handover'), collection: Location.all.map{|hl| [hl.location_address, hl.id]}
= f.input :return_location, label: I18n.t('.return') ,collection: Location.all.map{|rl| [rl.location_address, rl.id]}
= f.input :car_class, label: I18n.t('.car_class') ,collection: CarClass.all.map { |c| [c.name, c.id] }, include_blank: true
.col-md-4
= f.input :handover_date, as: :string, label: false
= f.input :return_date, as: :string, label: false
= f.submit class: 'btn btn-success'
Cars controller:
class CarsController < ApplicationController
skip_authorization_check
def index
#cars = Car.search(params)
end
def show
end
end
And class method in Car model which search correct cars:
def self.search(params)
self.joins(:reservations).where.not("reservations.reception_time <= ? AND reservations.return_time >= ?",
params[:cars][:return_date], params[:cars][:handover_date]).
joins(:car_class).where("car_classes.id= ?", params[:cars][:car_class])
.cars_at_both_locations(params[:cars][:handover_location], params[:cars][:return_location])
end
Now I'm trying to extract this to Form Object. I've created a file search_form.rb:
class SearchForm
include ActiveModel::Model
attr_accessor :handover_date, :return_date, :handover_location, :return_location, :car_class
end
But now I don't know how to handle my params to this form object. Thank's in advance.
I wish I could help you with the Form Object stuff, but I need to learn more about classes & modules
I can help you with the search functionality, as we've done it before here
Here's the code we used:
#View
<%= form_tag search_path, :method => :post, :id => "SearchForm" do %>
<%= text_field_tag :search, params[:search], placeholder: 'Search your favourite products or brands', :autocomplete => :off %>
<%= image_submit_tag 'nav_bar/search.png' %>
<% end %>
#config/routes.rb
match 'search(/:search)', :to => 'products#search', :as => :search, via: [:get, :post]
#app/controllers/products_controller.rb
def search
#products = Product.search(params[:search])
respond_to do |format|
format.js { render :partial => "elements/livesearch", :locals => {:search => #products, :query => params[:search]} }
format.html { render :index }
end
end
Notice the form_tag we used?
Simple form does not work with form_tag currently (it requires an object) - we just send the data with a GET request to the controller & that then sends the data to the Product model
I think your problem will be caused by the use of your SearchForm object. You only need this because your use of simple form means you have to pass an object. Problem being this is not necessary for search
A better way will be to use a standard form_tag, and send the request directly to your controller. This will allow you to process the data as params, which you'll be able to send directly to your Car model
--
I can write some code specific to you if you want
I found solution on my own.
Cars controller:
def index
#search_form = SearchForm.new(params[:search_form])
#cars = #search_form.submit(params[:search_form])
end
search_form.rb:
class SearchForm
include ActiveModel::Model
attr_accessor :handover_date, :return_date, :handover_location, :return_location, :car_class
def submit(params)
Car.search(params)
end
end
Search form in view:
.row
= horizontal_simple_form_for SearchForm.new, {url: cars_path, method: :get} do |f|
.col-md-4
.row
.col-md-12
= f.input :handover_location, label: I18n.t('.handover'), collection: Location.all.map{|hl| [hl.name, hl.id]}
= f.input :return_location, label: I18n.t('.return') ,collection: Location.all.map{|rl| [rl.name, rl.id]}
= f.input :car_class, label: I18n.t('.car_class') ,collection: CarClass.all.map { |c| [c.name, c.id] }, include_blank: true
.col-md-4
= f.input :handover_date, as: :string, label: false
= f.input :return_date, as: :string, label: false
= f.submit class: 'btn btn-success'
search method in car model:
def self.search(params)
self.joins(:reservations).where.not("reservations.reception_time <= ? AND reservations.return_time >= ?",
params[:return_date], params[:handover_date]).
joins(:car_class).where("car_classes.id= ?", params[:car_class])
.cars_at_both_locations(params[:handover_location], params[:return_location])
end

Rails simple_form error: false

in my app I have form that looks like this
= simple_form_for #user do |f|
= f.input :name, error: false
= f.input :surname, error: false
Is there any way to avoid this repetitions (error: false)?
If they're all of the same type, something like this should work:
= simple_form_for #user do |f|
- [ :name , :surname ].each do |field|
= f.input field, error: false
If not, you could use a hash or something, instead of an array, and specify the type, as well.
It appears that simple form has the following option:
If you want to pass the same options to all inputs in the form (for
example, a default class), you can use the :defaults option in
simple_form_for. Specific options in input call will overwrite the
defaults:
<%= simple_form_for #user, defaults: { input_html: { class: 'default_class' } } do |f| %>
<%= f.input :username, input_html: { class: 'special' } %>
<%= f.input :password, input_html: { maxlength: 20 } %>
<%= f.input :remember_me, input_html: { value: '1' } %>
<%= f.button :submit %>
<% end %>
From https://github.com/plataformatec/simple_form
So, in your case:
= simple_form_for #user , defaults: { error: false } do |f|
= f.input :name
= f.input :surname
You could loop through an array of symbols
simple_form_for #user do |f|
[:name, :surname].each do |element|
f.input element, error: false
end
end

Limit simple_form_for associated records number in Ruby on Rails

I have a large psychological test of 251 question. Each user can complete that test many times. So I created Summary model to represent each completion. Each Summary has many Answers. For each Summary I created a form that represents collection of answers, using Slim templater and simple_form gem:
= simple_form_for(#summary) do |f|
= f.simple_fields_for :answers do |a|
.question
= a.input :question_id, as: :hidden
div= a.object.question.title
- if a.object.question.kind_of? SpiritualityQuestion
ol class="sortable"
- a.object.question.sortable_variants.each do |sortable_variant|
= content_tag_for :li, sortable_variant
= sortable_variant.title
= a.input :text_data, as: :hidden, input_html: { class: 'sortable_data' }
- elsif a.object.question.kind_of? MultilineQuestion
div Time remaining: <span class="time">60</span> s.
= button_tag 'Start', type: 'button', class: 'start_button btn btn-primary'
= a.input :text_data, label: false, input_html: { class: 'span8 timed_text', cols: '60', rows: '20', disabled: true }
- else
= a.association :variant, collection: a.object.question.variants, as: :radio, label: false
br
= f.input :user_id, as: :hidden
= f.input :psy_test_id, as: :hidden
.actions
= f.button :submit, value: 'Save', class: 'btn btn-large btn-success'
And I have related controller action:
#summary = Summary.where(completed: false, user: current_user, psy_test: PsyTest.first)
.includes(:answers => { :question => :variants })
.first_or_initialize
#summary.build_answers if #summary.new_record?
Summary.build_answers:
def build_answers
# Creating answers for questions
psy_test.questions.includes(:variants).each do |q|
answers.new(question: q)
end
end
Now I'm trying to make the test to be paginated, because it's very large and the form generates very slowly. So I want to add limit and offset to answers. Something like that:
= f.simple_fields_for answers.limit(#limit).offset(#offset) do |a|
How it can be made?
I've looked to do field_for source and found simple answer, which I wasn't able find in any guide:
= f.simple_fields_for :answers, #summary.answers.limit(#limit).offset(#offset) do |a|

Resources