开发者

Rails: Prevent duplicate inserts due to pressing back button and save again

Think about a simple Rails scaffold application with a "new" action containing a form to add records to a database with a "save" button. After the "create" action the controller redirects to the "show" action, where the user can use the "edit" link to edit the just inserted record. So far, so simple.

But if the user instead uses the browser's back button after creating a record to get back to the "new" action, the browser shows the form with the values the user just ha开发者_如何学编程s entered. Now he changes some values and presses "save" again. He thinks that this would change the record, but of course this creates a new record.

What is the preferred way to prevent such duplicate entries? I'm looking for a general solution, maybe based on cookies or JavaScript.


After some investigations I found a suitable solution based on cookies. Here it is:

In the controller's "new" action, a timestamp with the current time is generated and rendered in the form as hidden field. When the user submits the form, this timestamps gets back to the controller's "create" action. After creating the record, this timestamp is stored in the session cookie. If the user goes back to the "new" form via browser's back button, he gets a stale form, which means its timestamp is older than the one stored in the cookie. This is checked before creating the record and results in an error message.

Here is the controller code:

def new
  @post = Post.new
  @stale_form_check_timestamp = Time.now.to_i
end

def create
  @post = Post.new(params[:post])

  if session[:last_created_at].to_i > params[:timestamp].to_i
    flash[:error] = 'This form is stale!'
    render 'new'
  else
    @post.save!
    @stale_form_check_timestamp = Time.now.to_i
    session[:last_created_at] = @stale_form_check_timestamp
  end
end

And here the form code:

- form_for @post do |f|
  = tag :input, :type => 'hidden', :name => 'timestamp', :value => @stale_form_check_timestamp
  = f.input :some_field
  = .......


When I had that same problem I created this little gem that solves it. When the user hits back, he's redirected to the edit_path of the record, instead of going back to the new_path.

https://github.com/yossi-shasho/redirect_on_back

You can do something like:

def create
  @user = User.new(params[:user])
  if result = @user.save
    redirect_on_back_to edit_user_path(@user) # If user hits 'back' he'll be redirected to edit_user_path
    redirect_to @user
  end
end


Your model validations will ensure things like email addresses are unique, but I think this is more about usability and experience than anything else.

Say you are talking about an account creation form. First of all, your form submit button should say something like "Create Account", instead of just "Submit". Then depending on whether it was successful or not, show a message like either "Account successfully created" or "There were errors creating your account". If the user sees this message, they will know what happened.

Sure you can't prevent someone from hitting the back button and hitting enter again, but you should design for the majority of use cases. If they happen to hit back, they will see the button that says "Create Account". You should probably have some other text on the page that says "Please sign up for a new account to get started".

Just my $0.02.


Session or cookie may result in sides effects.

I totally agree : if there is a way to validate with your model, it's the safest way to prevent duplicate records.

Still you can do 2 things. Prevent browser caching : fields will appear empty in the form when the user clicks on the back button. And disable the "Create" button when clicked.

= f.submit "Create", :disable_with => "Processing..."

When your user will press the back button the button will be disabled.


You can use validators to make sure that no duplicate values are inserted. In this case validates_uniqueness_of :field

If you for example want to prevent users from having the same email address you could put the following code in your user model.

validates_uniqueness_of :email

This checks the column for any previous entries that are the same as the one your trying to inert. Good luck


base on @Georg Ledermann answer i make this little snip of code for redirect to edit path if the user hits back and then hits create.

#objects_controller.rb
def new
    @object = Object.new
    @stale_form_check = Time.now.to_i
end

def create
    @object = Object.new(object_params)
    #function defined in application_controller.rb
    redirect_to_on_back_and_create(@object)
end

#application_controller.rb
private
def redirect_to_on_back_and_create(object)
    if session[:last_stale].present? and session[:last_stale_id].present? and session[:last_stale].to_i == params[:stale_form_check].to_i 
        redirect_to edit_polymorphic_path(object.class.find(session[:last_stale_id].to_i)), alert: "Este #{object.model_name.human} ya ha sido creado, puedes editarlo a continuación"
    else 
        if object.save
            session[:last_stale] = params[:stale_form_check].to_i
            session[:last_stale_id] = object.id
            redirect_to object, notice: "#{object.model_name.human} Creado con éxito"
        else
            render :new 
        end
    end
end

And finally add the @stale_form_check param to your form

<%= hidden_field_tag :stale_form_check, @stale_form_check %>

You could always abstracts this method where you need it, but in this way you could avoid lots of repetition in your project if you need this behavior in many parts

Hope it helps the next one, i used to use redirect_on_back gem, but it didn't work for me this time, the _usec param that this gem uses, was always been reset, so it can't compare in every time when it was need


Here's something that worked for me.

You will need to do 2 things: Create a method in your controller and add a conditional statement in that same controller under your 'create' method.

1) Your method should return the total count of that object from that user.

EX:

def user current_user.object.count end

2) Add conditional statement in your 'create' method.

EXAMPLE:

def create @object = Object.create(object_params) @object.save if user == 0 redirect_to x_path end

I hope this helps!


Add html: { autocomplete: "off" } in your form_for like this:

<%= form_for @object, url: xxx_path, html: { autocomplete: "off" } do |f| %>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜