Rails Validations neat errors in form - ruby-on-rails

So i made a Validation that will check gender if an accurate ID number is inputted, if the data is incorrect then an error will appear beneath the select within the form.
.col
%h4 Participant
= f.field(:full_name, :label => "Full name *")
= f.field(:id_number)
= f.field(:gender, :field_type => :select, :choices => GENDER_SELECT, :label => 'Gender *')
%br #note i added this as the 'hacky' fix
= f.field(:race, :field_type => :select, :choices => RACE_SELECT, :label => 'Race *')
without the %br i have the form return the error like this
Is there a less hacky way so that when the error is displayed that it renders it something like this. so that the errors don't overflow onto other form elements
%br
= participant.errors[:gender]
%br

.field-error
= participant.errors[:gender]
Then use css to style the field-error class.

You should use CSS styles to control the errors layout/visual appearance instead of additional HTML tags.

Related

NoMethodError when returning an array in a form

I'm building an app for a restaurant and I have a form where I add meals to an order and a price field gets dynamically updated depending on what dishes and how many of them you pick.
To do that I built a nested form (I think that doesn't matter anyway) which looks as follows:
.nested-fields
= f.collection_select(0, #dishes.collect{ |dish| [dish.name, :data => {:description => dish.price}]}, :name, :name, {include_blank: true}, {class: "meal-select"})
= f.select :quantity, options_for_select((1..10))
= f.text_field(:price, disabled: true)
= link_to_remove_association "X", f
The thing that bugs me is the collection_select. As you can see, I am returning an array with a name and a data-description which goes to the HTML tag. Based on the data-description, my price field gets updated.
However, I have no idea what method I should choose to extract the name of a dish. As you can see I tried 0 since name of the dish is always first in the array. I have also tried :first, :name but none of those works! The error I get is:
"NoMethodError in Orders#new
undefined method '0' for #Meal:0x007fe4eb8e26c8"
or when I use :name
undefined method `name' for ["Zupa z Krewetkami", {:data=>
{:description=>17.0}}]:Array
Naturally, it points to:
= f.collection_select(0, #dishes.collect{ |dish| [dish.name, :data => {:description => dish.price}]}, :name, :name, {include_blank: true}, {class: "meal-select"})
I don't think the problem lies in my controller but, I'll show it just in case:
def new
#dishes = Dish.all
#order = current_user.orders.build
end
I tried looking for an answer here but as you can see the problem has not been solved and it was slightly different than mine.
To sum up - my question is what method I should use to extract name of the dish from my array in collection_select. Thanks!
Here is how you can use collection_select
...
= f.collection_select :meal_select, #dishes, :name, :price, {include_blank: true}, {class: "meal-select"}
...
For more details see the docs.
Use below approach
options_for_select( [['First', 1, {:'data-price' => 20}],
['Second', 2, {:'data-price' => 30}]] )
= f.select :meal_select, options_for_select(#dishes.collect{ |dish| [dish.name, dish.price,{'data-description' => dish.price}]}), :class => 'meal-select'

Select option not preselected after validation using collection in formtastic field

I'm 90% sure I'm doing something obviously wrong here, but when I'm using a select with a collection:
<%= f.input :description,
:label => "Which best describes who you are?",
:prompt => "Select an option...",
:collection => [[ "I am working for a company", "working"],["I am a freelancer", "freelancer"],["I am studying", "studying"],["I have recently graduated", "graduated"],["I teach", "teach"],["None of these things","none"]]
%>
and the form fails validation, the previously selected value is not selected, even though it is saved and is being passed to the params[:user][:description] as expected. Any ideas where I'm going wrong?
It's described there https://github.com/justinfrench/formtastic/wiki/Deprecation-of-%3Aselected-option#what-to-do-instead, so following should work
f.select :description,
options_for_select([[ "I am working for a company", "working"],["I am a freelancer", "freelancer"],["I am studying", "studying"],["I have recently graduated", "graduated"],["I teach", "teach"],["None of these things","none"]], f.object.description)
:label => "Which best describes who you are?",
:prompt => "Select an option...",
Also I would suggest moving collection to a separate helper method

Adding a value-dependent data attribute to a simple_form checkbox collection

I'm generating a list of checkboxes for a single collection like so:
= f.input :parts, as:check_boxes, collection: #parts_list
I want some checkboxes in the collection to disappear/reappear depending on the value of a select widget higher up in the form. (e.g. choosing "Tracker Robot" from the Robot select means that the "Legs" part checkbox disappears and the "Wheels" checkbox appears, etc.)
What I'd like to do is attach a computed data attribute to each individual Part checkbox, with the attribute value listing the Robots that can use that Part; then some JS will do the work of hiding/showing the checkboxes. However, I don't know how I can generate those data attributes using simple_form.
I would normally create a custom "parts" input, but there seems to be a problem with making custom collection inputs; it looks for a named method (collection_parts) inside form_builder.rb, which won't exist, and if I try and extend the FormBuilder it sends me down a major rabbit hole.
I could write some JS to load the data attrs into the generated HTML, but then I have to generate custom JS based on my Rails data, and that feels like the wrong way to do it.
Let's assume that the form is for Order model and you are changing the parts collection based on the value of a field called region.
Update the form view. Specify the id for form, region field and parts field.
= simple_form_for(#order, :html => { :id => "order-form"}) do |f|
= f.input :region, :wrapper_html => { :id => "order-form-region", |
"data-parts-url" => parts_orders_path(:id => #order.id, :region => #order.region)} |
= f.input :parts, as: check_boxes, collection: #parts_list, |
:wrapper_html => { id' => 'parts-check-box-list'} |
Add a new action called parts in the route.rb file.
resources :orders do
collection do
get :parts
end
end
Add the new action to your controller
class OrdersController < ApplicationController
# expects id and region as parameters
def parts
#order = params[:id].present? ? Order.find(params[:id]) : Order.new
#parts_list = Part.where(:region => params[:region])
end
end
Add a helper
def parts_collection(order, parts_list)
"".tap do |pc|
# to generate the markup for collection we need a dummy form
simple_form_for(order) do |f|
pc << f.input(:parts, as: check_boxes, collection: parts_list,
:wrapper_html => {:id => 'parts-check-box-list'})
end
end
end
Add a js view for the action (orders/parts.js.erb)
$('#parts-check-box-list').replaceWith('<%= j(parts_collection(#order, #parts_list)) %>');
Register data change event handlers for region field in your application.js
$(document).ready(function() {
$('#order-form').on("change", "#order-form-region", function () {
// Access the data-parts-url set in the region field to submit JS request
$.getScript($(this).attr('data-parts-url'));
});
});
I think you can do it like this:
= f.input :parts do
= f.collection_check_boxes :parts, #parts_list, :id, :to_s, item_wrapper_tag: :label, item_wrapper_class: :checkbox do |b|
- b.check_box(data: { YOUR DATA ATTRIBUTES HERE }) + b.text
this may be simpler.
Assumptions
#robots - an array containing the list of robots
#parts - a hash containing a list of parts for each robot
Sample Code
# controller
#robots = %w[tracker nontracker]
#parts = { tracker: %w[wheels lcd resistor], nontracker: %w[lcd resistor] }
# view
= f.input :robots, as: :select, collection: #robots, input_html: { id: 'robot-select' }
#parts-list
:javascript
var parts = #{#parts.to_json};
$(document).ready(function() {
$('#robot-select').change(function() {
$('#parts-list').html('');
$(parts[$(this).val()]).each(function(index, text) {
$('#parts-list').append('<input type="checkbox" value=' + text + '>' + text + '</input>')
})
})
})
you can see this working if you clone https://github.com/jvnill/simple_form_search_app and go to /robots
Some input options in SimpleForm accept a lambda that gets called for every item in a collection:
f.input :role_ids, :collection => (1..10).to_a,
:label_method => :to_i, :value_method => :to_i,
:as => :check_boxes, :required=> true,
:disabled => ->(item){ item.even? }
but input_html doesn't seem to be one of them.
The solution is probably to create a custom SimpleForm collection input that applies the data attributes itself. Not as flexible perhaps, but I think this is the only way to go for now.
There's a tutorial page on GitHub that should get you started.

Rails formtastic is asking for a country plug-in.. I don't want it. Anyone know how to tell it I don't want it?

I'm using formtastic and I've got a field country.. I'm getting this error when I attempt to display the screen.
To use the :country input, please install a country_select plugin,
like this one: https://github.com/jamesds/country-select
Now. I don't want to use any plugin.. It's free text, and I want to keep it that way.
Anyone know how to remove this requirement? Should be easy as... but I'm buggered if I can see how.
= semantic_form_for #store, {:html => { :class => "form-horizontal" }} do |f|
= f.input :default_country
Add
, :as => :string
to the end of the line that's causing the error
= semantic_form_for #store, {:html => { :class => "form-horizontal" }} do |f|
= f.input :default_country, :as => :string
In Rails 4, formtastic with country select input field:
Add 'country-select' to your Gemfile:
gem 'country-select'
If I use semantic form select, it doesn't shows previously saved value. So, the following is not working properly:
=f.input :country, as: :select, collection: country_options_for_select
So have to use standard form elements to get working:
=f.select :country, collection: country_options_for_select
I found that this plugin works out of the box (note the underscore instead of the dash):
https://github.com/chrislerum/country_select

HAML - how to create form validation and display error messages?

I am able to render a form in HAML but I am not sure how to validate it.
Here is what I have so far:
#disclosure-form-container
= form_for([:mobile,#disclosure], :html => {:id => "disclosure-form", :remote => true}) do |f|
%p
=f.label :display_as, (t :select_disclosure_type)
=f.select :display_as, options_from_collection_for_select(Disclosure.basics, :display_as, :name, f.object.display_as)
%p
=f.label :notes, (t :notes)
=f.text_area :notes, :class => "#{validation_rules(:free_disclosure_notes)}", :rows => 5 , :id => "disclosure_notes_entry"
= buttonset({:submit_label => (t "buttons.influencers.disclosures.create"),:submit_css_class => "call-to-action",:cancel_url => "#",:direction => :left_to_right})
This renders a form with two fields and a button to click submit. But what if people just enter submit without doing much? Where do I put the validation code and how do I validate this form?
Thanks!
A user enters data that should be saved in your model and then stored in your DB. So it's naturally to implement validation on model-level. Rails allows you to create validation for models easy. Here you can read about it. In a few words: adding few lines to your model prevent it to be saved with inconsistent data.
But you can in addition to model-level validation use client-side validation: i.e., data will be checked before sending to server. This way user don't have to submit form to find out that he forgot to fill some required field. But of course this can't be any guaranty as it's easy to bypass. Here more about client-side validation.
So, as you see, Haml can't help you with validations. It's a great tool, but not for this purpose.
In your Disclosure model, you need:
class Disclosure < ActiveRecord::Base
validates_presence_of :display_as, :notes
...
end
Then you can include error messages at the top of your form with something like:
- if #disclosure.errors.any?
%ul.errors
- #disclosure.errors.full_messages.each do |msg|
%li= msg
Or if you use Simple Form you get automatic validation messages inline, and your form would just look like this:
= simple_form_for #disclosure do |f|
= f.input :display_as
= f.input :notes
= f.submit
And you're done.
Note, as Jdoe pointed out, HAML has nothing to do with detecting validations, it only displays what the server tells it. The server is what determines whether there are validation errors.
If you want to try and come up with the equivalent of something like this client side you could give your form an id and do something like this (in CoffeeScript):
jQuery ->
$('form#disclosures').submit (event) ->
unless $('input#display_as').val().length > 0 && $('input#notes').val().length > 0
event.preventDefault()
$(this).append('<div class="error">You must select 'display' and enter notes.</div>')

Resources