rails validation confirmation after form submit - ruby-on-rails

I've got a form with a date and I want prompt the user if they submit the form outside of a range, but still give them the opportunity to submit outside the range.
For instance (pseudocode)
if date < start_date and current_user.admin?
answer = ask 'are you sure you want to submit below the date range?'
elsif date > end_date
answer = ask 'are you sure you want to submit over the date range?'
else
answer = 'yes'
end
if answer == 'yes'
submit
else
return to original form
end
I was thinking one solution would be a multi-form wizard type of implementation but is there an easier way?
For instance, first I would show the original form with the date field. Then when the user hits the 'submit' button check with the server if we have to ask the user to confirm their out of range date, if so show a partial and wait for them to hit the 'confirm' or 'yes' button.

To me it sounds like you should have an intermediary action in your controller which you submit the form to. This intermediary action validates the form parameters, and renders the partial or just submits the form accordingly. The partial's submit button would contain a link to the final action which deals with submission.

Related

Submit button with Options

For one form, I want to have two submit buttons , named:
Appove, Decline
when the user clicks on Approve it should send approve: true as well
when the user clicks on Decline it should send approve: false as well
Basically the buttons should work like a checkbox
Is this possible with Rails and how to implement it? Thanks
If you're using the standard submit form helper, you will get returned a param with the key "commit". You can test for this in your controller code.
<%= f.submit 'Approve' %>
<%= f.submit 'Decline' %>
in the controller...
def create
approved = params[:commit] == 'Approve'
The approved variable will then contain true or false and you can use it as needed in the rest of the action / method.
You can do it but you need js/jquery for this. You will have hidden checkbox that you will check in proper way and two buttons.
Lets assume that your form has id 'form'. And you checkbox has id 'approve_checkbox'
In this case you need submit function for something like this.
$('#approve_button').on('click', function(e){
e.preventDefault();
$('#approve_checkbox').prop('checked', true);
$('#form').submit();
});
$('#decline_button').on('click', function(e){
e.preventDefault();
$('#approve_checkbox').prop('checked', false);
$('#form').submit();
});
Of course you can simplify this code, but I think idea is clear.

Different action on submit of form depending on a variable's value

I have a checkout form which includes a field to enter a coupon code. Currently, the form always posts to a payment merchant. However, I want it to take a different action/route if the amount due is 0 because of the coupon code.
The view page contains:
# Form where to enter coupon code
<%= form_for #coupon, method: :post, url: {action: "check_coupon", :controller => 'coupons', format: 'js'}, remote: true do |f| %>
<%= f.submit "Submit Coupon" %>
<%= f.text_field :couponcode, :placeholder => "Enter coupon" %>
...hidden fields...
<% end %>
# Form sent to payment merchant
<form action="https://secure.***.com/order.html" method="post" >
<input type="hidden" name="payment" value="<%=#payment%>" class='js-couponpayment' />
...other hidden fields...
</form>
The controller method check_coupon just validates the coupon and sets some of the variables that exist as hidden fields in the form to be send to the payment merchant. This includes a variable called #payment which specifies the sum to be paid. This sum gets updated based on the coupon entered. Ajax is used to display the updated sum on the screen but to keep it secure check_coupon, and not the script which is only for the number shown on screen, sets the actual sum to be send to the merchant.
If #payment is zero, I would like the second form not to post to the merchant, but instead redirect to root_path with a flash message. How can I do this? How can I make the action for the second form variable depending on the updated value for #payment (preferably using Ruby and not javascript)?
From what I understand, you don't want to update the payment hidden field with Ajax because you think rendering it server-side would be more secure. But, this is not the case AFAIK, because regardless of where you render, if you are still displaying the form and making users submit the form, there is always possibility of end user fiddling with the hidden field values. After all, from wherever you rendered, it will always end up the same HTML. So, security must be coped with other measures such as making an HMAC authenticity hash with a private key.
With the security considered, a pure ruby solutions would be to not use ajax at all. Instead make check_coupon action render a completely new page that checks the sum after coupon code and render the updated payment form again if sum > 0, otherwise redirect to root_url with flash.
But above solution costs a full page reload and reduces interactivity(user has to wait for the page reload). If you don't want that, you can make you ajax response send back a script that both updates the payment form and the sum user sees. Keep in mind that, with rails, you can still use ruby to render the payment form server-side. An eg. js response would be
$('#container-div-of-payment-form').html(<%= j render("payment_form_partial")%>);
//code to update the sum user sees here
Within your payment_form_partial, you can check if the updated sum is zero or not and sets the form url accordingly.
Hope you found this useful.

Search across multiple controllers

So I'm working on a Rails project in which I have a frontend interacting with multiple controllers to retrieve information. I have instructors and courses controllers and I am trying to implement a search feature where I can select which controller to search from using a drop down box, and when I click the search button, it will only search the selected controller.
My search box on the home page looks like this:
http://i.stack.imgur.com/o7yzk.png (SO won't let me embed an image)
The code to make it is below:
<%= form_tag(params[:option], :method => "get", id: "search-form") do %>
<%= select(:option,options_for_select([['Instructors', 'instructors'], ['Courses', 'courses'],['Departments','departments']])) %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil, html: {class: "button button-red"}%>
<% end %>
Special note on line 1 where I use params[:option], this populates with the information from the 'option' field, so if I select 'Instructors', the option becomes instructors.
Problem: On the homepage when I enter a name and click search the url that is created is as follows:
http://0.0.0.0:3000/?utf8=%E2%9C%93&option=%2Finstructors&search=John
and it takes me right back to the homepage. I can see in the debug information, that it isn't even getting to the right controller, it's only looking at the static pages controller.
If I click the search button again it brings me to the correct page with the correct search results:
http://0.0.0.0:3000/instructors?utf8=%E2%9C%93&option=%2Finstructors&search=john
I'm using Solr to do the searching, and I have everything working properly except this pathing issue which requires multiple clicks. My controller code for instructors index looks like this:
def index
if params[:search] != ''
#search = Instructor.search do
fulltext params[:search]
end
#instructors = #search.results
else
#instructors = Instructor.all
end
end
I feel like I am making an architectural mistake somewhere, and any input would be appreciated.
I feel like I am making an architectural mistake somewhere, and any input would be appreciated.
Exactly right. The mistake is this: params[:option] is a serverside value. In order to get to the server, you are creating a request using the form submission. The form decides where to submit using the serverside params[:option]. It's like trying to pull yourself by your own bootstraps.
In more detail, what happens is this:
The serverside receives the request for your /
The serverside renders your form. The form's action is set to params[:option], but such an option is not set. The default value for a form action when it is not specified is the same page you're on.
The clientside displays the form. The user selects Instructors and submits the form.
The clientside contacts the form's action (i.e. the same page), and transfers the form's values (option and search)
The serverside / receives the request, and renders the form
This time, params[:option] is set to instructors
The client submits the form again; this time, the form has a correctly set action (the received params[:option] from the last cycle)
Two easy approaches to solve this would be:
Use JavaScript to dynamically modify the form's action attribute as the option is selected
Make a dedicated search controller/action that will handle search for both kinds, depending on the value of params[:option]
Make another action at serverside that will redirect your request to one of the other two actions, depending on the value of params[:option]

Rails - Submit multiple forms on same page

Model:
Users have expenses. Expense has a status.
View:
As users add their expenses, they are shown in a list. Each expense row has a form button on the end, which is used to submit the expense (changing the status of the expense). This allows users to add expenses they have not completely filled out, and submit them when they are ready. There is no parent form on this page, just the form buttons which submit the expense to a method which changes the status, and then reloads the page.
Currently it works great, but users have asked to be able to "submit all" the expenses that are showing on the view with a single button.
Question:
What is the proper way to handle this in rails? Should I find a way to gather the array of expense id's and then submit a separate form? Is there a way to ask for a set of records present in a view with a certain status?
Thanks!
Another option, if I'm thinking about this right (big if), would be to wrap your page in a User form. Then you could have something like...
<%= form_for(#user) do |f| %>
<% #user.expenses.each do |expense| %>
<% f.fields_for expense do |e| %>
<!-- expense form -->
<% end %>
<% end >
<% end %>
This is something you could submit as a whole. I'm having trouble picturing what a single expense addition might look like, but hopefully this gets you a little further down the road.
Edit: in addition to having this User form on the page, you could have an "extra" Expense form to create an expense. When you submit a new expense, that expense appears in the list under the user form, where it can be edited or submitted, either as part of a group or individually (as part of a "group" of 1).
custom controller action:
def update_all_expense_statuses
expenses = current_user.expenses
ExpenseUpdater.new(expenses).update_expense
redirect_to :back
end
expense updater class:
class ExpenseUpdater
def initialize(expenses)
#expenses = expenses
end
def update_expense
#expenses.each do |expense|
expense.update_attributes(status: 'paid')
expense.save
end
end
end
This is just an example of one way to update all the user's expenses with a custom controller action. Just call the controller method from a link_to:
<%= link_to "Update all expenses", update_all_expense_statuses_path %>
Remember to add it to your routes. Hope this helps.
The first thing you should do is change the forms to submit remotely, ie make an ajax request. Then you're not reloading the whole page. Check out Rails' various "remote" form helpers, eg "remote_form_for".
Then, write a javascript function to submit all the forms for inputs that have changed since the page loaded. You'd probably want to add a "changed" (or similar) class to the parent form in an onchange event in each input, to facilitate this. I think this is the best way to handle the "status" thing you're asking about. Make a "Submit all" button which calls this function.
Use a form/service object http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ to encapsulate expense report

Confirm leave page unless they clicked the save button

I have a form where I create a model and I want a dialoge box to appear if the user navigates away from the page, unless they click the save/create button.
I have this javascript code that works anytime the user leaves the page; meaning this dialoge still appears when the user clicks save/create.
#javascripts/workouts.js.coffee
window.confirmExit = () ->
"Your changes will not be saved.";
#workouts/new.html.haml
= render 'form'
:javascript
window.onbeforeunload = confirmExit
#workouts_form.html.haml
= simple_form_for(#workout) do |f|
# some input fields
= f.button :submit
Now I know that I only want the confirm exit called if the submit button is not clicked, but I am not sure how to go about doing this.
What I usually do is I only show the confirmation when changes actually have been made to the form. To do that, you'll need to scan the forms on your page, serialize them and store it in memory. Then, when the user leaves the page, serialize the form again and see if there is a difference.
Otherwise, you might want to bind some function to submit event of the form, setting some global boolean like window.formSubmitted to true. Check that variable when leaving the page to determine if you want to show the confirmation box.
So I figured it out, at least it works for chrome and safari although right now I am under the impression that it will not work for ie. What I did was create a boolean that is set to false and only set to true when the submit button is selected.
#javascripts/workouts.js.coffee
window.submitButtonClicked = false
window.confirmExit = () ->
if window.submitButtonClicked
null
else
"Your changes will not be saved."
$(document).ready ->
$('#target').submit ->
window.submitButtonClicked = true
#workouts/new.html.haml
:javascript
window.onbeforeunload = confirmExit
#workouts_form.html.haml
#target
= simple_form_for(#workout) do |f|
# some input fields
= f.button :submit
I hope this helps anyone who was curious. Note that the #target line is actual haml and not a comment.

Resources