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' }
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.
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 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
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.
I'm following Ruby on Rails Tutorial by Michael Hartl. I have reached chapter 9. There are some codes that I don't understand. Please see below, line 4, why do we need a ! in front of current_user?(user) to make it !current_user?(user)Didn't we need true && true for the if statement to pass so Admin can see the delete action.
If a user logged in and is a admin, then current_user.admin? == true, current_user?(user) = true, and !current_user?(user) will be false and the if statement won't pass.
_user.html.erb
<li>
<%= gravatar_for user, size: 52 %>
<%= link_to user.name, user %>
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,data: { confirm: "You sure?" } %>
<% end %>
</li>
sessions_helper.rb
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
Thanks in advance.
Only Admins can delete users and they can't delete themselves.
From railstutorial.org Chapter 9.4:
administrative users should see such links, and by clicking on a delete link we expect an admin to delete the user
and
Note also that we have added a test to verify that the admin does not see a link to delete himself
So:
current_user.admin? means "is the current user an admin?"
!current_user?(user) means "is the current user different from the user I am displaying?"
Put together, <% if current_user.admin? && !current_user?(user) %> as a test for displaying the Delete link means that the delete link only displays for Admins but not when their own user details are being displayed. That is, only Admins can delete users and they can't delete themselves.
Remember, the user view is displaying a list of the users, not just the current user. In this context, user means the model of the user that is being displayed by the view at that time.
Check this image and note that Example User does not have a delete link but everyone else does.