Question

(Using Rails 3) I have authlogic set up to manage my users and user sessions (with a user controller and user_sessions controller and associated views, models, etc). I have two levels of user: an admin and a client. The admin can create users and edit users, and users are only able to look at documents on the website (no user editing or viewing privileges at all).

Then I implemented another controller (reset_password) which allows clients to change their password on the website. To do this I allowed clients to edit clients (so they can edit themselves). But The form that allows clients to change their password is much more limited than the form that allows admins to make clients.

The problem I am having is that when a client goes to the password editing form, and enters an invalid password or password confirmation, the page refreshes to the full admin form that lets clients change their privileges, etc. What I need is a way to force a failed form entry to return the client to the same form that they were just on rather than the admin form.

The problem, I am pretty sure, is because both the admin form and the client form use 'form_for @user do |f|' to define the form. I believe that rails cannot tell the two apart.

Here are the relevant files:

My admin form (users#edit with rendered form):

= stylesheet_link_tag :universal
= form_for @user do |f|
    - if @user.errors.any?
    #error_explanation
        %span.boldHeader= "#{pluralize(@user.errors.count, "error")} prohibited this user from being saved:"
        %ul
            - @user.errors.full_messages.each do |msg|
                %li= msg

    .fieldContainer
        .field
            = f.label :username
            = f.text_field :username
        .field
            = f.label :role
            = f.collection_select :role, User::ROLES, :to_s, :humanize, { :selected => :client }
    .fieldContainer
        .field
            = f.label :password
            = f.password_field :password
        .field
            = f.label :password_confirmation
            = f.password_field :password_confirmation
    .fieldContainer
         .field
            = f.label :first_name
            = f.text_field :first_name
         .field
            = f.label :last_name
            = f.text_field :last_name
        .field
            = f.label :firm_name
            = f.text_field :firm_name
    .fieldContainer
        .field
            = f.label :email
            = f.text_field :email
        .field
            = f.label :phone 
            = f.text_field :phone
    .actions
        = f.submit 'Save'
        %a.button{ :href => "/users" }Cancel

My client form (reset_password#edit with rendered form):

= stylesheet_link_tag :universal
= form_for @user do |f|
    - if @user.errors.any?
        #error_explanation
            %span.boldHeader= "#{pluralize(@user.errors.count, "error")} prohibited this user from being saved:"
            %ul
                - @user.errors.full_messages.each do |msg|
                    %li= msg

    .fieldContainer
        .field
            = f.label :password
            = f.password_field :password
        .field
            = f.label :password_confirmation
            = f.password_field :password_confirmation

    .actions
        = f.submit 'Save'
        %a.button{ :href => "/users" }Cancel

Client form update method:

def update
    @user = current_user
    if @user.update_attributes(params[:user])
        format.html { redirect_to @user, notice: 'Password successfully changed.' }
        format.json { head :no_content }
    else
        format.json { render json: @user.errors, status: :unprocessable_entity }
        format.html { redirect_to "/settings" }
    end
end

Admin form update method:

  def update
    respond_to do |format|
      if @user.update_attributes(params[:user])
        format.html { redirect_to @user, notice: 'User was successfully updated' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

Note: /settings routes to reset_passwords#edit which has a partial render in it which is how I display the client form (see above). I also have users, user_sessions, and reset_passwords as resources in my routes file.

Finally, here is what I am seeing in the production log. You can see that I start in reset_password/_form (the client form) but then I get rolled back to users/_form (the admin form).

Started GET "/settings" for 127.0.0.1 at 2013-07-02 16:26:52 -0400
Processing by ResetPasswordsController#edit as HTML
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
  Rendered reset_passwords/_form.html.haml (4.0ms)
  Rendered reset_passwords/edit.html.haml within layouts/application (14.0ms)
  Rendered shared/_header.html.haml (1.0ms)
Completed 200 OK in 57ms (Views: 54.0ms | ActiveRecord: 0.0ms)
[2013-07-02 16:26:52] WARN  Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true


Started GET "/assets/global.css?body=1" for 127.0.0.1 at 2013-07-02 16:26:52 -0400
Served asset /global.css - 304 Not Modified (0ms)
[2013-07-02 16:26:53] WARN  Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true


Started PUT "/users/1" for 127.0.0.1 at 2013-07-02 16:26:58 -0400
Processing by UsersController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"9KeHrOySL3FRhSnfYtVvKl2rCz9EPBSyGmtGGasDxnA=", "user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Save", "id"=>"1"}
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", "1"]]
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
   (0.0ms)  begin transaction
  User Exists (0.0ms)  SELECT 1 AS one FROM "users" WHERE ("users"."persistence_token" = '5d1007bf94b34a7de44707e4ca432b18338f1c9ef6c5f99bf04845096a6b880057b5b43c917b9d38f790521ef62147eec3d9302471da73967048b48b4a399b18' AND "users"."id" != 1) LIMIT 1
   (1.0ms)  rollback transaction
  Rendered users/_form.html.haml (9.0ms)
  Rendered users/edit.html.haml within layouts/application (19.0ms)
  Rendered shared/_header.html.haml (0.0ms)
Completed 200 OK in 80ms (Views: 61.0ms | ActiveRecord: 1.0ms)
Was it helpful?

Solution

I needed to specify the route for the form to take. Previously I had this in my "client" form (reset_password#edit with rendered form):

= form_for @user do |f|

I changed this to the following:

= form_for @user, :url => reset_password_path(@user) do |f|

I then also made some changes to the controller for the form:

def update
    @user = current_user
    @user.password = params[:user][:password]
    @user.password_confirmation = params[:user][:password_confirmation]
    respond_to do |format|
        if !@user.changed?
            format.html { redirect_to "/settings", notice: 'Form incomplete' }
            format.json { render json: @user.errors, status: :unprocessable_entity }
        elsif @user.save
            format.html { redirect_to "/index", notice: 'Password successfully changed' }
            format.json { head :no_content }
        else
            format.html { redirect_to "/settings", notice: 'Form incomplete' }
            format.json { render json: @user.errors, status: :unprocessable_entity }
        end
    end
end

The part that I needed was the respond_to do |format|, the rest are just some logic changes to make it work better for my application.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top