Rails Post method redirecting to edit action rathe

2019-08-30 10:40发布

问题:

I am writing a small rails application and I am attempting to add the ability to for users to reset their passwords in a method similar to rails cast 274 (http://railscasts.com/episodes/274-remember-me-reset-password). I was able to add this functionality, but it now seems to have broken. I have a link to a reset_password form from the signin page. The rails code is below:

 <h1>Reset Password</h1>

<%= form_tag password_resets_path, :method => :post do %>
  <div class="field">
    <%= label_tag  :email %>
    <%= text_field_tag :email, params[:email] %>
  </div>
  <div class="actions"><%= submit_tag "Reset Password" %></div>
<% end %>

And below is the html generated by the browser (for the password reset form, not the page source).

<form accept-charset="UTF-8" action="/password_resets" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /><input name="authenticity_token" type="hidden" value="1UwA3fS90cean/z/g60M3CJYs1RNtHjyy4a1yAssWo0=" /></div>
  <div class="field">
    <label for="email">Email</label>
    <input id="email" name="email" type="text" />
  </div>
  <div class="actions"><input name="commit" type="submit" value="Reset Password" /></div>
</form>

I am somewhat new to rails and html, but I think this form is passing data to my controller via the 'post' method. I think this is what I want, because running rake routes gives me (just for password resets - I also have a user and sessions resource):

users GET    /users(.:format)                    users#index

    password_resets GET    /password_resets(.:format)          password_resets#index
                    POST   /password_resets(.:format)          password_resets#create
 new_password_reset GET    /password_resets/new(.:format)      password_resets#new
edit_password_reset GET    /password_resets/:id/edit(.:format) password_resets#edit
     password_reset GET    /password_resets/:id(.:format)      password_resets#show
                    PUT    /password_resets/:id(.:format)      password_resets#update
                    DELETE /password_resets/:id(.:format)      password_resets#destroy

So I think submitting this form should get me to the create action on my password_resets controller. However, I get:

Routing Error

No route matches {:action=>"edit", :controller=>"password_resets", :id=>nil} 

Which means the form is somehow trying to submit to the edit action. If this is the case, id=>nil makes sense because my form is asking for an :email, not :id. I am not sure why/how this is happening, because I am treating password_resets like a resource (as in the rails cast), and just using the routes rails automagically generates.

Below is my routes.rb file and my password_resets controller file.

MyApp::Application.routes.draw do

  resources :users
  resources :sessions, only: [:new, :create, :destroy]
  resources :password_resets
  root to: 'static_pages#home'

  match '/help',    to: 'static_pages#help'
  match '/about',   to: 'static_pages#help'
  match '/signup',  to: 'users#new'
  match '/remove',  to: 'users#remove'
  match '/signin',  to: 'sessions#new'
  match '/signout', to: 'sessions#destroy', via: :delete
end

And the password_reset controller:

class PasswordResetsController < ApplicationController
  def new
  end

  def create
    user = User.find_by_email(params[:email])
    user.send_password_reset if user
    #this will work regardless of whether user is found
    #a bit more secure, but also a bit more annoying to the
    #user...
    redirect_to root_url,  :notice =>
        "Email was sent with password reset 
        instructions"
  end

  def edit
    @user = User.find_by_password_reset_token!(params[:id])
  end

  def update
    @user = User.find_by_password_reset_token!(params[:id])
    if @user.password_reset_sent_at < 2.hours.ago
      redirect_to new_password_reset_path, :alert => 
        "Password reset has expired."
    elsif @user.update_attributes(params[:user])
      redirect_to root_url, :notice => "Password has been reset!"
    else
      render :edit
    end      
  end
end

I have also created an action mailer to send the emails to the user, but I don't think there is a point to looking there yet because I am breaking before I get to any code that involves actually sending email. I would really appreciate any thoughts/speculation as to why my form is behaving as it is, rather than how I want it to.

Thanks to everyone in advance.

Update:

Thank you cortex! As suggested, I looked at the server log and my form was going to the create action, and the code was going through generating the email. However, the password_reset_token, which I give the user in the email as part of the url where they reset there password, is always nil, Which gives me the error. I produce the token through the following code (which I think is the same as the Rails Cast)

  def generate_token(column)
    begin
      self[column] = SecureRandom.urlsafe_base64
    end while User.exists?(column => self[column])
  end

I try to assign it to the user via:

  def send_password_reset
    self.password_reset_token = generate_token(:password_reset_token)
    self.password_reset_sent_at = Time.zone.now
    self.save(validate: false)
    UserMailer.password_reset(self).deliver
  end

So my question now is why would my generate token methods return a nil value? This is puzzling because I use the generate token method to generate a token to track users throughout a session, and it is working fine there...

Nathan

回答1:

I was able to figure out my bug. Essentially,

begin
  self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])

was causing nil tokens to be stored in the database. I reworked the logic of the function and now it works fine. Thanks to everyone who helped.

 def generate_token(column)
    test = SecureRandom.urlsafe_base64
    if
      User.exists?(column => test)
      self.generate_token(column)
    else
      self[column] = test 
    end
end