Respond to remote form_for with html or json? - ruby-on-rails

I've currently got several partials that contain forms for different AR models. I'm using these to build a dashboard-style pages so I can do an ajax post to CRUD controllers (rather than posting to a bunch of different actions within a dashboard controller).
Since I'm using AR to validate the data, I'd like to be able to just render the correct partial with the correct object in the CRUD controller and use that as my response. Something like this:
if #note.save?
flash[:notice] = "Note successfully saved!"
render '_note_form', :layout => false
else
flash[:notice] = "Something went wrong. Note was not saved!"
flash[:error] = #note.errors.full_messages.to_sentence
render '_note_form', :layout => false
end
Then I'm adding a js function to handle that response by replacing the form with the response like so:
$(function() {
$('form#new_note_form').bind('ajax:success', function(evt, data, status, xhr){
$("#new_note_form").replaceWith(xhr.responseText);
});
});
This gives me validation the same type of validation that I would see if I did a standard post, but only refreshes the partial rather than the entire page.
I can't help but to feel like there is a better way to handle this. Should I just be passing the error/success messages via json and handling them appropriately on the front-end? Is it bad practice to just replace the entire form?

Related

API Controller Specifically for shared methods

Would it be best practice to create some sort of 'general api controller' for shared methods among different models in my RoR application? For example I'm including email validation for Customers - once the user finishes typing in the email address (before submit), an $.ajax POST request is sent to the customers_controller where an external API is being called. After parsing the response I return a response back depending on certain conditions. The validation is handled in real time on the front end before submitting so that the user can know if an email address is valid or not before submitting. The controller method is below.
def validate_email_address
response = ValidateEmailAddressJob.perform_now(params[:email], "Agentapp")
data = JSON.parse response.body
if data['result']['verdict'] == "Valid" || data['result']['verdict'] == "Risky"
html = "<div class='text-success'><i class='fa fa-check'></i> This email appears to be valid</div>"
render json: {html: html, result: data['result']}, status: 200
elsif data['result']['verdict'] == "Invalid"
html = "<div class='text-warning'><i class='fa fa-exclamation'></i> This email may be invalid</div>"
render json: {html: html, result: data['result']}, status: 200
else
render json: {}, status: 503
end
end
I'm trying to keep my code as DRY as possible, so if I wanted to share this email_validation controller method with VendorContacts for example, I'm thinking I could just point the other VendorContact API call to /customers/validate_email_address endpoint even if it's not technically a customer that's needing the validation? Or I could just copy and paste the controller method into the vendor_contacts_controller? Or I could create a separate general api controller with an endpoint such as /validation/emailaddress.
Thanks ahead of time for any help, I'm just trying to learn what the appropriate way to handle this situation would be.
I don't know if this would be considered "best practice" but as a long-time Rails dev, my approach would be to create an EmailController controller, with a single action create, and an Email model (not inheriting from ActiveRecord::Base).
class EmailController < ApplicationController
def create
email = Email.new(params[:email])
if email.valid?
render partial: :valid_email_response
else
render partial: :invalid_email_response
end
end
end
So the Email model has an initialize method, and a valid? method, and the html response is contained in separate view files. Of course, nothing is saved to the database here. The EmailController#create action is invoked any time you wish to validate an email address.
My rationale is that controllers should implement CRUD methods, and in this case a model should handle the validation. I believe this is entirely consistent with the "Rails Way".
IMHO Rails controllers can become catch-all classes for all kinds of application logic, and that coercing the logic into MVC format makes the cleanest code.

Add a "Thank you for your order" page that is not directly visitable

When the user purchases an item we want to display a thank you page.
Then again we want this page not to be directly visitable.
In other words, if the page was to be /orders/thankyou, the user could directly navigate to it which would be rather ugly and fail aas it would have nothing to show.
how can we show this page only when in context and forbid direct navigation to it?
You can create a partial form and append it to the DOM after the purchase event is fired. In the partial view, be sure to add a redirect action if it was accessed without the purchase event firing.
For just displaying short text (and not also e.g. the order data) you could just use a flash notice. For example:
redirect_to #page, notice: "Thank you for your order!"
Ajax
Sounds like you'll be best using ajax:
#app/views/cart/index.html.erb
<%= form_tag cart_order_path, remote: true do |f| %>
... (cart form)
<%= f.submit %>
<% end %>
This (obviously very simple) form will send a "remote" (Ajax) form submission to your controller. The reason why this is important is because you will then handle the response directly in that page you just sent the request from:
#app/assets/javascripts/application.js
$(document).on("ajax:success", "#your_form_id", function(status, data, xhr) {
$("your_page_div").html(data);
});
The trick here will be to render your thank you view without a layout -
#app/controllers/cart_controller.rb
class CartController < ApplicationController
respond_to :js, only: :create
def create
... business logic here
render "thank_you", layout: false
end
end
This will render the view you want without any of the supporting "layout" HTML - giving you the chance to append that to your current view. This means that if you wanted to show the "Thank You" view without letting the user browse to it directly - that's what you'll do
How It Works
Ajax (Asynchronous Javascript and XML) is a javascript technology which basically allows you to send "pseudo requests" to your Rails backend:
Basically the same as a standard HTTP request, except handled with Javascript, Ajax gives you the ability to create the appearance of "no refresh" functionality in your app. It does this by sending requests on your behalf, through Javascript.
Ajax is typically used for small pieces of functionality on web interfaces - whereby you'll have the ability to send simple requests to the server, gaining a comparable response that you can then work into the DOM.
This is the functionality I have been proposing - whereby you'll be able to send a request to your controller (albeit using the Rails UJS engine), to which you'll then receive a response. This response can then be worked into your page, thus providing you with the ability to show the "Thank You" page without the user refreshing.
You can use any solution from the following:
Using ActionDispatch::Flash:
flash[:notice] = "Thank you for your order!"
redirect_to #page
or
redirect_to #page, notice: "Thank you for your order!"
Using alert, in show.js.haml file (assuming you use action show in orders_controller.rb):
alert("Thank you for your order!");
and then add respond_to :js, only: :show, and format.js in action show for orders_controller.rb

Displaying an error message without reloading the page in Rails

I have a page that lets one create records - if the validations aren't satisfied, it redirects to the same page and shows an error message. Here's that snip from the controller:
def create
#signature = Signature.new(signature_params)
if #signature.save
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
end
The trouble is, this is resulting in a full page refresh - so the error message isn't visible because the input form is placed under the fold of the page. I can place it at the top of the page, of course, but is there a way to show the message without reloading the page? Thanks.
OK, so here's what I've settled on:
1) I'm handling validation on the client side with HTML5 "required" attributes - they were created for this explicit purpose and no other gems or plugins are needed. They are supported in all major browsers. Details in this article.
2) I've moved the error messages to the top of the page to handle the case in which a user either is on an old or mobile browser or has JavaScript disabled. Error messages must work with a complete request-response cycle (even if this means re-loading the page) before they work with anything else - this is the unobtrusive JavaScript approach.
3) For the AJAX version, I'm going to be using remote: => true on the form element as explained in the Rails guides. I might be making this open source once I'm done with the callback part of it, and will post a link here.
Obviously, handling errors with flash is the most uniform & DRY way to show the user what's going on, but if you're willing to think outside the box, you'll be able to use Ajax to accomplish a similar job by just handling the errors yourself:
Code Example
#app/controllers/signatures_controller.rb
def create
#signature = Signature.new(signature_params)
if #signature.save
#success = "true"
end
respond_to do |format|
format.js { #errors = error_messages(#signature) }
format.html {
if #success.defined?
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
}
end
end
#app/views/signatures/create.js.erb
<% unless #success.defined? %>
alert(<%=j #errors.inspect() %>)
<% end %>
#app/assets/javascripts/signatures.js
$(document).on("submit", "#signature_form", function() {
$.ajax({
url: "/signatures"
type: "POST"
data: $(this).parent().serialize(); //serialize the form (not the button)
error: function() {
alert("Sorry, there was an error!");
}
});
});
You'd actually be better using JSON for this. If you like the idea, I can refactor it to include JSON for you!

Don't render layout when calling from Ajax

I have a rails action called index that renders the content for my page along with the layout. When I go to the /index action with a browser it works like expected. I want to be able to also render this action by calling it with Ajax, I am doing this using the following:
<%= link_to "Back", orders_path, :id => 'back_btn', :remote => true %>
<%= javascript_tag do %>
jQuery("#back_btn").bind("ajax:complete", function(et, e){
jQuery("#mybox").html(e.responseText);
});
<% end %>
When the action is called this way I would like it to render and pass the index action back, excluding the layout. How can I do this?
You should be able to add a format.js action to your controller action like so:
respond_to do |format|
format.js
format.html
format.json { render json: #foos }
Ideally, you would want to create a index.js.erb file that would build the contents of the page:
$('#foos_list').update("<%= escape_javascript(render(#foos)) %>");
If you're going to update the contents of a div, to basically update a whole page inside of a layout, then you're going to want to change it up a little bit. Inside of the format.js, you can do this:
format.js { render 'foos/index', :layout => false }
But if you're trying to go with an ajaxified front-end, may I recommend a framework for doing this, like Spine? It will go a long way in helping you build your site.
Also, using a framework like this will force you to separate your application per #Zepplock's second suggestion.
You can just detect if the request is an XML HTTP Request, then render a blank layout like so:
render layout: 'blank' if request.xhr?
You'll need to create a blank layout in app/views/layouts/blank.html.erb like this:
<%= yield %>
You need a way to let server know that there's a difference in request type. It can be done in several different ways:
Append a key value to the URL (for example layout=off) and change your controller logic to render data with no view. This is kind of a hack.
Make your controller return data via XML or JSON (controller will know what content type is being requested) then format it accordingly and present in browser. This is more preferred way since you have a clear separation between content types and is better suited for MVC architecture.
Create an API that will serve data. This will lead to separate auth logic, more code on client side, additional APi controller(s) on server etc. Most likely an overkill for your case

I am a newb to JSON, and am trying to build a multiple page AJAX form with it

I apologize for sounding daft in advance, but I recently discovered JSON in Rails and I'm really interested in creating a form that is broken up through several pages. The data would then be carried from page to page through a JSON object.
I'm stuck on how to properly to do that.
My first page of the form is this :
First Page
Controller:
def create
# removed some code here for the sake of simplicity
if #card_signup.save
respond_to do |wants|
wants.html {}
wants.json { render :json => { :html => (render_to_string :partial => 'final_form')}}
end
else
respond_to do |wants|
wants.html { render :new }
wants.json { render :json => { :errors => #card_signup.errors } }
end
end
end
The Javascript that handles saving the first form to this controller
$(".step_two_submit").click(function(){
$("#new_card_signup").attr("action", function(index, action) {
var values = [],
getKey;
$("#sortable").sortable('serialize').replace(/(\w+?)\[]=(\d+?)/g, function(all, key, number) {
values.push(number);
getKey = key;
});
var cardSignupGetParams = 'card_signup=' + getKey + '[' + values.join(', ') + ']';
return action + '?' + cardSignupGetParams + "&foo[]=bar";
});
});
Here's the tough part! I want to make it so that I validate if this form was written correctly as usual. No problem, the form is set up to do that. But when they click next, I want them to go to a second form which is a disclaimer they have to agree to before the Object is saved.
So if they back out, or don't approve of the disclaimer, the Object will have never been created.
To accomplish this.. I assume I need to instead of saving the object, I need to store the object in a JSON object and carry it over via AJAX to this next AJAX loaded disclaimer page.
Does anyone know how I might be able to splice that together?
Again, I apologize if this doesn't make much sense.
What you really want to do is validate the object before you go to the second part of the page. If you definitely want to make a round trip to the server, you can use #card_signup.valid? instead of #card_signup.save in the controller of your "check" action, and then just hide the form elements with javascript and show your disclaimer. This way you are still checking that it would save when it's finally submitted, but not actually saving it into the database.
Another way to do this without javascript would be to have the first page submit to the controller action that just checked for a valid object, and go to a second page which then renders most of the form elements as hidden elements, effectively carrying the filled-in form to the second page without saving it.
Alternately, you can do the validation on the client side as much as possible and not hit the server. See the Client Side Validations railscast for an interesting and easy-ish way to get this done.

Resources