Search code examples
ruby-on-rails-3ruby-on-rails-4deviserspec-rails

Porting from Rails 3.2 to Rails 4.2 - Devise - registration controller bypasses validations


I have mostly ported my application from Rails 3.2 to Rails 4.2. (about 2 out of 700 tests are still failing).

In the previous Rails stack, I used:

  • Rails 3.2.18
  • Ruby 2.1.5
  • Devise 3.2.2
  • RSpec 2.13.1

Now I'm using

  • Rails 4.2.5
  • Ruby 2.2.4
  • Devise 3.5.3
  • RSpec 2.14.1

My user class looks like this:

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :confirmable , :validatable  

  attr_accessible :name, :email, :password, :password_confirmation, :identity_url, 
                  :remember_me, :terms

  validates :terms, acceptance: { accept: 'yes' }
end

My registrations controller looks like this:

class RegistrationsController < Devise::RegistrationsController
  def new
    @title = "Sign up"
    super
  end
end

I have an RSpec test for the registrations controller which looks like this:

describe "failure to accept terms" do
  before(:each) do
    @attr = { :name => "New User", :email => "[email protected]",
              :password => "foobar", :password_confirmation => "foobar", :terms => "no" }
  end

  it "should not create a user" do
    lambda do
      @request.env["devise.mapping"] = Devise.mappings[:user]

      post :create, :user => @attr
    end.should_not change(User, :count)
    #raise Exception,  User.last.inspect
  end
end

With the previous application stack, the test passed. With the new application stack it fails.

1) RegistrationsController POST 'create' failure to accept terms should not create a user
  Failure/Error: lambda do
     count should not have changed, but did change from 0 to 1

With the old stack or the new stack, if I try the following in the Rails console,

irb(main) > User.all.count
=> 0
irb(main) > @attr = { :name => "New User", :email => "[email protected]",                  :password => "foobar", :password_confirmation => "foobar", :terms => "no" }
irb(main) > u = User.create(@attr)
irb(main) User.all.count
=> 0
irb(main) u.errors.messages
=> {:terms=>["must be accepted"]} 

......the user is not created, and u.errors yields the validation error for the terms. This is what should happen.

When I uncomment the "raise Exception" line in the test, I get the following:

#<User id: 2, name: nil, email: "[email protected]", created_at: "2016-01-22 18:13:35", updated_at: "2016-01-22 18:13:35", encrypted_password: "$2a$04$oySRVGtQQrbKkp9hmvHlIuA8kdpEASMkhnQ4rDuDC9L...", salt: nil, admin: false, reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, confirmation_token: "_deYRJswq4yLLNrNNRAz", confirmed_at: nil, confirmation_sent_at: "2016-01-22 18:13:36", authentication_token: nil, unconfirmed_email: nil, terms: nil, identity_url: nil >

It looks like the user gets created, but key data such as the name, and terms are not assigned.

The long and the short? With Devise, user validations all ran with the Rails 3.2 stack, and did not run with the Rails 4.2 stack.

Is there a way to ensure that validations are run when using the Devise-based registrations controller?


Solution

  • This issue is due to the switch to strong parameters in Rails 4.

    Here's another good thread that shows an example of this same concept.

    In the original post the code samples show that the attributes are still being declared in the model via attr_accessible.

    In order for the terms validation to be triggered that parameter must be specified in a controller. In this case since you are using an overridden Devise controller you can override the sign_up_params method there in.

    So in app/controllers/registrations_controller.rb do try the following:

    class RegistrationsController < Devise::RegistrationsController
    
      def new
        @title = "Sign up"
        super
      end
    
      private
    
        def sign_up_params
          params.require(:user).permit(:email, :password, :password_confirmation, :terms)
        end
    end
    

    That will allow the "terms" checkbox to make through correctly the model causing the validation of the check box to happen correctly.