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

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>')

Related

Money Rails Gem - null values

I have monetised two models of my Rails 4 app with Money-Rails gem.
One is called Participants, the other is called Funding. Each of these models is nested inside another model, called Scope. Scope belongs to Project.
The associations are:
Project has one Scope; Scope belongs to Project
Scope has one Participant and has one funding; each of Participant and Funding belong to Scope.
Project accepts nested attributes for Scope. Scope accepts nested attributes for Participant and Funding.
Params for each relevant attribute in Participant and Funding are permitted in the Scope and Project Controllers as well as the models themselves. Params for Scope are permitted in the Scope and Project controllers.
In my Project form, I ask several questions. That form also has nested forms for each of the models which belong to it. Inside the Scope form, I ask users two boolean questions, being: Do you want participants? Do you want funding? Each of these models has a follow up question about participation cost and funding (those attributes are monetised).
If the answer to those questions is true, then I reveal the participant or funding form partial and ask how much money they want.
I have two problems:
First problem: Not null violation
1. If a user says they do want participants, but there is no associated costs, so that the boolean question inside the participant model asking whether there is cost involved with participation, I get an error that says:
ERROR: null value in column "participation_cost_pennies" violates not-null constraint
If a user says they don't want participants in answer to the question asked in the Scope form, I get the same error as in 1 above
Second problem: If I save an amount in the monetised fields, and come back to edit the project form, the form does not show the saved amount in the monetised field - and if you don't reenter it, I get an error saying that it can't be blank.
Does anyone know how to:
make the first problem go away in all circumstances except those when participation costs are actually sought; and
Fix the second problem by displaying the original amount saved when you come back to edit the form? I have tried inserting :selected into my form element, but it doesn't do anything.
My code is as follows:
Inside my Scope form (nested inside my project form):
<%= f.simple_fields_for :scope do |s_all| %>
<%= s_all.input :if_participant, :as => :boolean, :label => false, inline_label: 'Public participants or volunteers' %>
<%= s_all.input :if_funding, :as => :boolean, :label => false, inline_label: 'Funding or expenses' %>
If the answer to these fields is true, then I reveal the partial forms for participant of funding (for whichever is true).
Inside my Participants partial form, I have:
<%= f.simple_fields_for :scope do |participants_s| %>
<%= participants_s.simple_fields_for :participant do |par| %>
<%= f.label 'Are participants reimbursed for their costs?', :class => 'question-project' %>
<%= par.collection_radio_buttons :costs, [[true, ' Yes'], [false, ' No']], :first, :last, {:item_wrapper_class => 'fixradio'}, {:class => "response-project"} %>
<%= f.label 'What amount will you pay for participation costs?', :class => 'question-project' %>
<%= par.select :participation_cost_currency,
options_for_select(major_currencies(Money::Currency.table)), selected: :participation_cost_currency,
label: false,
prompt: "Select your costs currency" %>
<%= par.input :participation_cost, label: false, placeholder: 'Whole numbers only', selected: :participation_cost_pennies, :input_html => {:style => 'width: 250px; margin-top: 20px', class: 'response-project'} %>
For the first problem, you'll want to set a default value for the participation_cost_cents column in a migration:
# in console
> rails g migration change_default_for_participation_cost_cents
# in migration file
class ChangeDefaultForParticipationCostCents < ActiveRecord::Migration
def change
change_column :participants, :participation_cost_cents, :integer, default: 0
end
end
I'm not sure I follow on the second problem though. Maybe you should split the question in two?
A meetup group for Rails has helped me answer this question. The answer is not obvious - especially for newcomers.
My problem was I had an attribute in my database called participation_cost. Monetise then tried to make a method with the same name and that was failing because of the attribute in my table. For others, you don't need the attribute in your database with the name of the field you want to monetise.
Removing that attribute (in my case, participation_cost) solved my problem.

Ruby on rails, split a big form into separate smaller forms

Could not find the answer to the question on how to split forms in rails in multiple smaller forms.
Say you have a big form with
firstname
lastname
gender
age
email
country
city
state
I have a validate_presence for all these fields. So when I create several forms like:
= simple_form_for #profile, :wrapper => :inline do |f|
= f.label "firstname"
= f.select :firstname
without all the values from the top list (first name,last name,etc) I get errors because the splitter form does not contain those values and they need to be present at first to make this work at all.
What would be a good way to have several forms but with only a portion of the values and update them without getting the issue described above?
If you want the ability to update pieces of the model then you need to split the validations into pieces as well.
One way to do it is to have a virtual attribute in your model that gets set by a hidden field in each form. E.g you may have a form:
= simple_form_for #profile, :wrapper => :inline do |f|
= f.hidden :form, :input_html => {:value => 'names'}
= f.label "firstname"
= f.select :firstname
Then in your model:
class Profile
attr_accessor :form
validates :firstname, :presence => true, :if => lambda { |o| o.form == "names" }
end
The validation will run only if the change was submitted from the right form.
Check out conditional validation guide: http://guides.rubyonrails.org/active_record_validations_callbacks.html#conditional-validation for more details.
Other way is a multistep form as suggested by apneadiving: http://railscasts.com/episodes/217-multistep-forms This uses the same technique as in the first example by having a current_step attribute, but the progression is linear.
You're asking for a multi step form I guess.
Look at this screencast, you'll find conditional validations which are the way to proceed.

Accept Rails model attribute only if it was previously blank

I have a Rails model (persisted with Mongoid) that can be collaboratively edited by any registered user. However, I want to allow editing any particular attribute only if it was previously blank or nil.
For example, say someone created an object, and set its title attribute to "Test Product". Then another user comes along and wants to add a value for price, which until now has been nil.
What's the best way to do this, while locking an attribute that has previously been entered?
Look into the ActiveRecord::Dirty module for some nice utility methods you can use to do something like this:
NON_UPDATABLE_ATTRIBUTES = [:name, :title, :price]
before_validation :check_for_previously_set_attributes
private
def check_for_previously_set_attributes
NON_UPDATABLE_ATTRIBUTES.each do |att|
att = att.to_s
# changes[att] will be an array of [prev_value, new_value] if the attribute has been changed
errors.add(att, "cannot be updated because it has previously been set") if changes[att] && changes[att].first.present?
end
end
The easiest way, i think, is by checking for it in the form itself.
Just say add :disabled => true to the input field if the person cannot edit it.
<% if #my_object.name %>
<%= f.text_field :name, :disabled => true %>
<% else %>
<%= f.text_field :name, :disabled => true %>
<% end %>
(i think there is a prettier way to write this code)
But by using this the user has a visual feed back that he can't do something, it is always better to not allor something than to give an error message

an example of a nested form in simple form?

I am still struggling both writing the controller and the actual form to be able to nest one form in another with an optional model?
I have Message which has many contacts
When submitting a message, I want to add a contact optionally.
I have this as an example:
= simple_form_for Message.new, :remote => true do |f|
#message_form
= f.error_messages
%p
= f.input :account_name, :url => autocomplete_account_name_messages_path, :size => 40, :as => :autocomplete
%p
= f.input :topic, :required => true,
:input_html => {:size => 30}
#add_contact_btn
= link_to "Add Contact"
#contact_form
= f.simple_fields_for :contactd do |fc|
= fc.input :email
= fc.input :first_name
= fc.input :last_name
= f.submit 'Give'
= f.submit 'Request'
For Message.rb model, I have the following:
has_many :contacts
accepts_nested_attributes_for :contacts, :reject_if =>:all_blank
Note: When I used :contacts in the simple_fields_for it didn't work, so it is singular. But the reverse for accepts_nested_attributess_for.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Here is what I see passed as form data from google chrome:
message%5Baccount_name%5D:McKesson
message%5Btopic%5D:testing a contact
message%5Bbody%5D:testing this
sender_id:
receiver_id:23
message%5Bcontacts%5D%5Bemail%5D:888#gmail.com
message%5Bcontacts%5D%5Bfirst_name%5D:Ang
message%5Bcontacts%5D%5Blast_name%5D:Name
The correct method name is simple_fields_for (notice the plural)
Also, you need to keep the f. to call it on the simple_form object
I have a small project where I demonstrate how to use nested forms in simple-form, combined with cocoon (a gem I created to add/remove nested elements dynamically).
The project is on github.
Hope this helps.
In my create controller for message, I included message.contacts.build
But right now I am still generating nil contacts.
Make sure you put in your Message.rb model the ability for it to accept the attributes too.
class Message < ActiveRecord::Base
attr_accessible :contacts_attributes
has_many :contacts
accepts_nested_attributes_for :contacts
I know it doesn't answer your question fully but it may have just been this. When it comes to my project, it would return nil if i didn't include the :contacts_attributes, in my case it deals with products. Hope this helps even if I'm not using simple form as of now!
I faced similar issues working with nested forms. As suggested by JustinRoR you need to define
attr_accessible: contacts_attributes.
You should be able to test the hash in the ruby console ( I am not sure if you have tried this). I suggest you to print the params[:message] and use this to create message from console like Message.new(params[:message]). (Note params[:message] is what you get by printing the params[:message] hash).
Once it works in console it should work like charm

Formtastic non-model form, integration with external site, override/specify the input ID values

I'm using formtastic to collect information from a form and post dirctly to an external site.
I have no problem generating the form itself. However, since this is being submitted to an external site, they require that each input field have the specific IDs they specify, eg email or last_name -- not the closest Formtastic form, eg _email_input or _last_name_input.
I've looked at the Formtastic v1.2.3 code and I'm 90% sure the answer is "sorry, can't do that." I figured it couldn't hurt to check if I'm missing something. I would like some way to specify the ID completely, as in:
= semantic_form_for('', :url => "https://external_site.com/handler, :method => "post") do |form|
= form.input :last_name, :id => "last_name"
[etc]
Is this possible?
(I will note that I recognize that another, arguably superior approach would be to create an appropriate controller, sanity check the parameters locally, and dispatch the remote call from within the app only when it's well formed; however, that's not what I'm trying to do at the moment.)
Firstly i think you need to use semantic_fields_for for non-model forms. Next, to pass ids to each field, you can use the input_html options to specify them. for eg
form.input :email, :input_html => {:name => 'email', :id => 'email' }

Resources