I followed this devise wiki documentation on how to write a custom update action for the registration controller when you want to allow users to edit their account without providing their passwords except if changing their passwords themselves.
Devise Wiki - How to Allow Users to Edit Account Without Providing a Password.
However, I can't figure out what's missing in my Rspec test to make it pass. Here are the relevant code snippets:
app/controllers/registrations_controller.rb
def update
#user = User.find(current_user.id)
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
else
# remove the virtual current_password attribute
# update_without_password doesn't know how to ignore it
params[:user].delete(:current_password)
#user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case their password changed
sign_in #user, :bypass => true
redirect_to users_path
else
render "edit"
end
end
spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password 'XXXXXXXXX'
first_name { Faker::Name.first_name }
middle_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
end
end
spec/controllers/registrations_controller_spec.rb
describe "PUT #update" do
login_pcp
let(:user) { FactoryGirl.create(:user, first_name: 'Tom') }
it "changes user attributes" do
attrs = FactoryGirl.attributes_for(:user, first_name: 'Jerry')
attrs.delete(:password)
put :update, user: attrs
user.reload
assigns[:user].should_not be_new_record
expect(user.first_name).to eq 'Jerry'
expect(flash[:notice]).to eq 'You updated your account successfully.'
end
end
When I run the spec I get the following error:
Failures:
1) RegistrationsController PUT #update changes user attributes
Failure/Error: expect(user.first_name).to eq 'Jerry'
expected: "Jerry"
got: "Tom"
(compared using ==)
# ./spec/controllers/registrations_controller_spec.rb:55:in `block (3 levels) in <top (required)>'
For some reason, it's not saving the update. I'm not sure if a password should be entered in order for the update to take place? Any help would be appreciated. Thanks!
The test now looks like this and it passes:
describe "PUT #update" do
before :each do
#request.env['devise.mapping'] = Devise.mappings[:user]
user_tom = FactoryGirl.create(:user, email: 'tom#test.com')
sign_in user_tom
end
it "changes user attributes" do
put :update, user: { email: 'jerry#test.com' }
subject.current_user.reload
assigns[:user].should_not be_new_record
expect(subject.current_user.email).to eq 'jerry#test.com'
expect(flash[:notice]).to eq 'You updated your account successfully.'
end
end
I ran into this issue as well, but as I can see it's because when you fill the update form, you will be required to fill in a field called "Current password". Since the data won't be updated unless you fill in the filed. When you use factory girl to produce user data, there is no this value. I solved it as can be seen in following code.
describe "PATCH #UPDATE" do
before :each do
#user = create(:user)
#old_email = #user.email
sign_in #user
end
context 'valid attributes' do
it "updates user attributes" do
patch :update, id: #user,
user: attributes_for(:user, current_password: "password")
expect(#user.reload.email).not_to eq(#old_email)
end
end
end
Related
I am using cancancan authentication mechanism in my rails application. I want only those users who are owner of their own posts and comments t be edited and deleted , and admin to manage all the things. my admin ability is working fine but others are not working. here is my ability.rb file
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.admin?
can :manage, :all
else
can :read, :all
can :create, Topic
can :update, Topic do |topic|
topic.try(:user) == user
end
can :delete, Topic do |topic|
topic.try(:user) == user
end
can :delete, Comment do |comment|
comment.try(:user) == user
end
# can :manage, Comment, :task => { :user_id => user.id }
can :update, Comment do |comment|
comment.try(:user) == user
end
end
end
end
what should i do in order to work it properly. its working properly for topics but not for comments
this is the line in my topics contrller
load_and_authorize_resource :topic
If it is working for topics there must be something wrong with your comments model. Does the comments table have a user_id column that stores the author of the comment or any other way to check its ownership? It might be that comment.try(:user) returns nil and it then fails to give the users the right permissions.
I went off the Devise Page so that users can change their password inside ActiveAdmin. The method 3 doesn't work as is, so I had to modify it a bit for ActiveAdmin
ActiveAdmin.register_page 'UserPassword' do
def user_params
params.required(:user).permit(:password, :password_confirmation)
end
page_action :update_password, method: :post do
#user = AdminUser.find(current_admin_user.id)
if #user.update(params.required(:user).permit(:password, :password_confirmation))
# Sign in the user by passing validation in case their password changed
sign_in #user, :bypass => true
redirect_to admin_root_path, notice: "Your password was changed"
else
redirect_to admin_userpassword_path, alert: "Your password couldn't be changed"
end
end
content do
render partial: 'edit', locals: {user: current_admin_user}
end
end
I had to change if #user.update(params.required(:user).permit(:password, :password_confirmation)) because if (#user.update(user_params)) would throw an error saying undefined local variable or method user_params
is the code I have listed above the correct approach to being able to let users change their passwords inside the ActiveAdmin layout?
I would register the User model as a resource in ActiveAdmin. Then you can use the form block to create a form, where the use can change the password.
ActiveAdmin.register User do
...
form do |f|
inputs 'Details' do
input :password
input :password_confirmation
end
actions
end
...
end
With this code, the test works as expected. Red when :admin is in params and green when :admin is removed.
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch :update, id: #other_user, user: { password: 'password',
password_confirmation: 'password',
admin: true }
assert_not #other_user.reload.admin?
end
user params in users controller
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation, :admin )
end
If I test using the code below, it is always red and from what I can see allows toggling of adminregardless if I do or don't have :admin in permitted params. Why can I change the status of a user from admin to not an admin using this code?
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch :update, id: #other_user, user: { password: 'password',
password_confirmation: 'password',
admin: #other_user.toggle!(:admin) }
assert_not #other_user.reload.admin?
end
What happens is that when you do #other_user.toggle!(:admin), this doesn't just return a value; it actually does toggle the admin value. This is a toggle that happens within the (test) code, not through the web, hence why it is not filtered by user_params. What you probably want to test is:
admin: !#other_user.admin?
I am new to Rails and programming in general. I am using Devise for registration and have a User model with two roles: project_manager and sales.
Upon registration, if a user selects project_manager, I would like registration to continue as usual. If sales is selected, I need to check the email domain against a whitelist.
Initially I was able to get this working using two separate models for devise and validating the email format of the Sales model with:
validates_format_of :email, :with => /\A([^#\s]+)#(company\.com)|(work\.com)\z/
I cannot figure out how to run this validation based on a selected role before a user record is created.
validates :email, format: { with: /\A([^#\s]+)#(company|work)\.com\z/i }, if: ->(user) { user.role == 'sale' }
validates_format_of :email, :with => /\A([^#\s]+)#(company\.com)|(work\.com)\z/, if: ->{ |user| user.role == 'sale' }
I created a starter app from RailsApps with the rails-devise-pundit example app. I am trying to write a user controller test because I plan to change some functionality and I want to make sure things still work. The pundit UserPolicy is not returning the correct value which is based on a role enum in the User class. The UserPolicy.index? method seen below is returning false when called from the first test in UsersControllerTest. Sorry there is a lot of code and detail here. I hope everyone can follow it.
Here's the failing test in UsersControllersTest. The response is a :redirect instead of :success.
require "test_helper"
class UsersControllerTest < ActionController::TestCase
def setup
#admin = users(:admin)
#admin.role = :admin
end
test "should get index page when authenticated as an admin" do
sign_in #admin
get :index
assert_response :success
end
...
end
Here's my user controller class just showing the index method where my problem is. authorize #users should call the UserPolicy.index? method.
class UsersController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized, except: [:show]
def index
#users = User.all
authorize #users
end
...
end
My pundit user policy class. When I change the index? method so it returns true, the response in my UsersControllerTest is :success. So for some reason #user.admin? is not returning the correct value.
class UserPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
#user.admin?
end
...
end
What is even stranger is that I created a UserPolicyTest class and when I test calling index? from there, I get the correct response. This test works correctly:
require 'test_helper'
class UserPolicyTest < ActiveSupport::TestCase
def setup
#admin = users(:admin)
#admin.role = :admin
end
def test_index
policy = UserPolicy.new #admin, nil
assert policy.index?
end
end
Here is my User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
enum role: [:user, :vip, :admin]
after_initialize :set_default_role, :if => :new_record?
validates :name, presence: true
def set_default_role
self.role ||= :user
end
end
Here's my test fixture for an admin user:
admin:
email: admin#example.com
name: Mr Admin
role: admin
encrypted_password: $2a$10$PoBe1MvkoGJsjMVTEjKqgeBUp.xdfzWoiDjBzQhtLAj16NqIa2fOy
remember_created_at: nil
sign_in_count: 3
current_sign_in_at: 2014-01-02 08:31:23
last_sign_in_at: 2014-01-02 08:31:23
current_sign_in_ip: 127.0.0.1
last_sign_in_ip: 127.0.0.1
confirmation_token: nil
confirmed_at: 2014-01-02 08:31:23
confirmation_sent_at: 2014-01-02 08:30:59
created_at: 2014-01-02 08:30:59
updated_at: 2014-01-02 08:31:23
I found that setting the role in the fixture doesn't work. I'm guessing that's because of the after_initialize :set_default_role, :if => :new_record? line in my User model. If there's another reason or a better way to handle this, please let me know.
UPDATE: Maybe this is being caused by strong parameters. When I tried debugging my code with pry, I found that in the UsersControllerTest, after signing in, the admin user had a role of 2 which is correct. But when it got to User.Policy.index?, the role was 0. I may need to add the role field to the devise strong parameters. I saw something about how to do that a while back. It didn't look easy. If someone knows the answer before I get to it, please let me know.
After I changed the value of #admin.role in setup, I didn't save the user. After adding #admin.save to the setup method, the test passed.