8
votes

I'm having a problem where when a user fills out my evaluation form, click "Create", then click the browser's back button, make some edits, and click "Create" again, it's creating duplicate Evaluations.

What is the best way to prevent something like this happening.

Only ONE evaluation should exist for each survey_criterion on creation. I don't want the user to lose any data they enter after hitting the back button, filling out the form with new stuff, and clicking "Create" again.

UPDATE

routes.rb

resources :survey_criteria do
  resources :groups do
    resources :evaluations
  end
end

survey_criterion.rb

has_many :evaluations

evaluation.rb

belongs_to :survey_criterion
belongs_to :group

There are more complicated associations, but the answer I'm looking for is more, "how does one handle it when users press the 'Back' button, modify the form, then click Create again".

I want it to update the one that was automatically created I think in this instance, and not throw an error to the user. I know I could add a validation that would error out, but I want this to be invisible to the user I think.

Thoughts?

4
can you be more specific about the nature of the relation between the two models ? and about your create action ?m_x
When I go into work tomorrow I'll try to add some more information. Thanks.ardavis
it might be useful to have information about your form, too. Do you create the associated evaluation along with the new survey_criterium (in a nested form), or do you create both in different forms ? If i understand well what you're trying to achieve, a nested form should do the trick, because hitting the back button will get you back at the new survey_criterium form, ensuring that if you hit submit again you will try to create a new survey_criterium instead of a new evaluation.m_x

4 Answers

8
votes

The simplest solution, would be to change the create action, which should work like this pseudocode:

def create
  # ...
  if evaluation_exists?
    update_evaluation(params[:evaluation])
  else
    create_evaluation(params[:evaluation])
  end
  # ...
end

As for Your question "how does one handle it when users press the 'Back' button, modify the form, then click Create again", then I use some random token (a short string) placed as a hidden field in the form.

When the create-request comes, I check whether this token is already stored in the session. If it is not, then I create the object, and add that token to the list of used ones. If the token is already present in the session, I know that user has just resubmitted the form, and I can act accordingly. Usually I ask him whether another object should be created. In the session I store usually not more that 3-5 tokens.

It looks like this (yes, that's just an illustration):

def create
  token = params[:token]
  session[:tokens] ||= []
  if session[:tokens].include? token
    render_the_form_again( "You have already created the object. Want another?" )
  else
    create_the_object
    session[:tokens] << token
  end
  # ...
end
1
votes

In your Evaluation model, add this line :

validates_uniqueness_of :survey_criterion_id

This is assuming that SurveyCriterion holds the foreign key that associates with your Evaluation.

1
votes

You can also do 2 things :

  1. Prevent the browser cache.
  2. Disable the Create button with :disable_with => "Processing" option.

It is discussed here too: https://stackoverflow.com/a/12112007/553371

0
votes

A less elegant way but more generic way to do this is to use history.pushState. On the page after create:

$(function(){
  if(history.pushState){
    window.onpopstate = function(event){
      if(window.history.state && window.history.state.previousStep){
        window.location = window.history.state.previousStep;
      }
    }
    window.history.replaceState({ previousStep: '#{edit_resource_url(resource)}'}, document.title, window.location);
    window.history.pushState({}, document.title, window.location);
  }

})

This example uses HTML5's History API. A similar thing can be done with fallback using the history.js project