Related
I've got an app where the user has to fill out a survey. I need to store user's answers in inside TestResult model which have only one field answers:string
With current implementation I'm getting params from the form as:
params => {
{
"question_#{id}": "some answer 1",
"question_#{id}": "some answer 12345",
}
}
Which I want to change to the below structure:
# expected hash params
params => {
{
question: 'first question',
answer: 'some answer 1'
},
{
question: 'second question',
answer: 'some answer 123431'
}
}
What should I change (probably in a view) to get this hash?
new.html.erb
<%= simple_form_for :test_results, url: test_results_path do |f| %>
<% #randomize_questions.map do |q| %>
<%= q[:question] %>
<%= f.input "question_#{q[:id]}", collection: q[:answers], as: :radio_buttons %>
<% end %>
<%= f.button :submit %>
<% end %>
controller:
class TestResultsController < ApplicationController
before_action :fetch_random_questions, only: [:new, :create]
def new
#test_result = TestResult.new
end
def create
#test_result = TestResult.new(
answer: test_result_params,
)
#test_result.save
redirect_to dummy_path
end
end
private
def test_result_params
params.require(:test_results).permit!
end
def fetch_random_questions
TestQuestion.where(published: true).order('RANDOM()')
#randomize_questions = test_questions.map do |obj|
{
id: obj.id,
question: obj.question,
answers: [obj.correct_answer, obj.other_answer1, obj.other_answer2, obj.other_answer3],
}
end
end
end
TestResult model
class TestResult < ApplicationRecord
serialize :answer, Hash
serialize :answer, String
validates :answer, presence: true
end
The params get his structure from the input names.
So you could add an hidden field for question, and then specify a name for both of your fields.
<%= simple_form_for :test_results, url: test_results_path do |f| %>
<% #randomize_questions.map do |q| %>
<%= q[:question] %>
<%= f.input "question_#{q[:id]}", as: :hidden, input_html: { name: "test_results[#{q[:id]}][question]", value: q[:question] } %>
<%= f.input "question_#{q[:id]}", collection: q[:answers], as: :radio_buttons, input_html: { name: "test_results[#{q[:id]}][answer]" } %>
<% end %>
<%= f.button :submit %>
<% end %>
Params should looks like this:
params => {
test_result: {
1 => {
question: "...",
answer: "..."
},
2 => {
question: "...",
answer: "..."
}
}
}
Not tested. Could you tell if that's works for you?
I have this nasty if/else statement in a rails view:
<% if question.field_type == "text_area" %>
<%= f.text_area :content, :class=>"form-control question-field", :data => {:question => question.id, :filter=> #filter}, :value=> question.answer(#assessment).try(:content) %>
<% elsif question.field_type == "date" %>
<%= f.date_select :content, { :order => [:year, :month, :day], :prompt => { :day => 'day', :month => 'month', :year=> "year" }, :end_year=> Date.today.year, :start_year => Date.today.year - 2 }, {:class => "question-field", :data => {:question => question.id, :filter=> #filter}, :value=> question.answer(#assessment).try(:content)} %>
<% elsif question.field_type == "text_field" %>
<%= f.text_field :content, :class=>"form-control question-field", :value=> question.answer(#assessment).try(:content), :data => {:question => question.id, :filter=> #filter} %>
<% elsif question.field_type == "dropdown" %>
<%= f.select :content, options_for_select(question.options), { :prompt => "Choose One..." }, :class=>"form-control question-field", :value=> question.answer(#assessment).try(:content), :data => {:question => question.id, :filter=> #filter} %>
<% elsif question.field_type == "number" %>
<%= f.select :content, options_for_select(1..10), {:include_blank=> true}, :class=>"form-control question-field", :value=> question.answer(#assessment).try(:content), :data => {:question => question.id, :filter=> #filter} %>
<% elsif question.field_type == "percentage" %>
<h2>100%</h2>
<%= f.range_field :content, :value=> get_percentage(question), :class=> "question-field percentage", :data => {:question => question.id, :filter=> #filter}, :step => 25, :in => 0..100 %>
<% end %>
Is there a good way to refactor this to make it nicer? This piece of code is in every field:
:class=>"form-control question-field", :value=> question.answer(#assessment).try(:content), :data => {:question => question.id, :filter=> #filter}
Do I refactor into a helper method or a partial?
Sometimes templates are just messy and you can only clean up detail. Refactoring into a parameterized partial will help. For goodness sake, use a case. And consider switching to HAML. It eliminates a lot of the visual clutter.
<%= render 'question_field', f: f, type: question.field_type %>
Then in _question_field.erb,
<%= case type %>
<% when 'text_area' %>
<% f.text_area :content, class: 'form-control question-field', %>
<% data: { question: question.id, filter: #filter }, %>
<% value: question.answer(#assessment).try(:content) %>
<% when ... %>
<% end %>
Note common industrial practice is to pick a max line length and stick to it: 100 and 120 are pretty common. Also, use the new symbol key notation for hashes. The old hook-and-arrow is too noisy.
In HAML:
= case type
- when 'text_area'
- f.text_area :content, class: 'form-control question-field',
data: { question: question.id, filter: #filter },
value: question.answer(#assessment).try(:content)
- when ...
I would get rid of if's and when's altogether by creating seperate partial for every possibility, then you just end up with:
<%= render question.field_type, locals: {question: question} %>
Or to make it even cleaner for view make helper method and call only
<%= question_field(question) %>
and this method would look little bit like
def question_field(question)
return render question.field_type, locals: {question: question}
# raise when no partial found, or do something elese
end
In my Ruby on Rails application I am trying to display a three drop down menus in the _form.html.erb which are rendered from the file _booking_lookup.html.erb and get there data from the drop down menu methods in the models.
_form.html.erb:
<%= render(:partial => '/booking_lookup', :locals=> {:film => #film = Film.all, :showings => #showings = Showing.all, :seats => #seats = Seat.all, :my_path => '/films/booking_lookup' }) %>
_booking_lookup.html.erb:
<%= form_tag my_path, :method=>'post', :multipart => true do %>
<%= select_tag ('title_id'),
options_from_collection_for_select(#films, :id, :title_info, 0 ),
:prompt => "Film" %>
<%= select_tag ('showings_id'),
options_from_collection_for_select(#showings, :id, :showing_times, 0 ),
:prompt => "Showings" %>
<%= select_tag ('seat_id'),
options_from_collection_for_select(#seats, :id, :seats_available, 0 ),
:prompt => "Seats" %>
<%= submit_tag 'Search' %>
film.rb:
class Film < ActiveRecord::Base
has_many :showings
belongs_to :certificate
belongs_to :category
def title_info
"#{title}"
end
end
seat.rb:
class Seat < ActiveRecord::Base
belongs_to :screen
has_many :bookings
def seats_available
"#{row_letter}#{row_number}"
end
end
showing.rb:
class Showing < ActiveRecord::Base
belongs_to :film
has_many :bookings
belongs_to :screen
def showing_times
"#{show_date.strftime("%e %b %Y")} # #{show_time.strftime("%H:%M")}"
end
end
But for some reason with the line: <%= select_tag ('title_id'),
options_from_collection_for_select(#films, :id, :title_info, 0 ),
:prompt => "Film" %> I get the error:
NoMethodError in Bookings#new
undefined method `map' for nil:NilClass
The weird part is that I am using a lot of this code else where, I have a _multi_search.html.erb form:
<%= form_tag my_path, :method=>'post', :multipart => true do %>
<!-- Genre: -->
Search By:
<%= select_tag ('cat_id'),
options_from_collection_for_select(#categories, :id, :category_info, 0 ),
:prompt => "Genre" %>
<%= select_tag ('cert_id'),
options_from_collection_for_select(#certificates, :id, :certificate_info, 0 ),
:prompt => "Age Rating" %>
<%= text_field_tag :search_string, nil, placeholder: "ACTOR" %>
or
<%= select_tag ('title_id'),
options_from_collection_for_select(#films, :id, :title_info, 0 ),
:prompt => "Film" %>
<%= submit_tag 'Search' %>
<% end %>
And is used in the application.html.erb:
<%= render(:partial => '/multi_search', :locals=> {:categories => #categories = existing_genres, :certificates => #certificates = Certificate.all, :films => #films = Film.all, :my_path => '/films/multi_find' }) %>
And that works fine.
What am I doing wrong?
It looks like #films is nil. Try setting #films = Film.all (instead of #film = Film.all) in _form.html.erb.
Update:
I would recommend moving the queries to the controller action. In the Model-View-Controller pattern, Controllers should be asking Models for data, not Views.
# BookingLookupController
def new
#films = Film.all
#showings = Showing.all
#seats = Seat.all
end
You can then reference the instance variables in the view.
<%= render partial: '/booking_lookup', locals: {films: #films, showings: #showings, seats: #seats, my_path: '/films/booking_lookup' } %>
In Controller, select fields as you just want to display names in dropdown
def method_name
#films = Film.select([:id, :title_info])
#showings = Showing.select([:id, :showing_times])
#seats = Seat.select([:id, :seats_available])
end
In page
<%= render(:partial => '/booking_lookup', :locals=> {:films => #films, :showings => #showings, :seats => #seats, :my_path => '/films/booking_lookup' }) %>
In partial
options_from_collection_for_select(films, :id, :title_info, 0 ),:prompt => "Film" %>
This is my form partial:
<%= f.simple_fields_for :photo_attributes, :html => { :multipart => true } do |d| %>
<%= d.label :image, :label => 'Upload logo', :required => false %>
<%= d.file_field :image, :label => 'Image, :required => false', :style => 'margin-bottom:2px' %>
<%= d.input :image_url, :label => 'Billed URL', :required => false %>
<% end %>
If the action is edit I want to show this instead:
<%= f.simple_fields_for :photo, :html => { :multipart => true } do |d| %>
<%= d.label :image, :label => 'Upload logo', :required => false %>
<%= d.file_field :image, :label => 'Image, :required => false', :style => 'margin-bottom:2px' %>
<%= d.input :image_url, :label => 'Billed URL', :required => false %>
<% end %>
How can i achieve this?
current_page?(action: 'edit')
See ActionView::Helpers::UrlHelper#current_page?
Rails also makes the methods controller_path, controller_name, action_name available for use in the view.
Generally the form partial only contains the fields, not the form tag or the fields for, but if you have no other way, you can always see what params[:action] is currently set to and behave accordingly.
You could write something like
<%- form_url = #object.new_record? ? :photo_attributes : :photo %>
<% f.simple_fields_for form_url, :html => { :multipart => true } do |d| %>
That is, if you have an #object to check against. Otherwise you could use action_name (and even controller_name).
So something like:
<%- form_url = action_name == :edit ? :photo : :photo_attributes %>
<% f.simple_fields_for form_url, :html => { :multipart => true } do |d| %>
Hope this helps.
Rails 5: Display Action within the view
<%= action_name %>
If statement within the view
<% if action_name == "edit" %>
This is an edit action.
<% end %>
Just use #_controller.action_name in view
I'm having problems with my rails custom validations.
def validates_hsp_program(*attr_names)
options = attr_names.extract_options!
regex = '^('
err = ''
$CetConfig.program.each do |key, val|
regex << val.to_s << '|'
err << $CetConfig.program_prefix + " " + val.to_s + ", "
end
regex.chomp!('|')
regex << ')$'
regex = Regexp.new(regex)
validates_each attr_names do | record, attr_name, value |
exit 1
unless value.nil? or value =~ regex
record.errors.add(attr_name, 'must be one of ' + err.chomp(", "));
end
end
end
The problem is that for debugging purposes I added exit 1 as I wasn't getting the error message for invalid date for that field. However, it never exits. This is the same thing I do in all my other custom validators. The only difference I can see is that this one is the second model of a multi-model form and all the others are on the first model... What am I doing wrong?
My Model
class ProfileProgram < ActiveRecord::Base
set_table_name "profile_program"
set_primary_key "STUDENT_ID"
belongs_to :profile_core, :primary_key => "STUDENT_ID", :foreign_key => "STUDENT_ID"
validates_presence_of :program
validates_hsp_program :program
end
Action from my controller
def create
#pc = ProfileCore.new(params[:profile_core])
#pp = ProfileProgram.new(params[:profile_program])
#pc.student_type = $CetConfig.student_type.application
#pc.AGENT_ID = current_agents_staff.AGENT_ID
year = #pc.contract.to_s
case #pp.program
when 10 then
sd = year + '-09-01'
ed = year + '-06-30'
when 51 then
sd = year + '-08-15'
ed = year + '-06-30'
when 52 then
sd = year + '-01-15'
ed = year + '-06-30'
when 12 then
sd = year + '-01-15'
ed = year + '-01-14'
else
sd = nil
ed = nil
end
#pc.start_date = Date.parse(sd) unless sd.nil?
#pc.end_date = Date.parse(ed) unless ed.nil?
#pc.program_status = $CetConfig.student_status.apply
if #pc.valid? and #pp.valid?
ProfileCore.transaction do
#pc.save!
#pp.save!
end
redirect_to(students_path(#pc.STUDENT_ID))
else
render :action => 'new'
end
end
My view (element_block is a helper that just stuffs the label and field into the right tags for the dl)
<% form_for :profile_core, #pc, :url => { :controller => 'core', :action => 'create'} do |f| %>
<%= error_messages_for :object => [ #pc, #pp ] %>
<dl>
<%= element_block f.label(:contract, 'Contract Year'), f.contract_year_select(:contract) %>
<% fields_for :profile_program do |pp| %>
<%= element_block pp.label(:program, 'Program'), pp.hsp_program_select(:program) %>
<% end %>
<%= element_block f.label(:passport_number, 'Passport Number'), f.text_field(:passport_number) %>
<%= element_block f.label(:passport_country, "Country that issued the student's passport"), f.countries_select(:passport_country) %>
<%= element_block f.label(:passport_expires, 'Passport Expiration Date'), f.text_field(:passport_expires, :class => 'datepicker') %>
<%= element_block f.label(:last_name, 'Last Name (as on passport)'), f.text_field(:last_name) %>
<%= element_block f.label(:first_name, 'First Name (as on passport)'), f.text_field(:first_name) %>
<%= element_block f.label(:middle_name, 'Middle Name (as on passport)'), f.text_field(:middle_name) %>
<%= element_block f.label(:other_names, 'Other Names'), f.text_field(:other_names) %>
<%= element_block f.label(:residence_street_address, 'Street Address'), f.text_field(:residence_street_address) %>
<%= element_block f.label(:residence_city, 'City'), f.text_field(:residence_city) %>
<%= element_block f.label(:residence_province, 'Province'), f.text_field(:residence_province) %>
<%= element_block f.label(:residence, 'Country'), f.text_field(:residence) %>
<%= element_block f.label(:residence_postal_code, 'Postal Code'), f.text_field(:residence_postal_code) %>
<%= element_block f.label(:birthdate, 'Date of Birth'), f.text_field(:birthdate, :class => 'datepicker', :id => "student_birth_date") %>
<%= element_block f.label(:citizenship, 'Country of Citizenship'), f.countries_select(:citizenship) %>
<%= element_block f.label(:birth_city, 'Birth City'), f.text_field(:birth_city) %>
<%= element_block f.label(:nationality, 'Nationality'), f.countries_select(:nationality) %>
<%= element_block f.label(:gender, 'Gender'), f.gender_select(:gender) %>
<%= element_block f.label(:email, 'Email'), f.text_field(:email) %>
<%= element_block f.label(:desires_esl, 'Does the student wish to participate in CLEP?'), f.bool_yes_no_select(:desires_esl) %>
<%= element_block f.label(:may_pay_tuiton, 'Willing to pay tuition'), f.yes_no_select(:may_pay_tuition) %>
</dl>
<div class="submit"><%= submit_tag("Proceed to Step Two") %></div>
<% end %>
This is how I ended up taking care of it with accepts_nested_attributes_for
Primary Model. (Irrelevant code replaced with ellipses)
class ProfileCore < ActiveRecord::Base
set_table_name "profile_core"
set_primary_key "STUDENT_ID"
belongs_to :agents_profile, :primary_key => "AGENT_ID", :foreign_key => "AGENT_ID"
has_one :profile_program, :primary_key => "STUDENT_ID", :foreign_key => "STUDENT_ID"
accepts_nested_attributes_for :profile_program
...
end
Secondary Model
class ProfileProgram < ActiveRecord::Base
set_table_name "profile_program"
set_primary_key "STUDENT_ID"
belongs_to :profile_core, :primary_key => "STUDENT_ID", :foreign_key => "STUDENT_ID"
validates_presence_of :program
validates_hsp_program :program
end
New action in the controller. (Create was irrelevant)
def new
#pc = ProfileCore.new
#pp = #pc.build_profile_program
end
View
<% form_for #pc, :url => { :controller => 'core', :action => 'create'} do |f| %>
<%= f.error_messages %>
<dl>
<%= element_block f.label(:contract, 'Contract Year'), f.contract_year_select(:contract) %>
<% f.fields_for :profile_program do |pp| %>
<%= element_block pp.label(:program, 'Program'), pp.hsp_program_select(:program) %>
<% end %>
<%= element_block f.label(:passport_number, 'Passport Number'), f.text_field(:passport_number) %>
...
<% end %>
It's not correct custom validation implementation, try something like that:
http://marklunds.com/articles/one/312
or http://chrisblunt.com/blog/2009/04/18/rails-writing-dry-custom-validators/