I've followed this tutorial for the exact same use case of assigning roles to users.
I'm using Rails 3.2.16.
When I try testing role assignment from the pry console I get that the roles field of User is not updated.
here's the pry text:
[14] pry(main)> a = User.last
User Load (0.3ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
=> #<User id: 7, email: "a#b.c", encrypted_password: "...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 1, current_sign_in_at: "2013-12-29 11:14:36", last_sign_in_at: "2013-12-29 11:14:36", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2013-12-29 11:14:04", updated_at: "2013-12-31 09:50:26", username: "asf", confirmation_token: nil, confirmed_at: "2013-12-29 11:14:26", confirmation_sent_at: "2013-12-29 11:14:04", unconfirmed_email: nil, roles_mask: 0>
[15] pry(main)> a.roles = [:admin, :expired]
=> [:admin, :expired]
[16] pry(main)> a.save
(0.1ms) BEGIN
(0.1ms) COMMIT
=> true
Here's the relating methods from the model:
ROLES = %w[admin active expired]
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
end
def roles
ROLES.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
end
def role_symbols
roles.map(&:to_sym)
end
and here's the migration:
class AddRolesMaskToUsers < ActiveRecord::Migration
def change
add_column :users, :roles_mask, :integer
end
end
Would you know why the association is not persisted here?
thanks a lot in advance.
I figured that I was matching strings with symbols in the roles= method and this would not work.
I've converted everything to symbols now, and it works nicely.
Here's the new code:
ROLES = [:admin, :active, :expired]
def roles=(roles)
self.roles_mask= (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
end
def roles
roles_symbols.reject { |r| ((roles_mask || 0) & 2**ROLES.index(r)).zero? }
end
Related
A question please!
I don't know how to only give permission to user to update only his own data:
ability.rb
elsif user.has_role? :user
can :update, User, id: user.id
else
user_controller.rb
def update
a = Ability.new(current_user)
user = User.find params[:id]
if a.can? :update, User, id: user.id
...
This returns true on any ID.
Aditional data with pry:
[3] pry(main)> a = Ability.new(u)
Role Load (0.7ms) SELECT `roles`.* FROM `roles` INNER JOIN `users_roles` ON `roles`.`id` = `users_roles`.`role_id` WHERE `users_roles`.`user_id` = 3 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))
Role Load (0.4ms) SELECT `roles`.* FROM `roles` INNER JOIN `users_roles` ON `roles`.`id` = `users_roles`.`role_id` WHERE `users_roles`.`user_id` = 3 AND (((roles.name = 'user') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))
=> #<Ability:0x007f908ba931e8
#rules=
[#<CanCan::Rule:0x007f908be9b2b8
#actions=[:update],
#base_behavior=true,
#block=nil,
#conditions={:id=>3},
#match_all=false,
#subjects=
[User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, sign_in_count: integer, current_sign_in_at: datetime, last_sign_in_at: datetime, current_sign_in_ip: string, last_sign_in_ip: string, created_at: datetime, updated_at: datetime, auth_token: string, name: string, surnames: string)]>],
#rules_index=
{User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, sign_in_count: integer, current_sign_in_at: datetime, last_sign_in_at: datetime, current_sign_in_ip: string, last_sign_in_ip: string, created_at: datetime, updated_at: datetime, auth_token: string, name: string, surnames: string)=>
[0]}>
[4] pry(main)> a.can? :update, User, id: 3
=> true
[5] pry(main)> a.can? :update, User, id: 2
=> true
What i'm doing wrong?
Thanks!
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :user
can :update, User, id: user.id
else
can :read, :all
end
end
end
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find params[:id]
authorize! :update, #user
...
end
end
From the docs:
CanCanCan expects a current_user method to exist in the controller.
You don't need to explicitly invoke Ability, it's done via authorize! or can?
Im learning ruby on rails and have a trouble with aasm callbacks and actionmailer.
I have a hotels model. Heres a code:
class Hotel < ActiveRecord::Base
include AASM
scope :approved_hotels, -> { where(aasm_state: "approved") }
has_many :comments
belongs_to :user, :counter_cache => true
has_many :ratings
belongs_to :address
aasm do
state :pending, initial: true
state :approved
state :rejected
event :approve, :after => :send_email do
transitions from: :pending, to: :approved
end
event :reject, :after => :send_email do
transitions from: :pending, to: :rejected
end
end
def send_email
end
end
As you see user has to get email when state of the hotel he added was changed. Heres what i wrote but its not THE solution cos user gets emails every time admin updates hotel with "pending" state.
class HotelsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show, :top5hotels]
def update
#hotel = Hotel.find(params[:id])
if #hotel.aasm_state == "pending"
#hotel.aasm_state = params[:state]
UserMailer.changed_state_email(current_user, #hotel.name,
#hotel.aasm_state).deliver
end
if #hotel.update_attributes!(params[:hotel])
redirect_to admin_hotel_path(#hotel), notice: "Hotel was successfully updated."
else
render "edit"
end
end
end
So i think i need to use callback but i dont know how to call
UserMailer.changed_state_email(current_user, #hotel.name,
#hotel.aasm_state).deliver
from the model.
I tried
UserMailer.changed_state_email(User.find(:id), Hotel.find(:name),
Hotel.find(aasm_state)).deliver
but that doesnt work.
Im really out of options and looking for any help.
Thanks!
UPDATE 1:
Thank to Amit Sharma! I`ve made these changes and now getting
NoMethodError: undefined method `email' for nil:NilClass
Looks like user object Im passing to changed_state_email() method is nill but I have no idea why.
Here is my mailer file aswell:
class UserMailer < ActionMailer::Base
default from: "localhost"
# Send email to user when hotels state change
def changed_state_email(user, hotel_name, current_state)
mail(to: user.email, subject: 'State of your hotel '+hotel_name+'has been
changed to '+current_state)
end
end
Here is a result of puts "====#{self.inspect}":
====#<Hotel id: nil, name: "CoolName", breakfast: nil, room_description: nil, price_for_room: 34, star_rating: 3, user_id: nil, address_id: nil, created_at: nil, updated_at: nil, average_rating: nil, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, aasm_state: "approved">
F.====#
F.====#
UPDATE 2:
It returns user object. Output from the console:
1.9.3-p551 :006 > h = Hotel.find(1)
Hotel Load (0.4ms) SELECT "hotels".* FROM "hotels" WHERE "hotels"."id" = ? LIMIT 1 [["id", 1]]
=> #<Hotel id: 1, name: "QWERTYUI", breakfast: nil, room_description: nil, price_for_room: 44, star_rating: 4, user_id: 2, address_id: nil, created_at: "2015-05-30 22:55:01", updated_at: "2015-05-30 22:55:01", average_rating: nil, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, aasm_state: "pending">
1.9.3-p551 :007 > h
=> #<Hotel id: 1, name: "QWERTYUI", breakfast: nil, room_description: nil, price_for_room: 44, star_rating: 4, user_id: 2, address_id: nil, created_at: "2015-05-30 22:55:01", updated_at: "2015-05-30 22:55:01", average_rating: nil, photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, aasm_state: "pending">
1.9.3-p551 :008 > h.user
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
=> #<User id: 2, name: "qwerty", email: "qweqweqweqwe#qwe.com", encrypted_password: "$2a$10$FG5xXb/9wYLcdsCrfJtuDOTsslyY8p.m0qkbP4a5OEvJ...", 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, admin: false, created_at: "2015-05-30 22:54:14", updated_at: "2015-05-30 22:54:14", comments_count: 0, hotels_count: 1>
You can Try this. I hope this will help you.
In Hotels Controller.
class HotelsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show, :top5hotels]
def update
#hotel = Hotel.find(params[:id])
if #hotel.pending?
if params[:state] == "approved"
#hotel.approved!
elsif params[:state] == "rejected"
#hotel.rejected!
end
end
if #hotel.update_attributes!(params[:hotel])
redirect_to admin_hotel_path(#hotel), notice: "Hotel was successfully updated."
else
render "edit"
end
end
end
In Hotel model.
def send_email
user = self.user
puts "====#{self.inspect}===#{user.inspect}"
UserMailer.changed_state_email(user, self.name,
self.aasm_state).deliver
end
Please revert back to me if you face any issue.
I'm having an issue with my scheduled text messages. I run a rake task that checks to see if a text message should be put into a Sidekiq queue. The record is processed (the text is sent) but a new empty record is generated and the sentstatus is not updated to "true".
send_scheduled_text.rake
require_relative '../../app/workers/send_text_worker'
namespace :send_scheduled_text do
task:texts => :environment do
TextMessage.all.each do |text_message|
if text_message.sentstatus == false
if (Date.today == text_message.scheduled_date) && (Time.now.hour >= text_message.scheduled_time.hour)
# Sidekiq code:
SendTextWorker.perform_async(text_message.id)
end
end
end
end
end
send_text_worker.rb
class SendTextWorker
include Sidekiq::Worker
def perform(text_message_id)
text = TextMessage.find(text_message_id)
text.send_text_message(text.content, text.phone)
end
end
text_message.rb
require 'twilio-ruby'
require 'date'
class TextMessage < ActiveRecord::Base
belongs_to :client, dependent: :destroy
belongs_to :step, dependent: :destroy
has_many :coach_emails
before_save :grab_phone
def grab_phone
self.phone = phone
end
def send_text_message(message, phone)
twilio_sid = ENV["TWILIO_ACCT_SID"]
twilio_token = ENV["TWILIO_AUTH_TOKEN"]
twilio_phone_number = ENV["TWILIO_PHONE_NUMBER"]
begin
#twilio_client = Twilio::REST::Client.new(twilio_sid, twilio_token)
#twilio_client.account.sms.messages.create(
:from => "+1#{twilio_phone_number}",
:to => phone,
:body => message)
rescue Twilio::REST::RequestError => e
puts e.message
end
if e != "400" || e != "500"
self.sentstatus = true
end
self.save!
send
send
Rails console: before rake task is called
(sentstatus is false)
irb(main):001:0> TextMessage.all
TextMessage Load (0.5ms) SELECT "text_messages".* FROM "text_messages"
=> #<ActiveRecord::Relation [#<TextMessage id: 164, client_id: nil, content: "Testing Sidekiq processing", incoming_message: false, created_at: "2015-02-02 04:43:29", updated_at: "2015-02-02 04:43:29", scheduled_date: "2015-02-01", sentstatus: false, step_id: 4, phone: "+14127364161", scheduled_time: "2000-01-01 14:00:00">]>
Rails console: After rake task is called
(sentstatus is false, should be true. I also have this new bizarre empty record)
irb(main):001:0> TextMessage.all
TextMessage Load (0.5ms) SELECT "text_messages".* FROM "text_messages"
=> #<ActiveRecord::Relation [#<TextMessage id: 164, client_id: nil, content: "Testing Sidekiq processing", incoming_message: false, created_at: "2015-02-02 04:43:29", updated_at: "2015-02-02 04:43:29", scheduled_date: "2015-02-01", sentstatus: false, step_id: 4, phone: "+14127364161", scheduled_time: "2000-01-01 14:00:00">,
#<TextMessage id: 165, client_id: nil, content: nil, incoming_message: nil, created_at: "2015-02-02 04:45:24", updated_at: "2015-02-02 04:45:24", scheduled_date: nil, sentstatus: true, step_id: nil, phone: nil, scheduled_time: nil>]>
I have a feeling this is a Sidekiq nuance that I'm missing. Thanks for any thoughts!
I ended up moving my model logic to my worker. Presto -- timing works and I'm not generating any extra nil records.
So in my wiki model I have an attribute for private. If private is true then the wiki should not be viewable to users who are not assign to the wiki_ids via a HABTM relationship.
wiki.rb:
class Wiki
include Mongoid::Document
include Mongoid::Timestamps
has_and_belongs_to_many :users
field :title, type: String
field :body, type: String
field :private, type: Boolean, default: false
scope :visible_to, ->(user) {
user.present? || user.blank? ?
where(:private => false) : where(:private => false).or(:id => user.wiki_ids)
}
def public?
!self.private?
end
end
WikisController:
def index
##wikis = policy_scope(Wiki)
##wikis = Wiki.all
#wikis = Wiki.visible_to(current_user)
authorize #wikis
end
def show
#wiki = Wiki.find(params[:id])
end
def new
#wiki = Wiki.new
authorize #wiki
end
def create
#wiki = current_user.wikis.build(params.require(:wiki).permit(:title, :body, :private, :user))
authorize #wiki
if #wiki.save
flash[:notice] = "Wiki was saved."
redirect_to #wiki
# report success
else
flash[:error] = "There was an error saving your wiki. Please try again."
render :new
end
I'm pretty confident its the scope that needs to be modified in the model, because if i comment out the scope in the model and replace the index in the controler to Wiki.all. I see all the wikis.
As of right now as somebody who created the wiki plus flagged it private and I am logged in I do not see that wiki nor does anybody that I add as a user to the wiki.
I tried adding other conditions to the end such as user.present? ? where(:id => user.wiki_ids) and user.present? && where(:id => user.wiki_ids) but just get errors thrown back at me.
DB entry for User:
User_id: 547eb8867261691268000000, wiki_ids: [BSON::ObjectId('54807226726 1690be0260000'),
BSON::ObjectId('5480735c7261690bae000000'), BSON::ObjectId('548
136e57261690aef000000'), BSON::ObjectId('5489af337261690d95000000'),
BSON::Objec tId('5489b57e7261690d95010000'),
BSON::ObjectId('548f9f607261690bb5060000'), BSO
N::ObjectId('54908f127261690be8000000'),
BSON::ObjectId('54908f207261690be801000 0')], name: "Carey VonRueden",
email: "admin#email.com", encrypted_password: "$2a
$10$NrlQ2XH64UucOPcI1aje9.57eoSO74676264YrIjfGvncyGcpGWy",
reset_password_token : nil, reset_password_sent_at: nil,
remember_created_at: nil, sign_in_count: 7, current_sign_in_at:
2014-12-17 18:51:15 UTC, last_sign_in_at: 2014-12-16 02:38:5 8 UTC,
current_sign_in_ip: "10.0.2.2", last_sign_in_ip: "10.0.2.2",
confirmation
_token: nil, confirmed_at: 2014-12-03 07:15:18 UTC, confirmation_sent_at: nil, u nconfirmed_email: nil, role: "admin">
DB entry for Wiki:
Wiki _id: 54908f207261690be8010000, created_at: 2014-12-16 19:59:28 UTC, updated_at: 2014-12-16 19:59:28 UTC, user_ids:
[BSON::ObjectId('547eb886726169126 8000000')], title: "Private", body:
"Private", private: true>
your scope condition is wrong
user.present? || user.blank? -> this will be true always. if user is present or user is blank, it will always return only the public wikis
Change your scope to something like below.(assuming you want all public wiki's if user is not signed in. If user is signed in, you want public + the wikis created by user)
scope :visible_to, ->(user) {
user.nil? ? where(:private => false) : where(:private => false).or(:id => user.wiki_ids)
}
If you are still not getting what you are expecting, check if user.wiki_ids is returning the right values
My user model fails to save because password and password_confirmation can't be blank. This happens when:
Updating the user on edit_user_registration and leaving the password and password confirmation blank. This is where I first noticed
On the console, immediately after loading an object and calling save.
Devise will validate the presence of password if password_required?
validates_presence_of :password, :if => :password_required?
def password_required?
!persisted? || !password.nil? || !password_confirmation.nil?
end
This is my rails console output:
Loading development environment (Rails 3.2.11)
1.9.3p374 :001 > u = User.last
User Load (1.0ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
=> #<User id: 9, name: "Name", email: "name#example.com", encrypted_password: "$2a$10$oCL2K0nEa8LxaMGuC1oyNOuwIHk8l/IOW.AylGcWTYj3...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 2, current_sign_in_at: "2013-02-06 20:56:14", last_sign_in_at: "2013-02-06 20:52:01", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmation_token: nil, confirmed_at: "2013-02-07 22:04:01", confirmation_sent_at: "2013-02-07 22:03:25", unconfirmed_email: nil, authentication_token: nil, created_at: "2013-02-06 20:52:00", updated_at: "2013-02-07 22:04:31">
1.9.3p374 :002 > u.save
(0.2ms) BEGIN
(0.2ms) ROLLBACK
=> false
1.9.3p374 :003 > u.errors
=> #<ActiveModel::Errors:0x007f832c04f090 #base=#<User id: 9, name: "Name", email: "name#example.com", encrypted_password: "$2a$10$oCL2K0nEa8LxaMGuC1oyNOuwIHk8l/IOW.AylGcWTYj3...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 2, current_sign_in_at: "2013-02-06 20:56:14", last_sign_in_at: "2013-02-06 20:52:01", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", confirmation_token: nil, confirmed_at: "2013-02-07 22:04:01", confirmation_sent_at: "2013-02-07 22:03:25", unconfirmed_email: nil, authentication_token: nil, created_at: "2013-02-06 20:52:00", updated_at: "2013-02-07 22:04:31">, #messages={:password=>["can't be blank"], :password_confirmation=>["can't be blank"]}>
1.9.3p374 :004 > !u.persisted? || !u.password.nil? || !u.password_confirmation.nil?
=> false
Thanks for your help!
Thanks to #rainkinz, I went back to the basics (the user model) and took a closer look. I found out that a module I was importing was adding an extra
validates :email, :password, :password_confirmation, presence: true
I was able to see this by running in the console :
User.validators
...
#<ActiveModel::Validations::PresenceValidator:0x007fb80fbcd260
#attributes=[:email, :password, :password_confirmation],
#options={}>,
...
This is different than the validations devise puts in, which contain:
#options={:if=>:password_required?}>,
Removing those solved the problem. Thanks for your help!
Do this in your update method.
user = User.find_by_id(params[:id])
unless user.blank?
if user.update_attributes(params[:user])
flash[:notice] = "User updated successfully."
redirect_to "somwhere"
else
render :action => 'edit'
end
else
render :action => 'edit'
end
if you don't want to update the old password then add these line before updation so the new code will be:
user = User.find_by_id(params[:id])
unless user.blank?
params[:user].delete(:password) if params[:user][:password].blank?
params[:user].delete(:password_confirmation) if params[:user][:password_confirmation].blank?
if user.update_attributes(params[:user])
flash[:notice] = "User updated successfully."
redirect_to "somwhere"
else
render :action => 'edit'
end
else
render :action => 'edit'
end
write somthing like this in user.rb model
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :locakable
I've had a similar problem, but the solution that worked for me was calling devise's method again with super after doing what I wanted. For example:
def password_required?
return false if some_check?
super
end
Credits for this other answer that shed light on me.