8

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?

ardavis
  • 9,842
  • 12
  • 58
  • 112
  • can you be more specific about the nature of the relation between the two models ? and about your `create` action ? – m_x Dec 05 '11 at 22:38
  • When I go into work tomorrow I'll try to add some more information. Thanks. – ardavis Dec 05 '11 at 22:42
  • 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](http://guides.rubyonrails.org/2_3_release_notes.html#nested-object-forms) 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 Dec 06 '11 at 00:17

4 Answers4

8

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
Arsen7
  • 12,522
  • 2
  • 43
  • 60
  • Thank you for putting time into this answer, it really helps me think about it, and should work just fine for what I needed. – ardavis Dec 06 '11 at 16:14
  • 1
    I think that if someone uses a back button on a create form, he should expect to create a new object every time, it's just common sense. On the other hand, i do admit that that @Arsen7's solution is more user-friendly – m_x Dec 06 '11 at 16:51
  • 3
    I think if someone made a typo in their create form, quickly hit back not realizing it, fixed their typo and clicked create again, it would not be 'common sense' to them at that point. They would just be confused why it didn't do what they wanted. Users and common sense don't exactly mix :) – ardavis Dec 06 '11 at 17:38
  • That'll look great in all your controllers. – Damien Roche Sep 21 '12 at 14:30
1

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

Community
  • 1
  • 1
Jérémie
  • 305
  • 3
  • 10
1

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.

Trip
  • 26,756
  • 46
  • 158
  • 277
  • Well, it might be a bit more complex than that. I'll touch base on this tomorrow when I go back into work. Thanks. – ardavis Dec 05 '11 at 22:41
0

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

Akshay Rawat
  • 4,714
  • 5
  • 40
  • 65