1
votes

I am going through Michael Hartl's Tutorial on RoR 4.0 and am currently doing the first exercise in chapter 9.

I'm supposed to write a test that confirms that it's not possible to issue a PATCH request to edit the admin attribute on a user. To prevent mass-assignment, the tutorial introduces a user_params function that only permits certain attributes.

In the test, I am issueing

patch user_path(user), params

where params is a hash containing an a true value for admin (see below). After this request, I expect the attribute on the user to still be false.

Here's the problem

While the test (correctly) succeeds with my current code, it also (incorrectly) succeeds when I add the admin attribute to the list of allowed attributes in the user_params function.

Using curl to issue a PATCH request to /users/:id gives an error page that I don't quite understand yet. I've tried replacing PATCH by PUT, having read somewhere that support for this method is fairly new and thinking that I might not have the correct version for each and every gem. I'm using ruby 1.9.3, while the tutorial uses 2.x.

Is there anything I'm missing here? (Any hints on how to simulate these requests in the rails console would be helpful as well!)

I pasted the relevant parts of the code I am using below:


Code:

app/controllers/users_controller.rb


    class UsersController < ApplicationController
      before_action :signed_in_user, only: [:edit, :update, :index, :destroy]
      before_action :correct_user, only: [:edit, :update]
      before_action :admin_user, only: :destroy

      ...
        def update
            @user = User.find(params[:id])
            if @user.update_attributes(user_params)
                sign_in @user
                flash[:success] = "Profile updated"
                redirect_to @user
            else
                render 'edit'
            end
        end

      ...

        private

            def user_params
                params.require(:user).permit(:name, :email, :password,
                                       :password_confirmation, :admin)
            end
      ...

spec/features/users_pages_spec.rb

...
    describe "edit page" do
        let(:user) { FactoryGirl.create(:user) }
        before do
            sign_in user
            visit edit_user_path(user)
        end
...
        describe "directly patch forbidden admin attribute" do
            let(:params) do
              { user: { admin: true, password: user.password, password_confirmation: user.password } }
            end

            before { 
              patch user_path(user), params
            }
            specify {  expect(user.reload).not_to be_admin }
        end
...

app/models/user.rb

class User < ActiveRecord::Base
    before_save { email.downcase! }
    before_create :create_remember_token

    validates :name, presence: true, length: { maximum: 50 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
    validates :password, length: { minimum: 6 }

    has_secure_password

    def User.new_remember_token
        SecureRandom.urlsafe_base64
    end

    def User.encrypt(token)
        Digest::SHA1.hexdigest(token.to_s)
    end

    private

        def create_remember_token
            self.remember_token = User.encrypt(User.new_remember_token)
        end
end
1
I haven't tried the techniques, but see the last two answers at stackoverflow.com/questions/151030/… for how to call controller methods from the Rails console. Oh, and I can't see anything wrong with your code, but I would suggest modifying your post to show the spec with :admin in the permitted params list, since that's the case that isn't behaving as you think it should. You also might share your User model.Peter Alfvin

1 Answers

3
votes

I've had the same problem the other day. As shown in the log

Redirected to http://www.example.com/signin
Filter chain halted as :signed_in_user rendered or redirected
Completed 302 Found in 3ms (ActiveRecord: 0.7ms)

the reason for not getting the tests to red is due to the signed_in_user before filter defined in user controller. Therefore, signing in the user fixes the 'getting the test to red/green' problem you encountered.

describe "forbidden attributes" do
  let(:params) do
    { user: { admin: true, password: user.password,
      password_confirmation: user.password } }
  end
  before { 
    sign_in user, no_capybara: true
    patch user_path(user), params 
  }
  specify { expect(user.reload).not_to be_admin }
end

After inserting the sign_in your tests should be red/green depending on the strong params defined in the controller.

Hope that helps to get you started.

Best, Ben.

P.S.: However, I did not have put much effort into investigating why the mentioned before_filter arrives at the conclusion that the user is not signed in ...