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?
Related
Scenario: Show email in landing page when you click on password reset link
Given I am a user and I clicked on the password reset link inside my email
Then I am redirected to 'Change Password' page
And I see two password fields
Expected I also see the email of the user whose password is to be reset
Problem: How to get the email from only the :reset_password_token value?
Output from console when I binding.pry in the view /views/devise/passwords/edit.html.erb
[1] pry(#<#<Class:0x007fd4ec6f65f8>>)> resource
=> #<User id: nil, email: "", encrypted_password: "", reset_password_token: "Ba-9txxmmUMkU_xywypz", 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, created_at: nil, updated_at: nil, first_name: nil, last_name: nil, customer_admin_id: nil, admin_id: nil, confirmation_token: nil, confirmed_at: nil, confirmation_sent_at: nil, organization_id: nil, status: nil, middle_name: nil, type: nil, cell_number: nil, phone_number: nil, extension: nil>
also When I invoke User.find(5) I see
#<User id: 5, email: "recruiter_one+org_one#mysite.com", encrypted_password: "$2a$10$v7gYTdfoIZ9yCVIx3Xb5lOYVPly71NHtOc1mbWuHxZvt...", reset_password_token: "095a18fc2455c39e4838c322e3124d0052cf2cc0e86b7fe3486...", reset_password_sent_at: "2015-06-29 10:01:56", remember_created_at: nil, sign_in_count: 10, current_sign_in_at: "2015-06-29 10:01:04", last_sign_in_at: "2015-06-29 09:35:56", current_sign_in_ip: "192.164.79.122", last_sign_in_ip: "192.164.79.122", created_at: "2015-06-12 05:06:50", updated_at: "2015-06-29 10:01:56", first_name: "Rec One Loc One", last_name: "Peter", customer_admin_id: nil, admin_id: nil, confirmation_token: nil, confirmed_at: "2015-06-12 05:13:49", confirmation_sent_at: "2015-06-12 05:06:50", organization_id: 3, status: "active", middle_name: "", type: nil, cell_number: "", phone_number: "1231232131", extension: "">
That is:
[4] pry(#<#<Class:0x007fd4ec6f65f8>>)> User.find(5).reset_password_token
CACHE (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 5 LIMIT 1 [["id", 5]]
=> "095a18fc2455c39e4838c322e3124d0052cf2cc0e86b7fe3486f72a67c5a5315"
[5] pry(#<#<Class:0x007fd4ec6f65f8>>)>
095a18fc2455c39e4838c322e3124d0052cf2cc0e86b7fe3486f72a67c5a5315 from database does not match with Ba-9txxmmUMkU_xywypz I get from resource
PS: I use devise (3.2.4)
You should get digest from password_reset_token by
digiest = Devise.token_generator.digest(self, :reset_password_token, original_token)
where original_token is Ba-9txxmmUMkU_xywypz
Next, You can find resource by
user = User.find_by_reset_password_token(digiest)
Also, You should override password_controller#edit, and define #email variable
class PasswordsController < Devise::PasswordsController
# GET /resource/password/edit?reset_password_token=abcdef
def edit
digiest = Devise.token_generator.digest(resource_class, :reset_password_token, params[:reset_password_token])
#email = resource_class.find_by_reset_password_token(digiest).email
self.resource = resource_class.new
set_minimum_password_length
resource.reset_password_token = params[:reset_password_token]
end
end
in routes.rb
devise_for :users,
:controllers => {
:passwords => 'passwords'
}
For the latest version of devise (>= v3.3.0), you could simply use the method given by devise with_reset_password_token(:token)
User.with_reset_password_token('sxZo2fk8hux6hKyufpCx')
This will return the user if exists or returns nil.
You also would want to override the edit method of Devise::PasswordsController, or for the simplest use, keep the above code in a helper.
I have a class Node that I have setup acts_as_taggable on, so I can add user_tags to any node. I also have a method on my Node model that will look up the actual User records for all the users in the user_tag_list. Here is an illustration:
[32] pry(main)> m = Node.find(85)
Node Load (8.6ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 85]]
=> #<Node id: 85, name: "House Fire 2", family_tree_id: 57, user_id: 57, media_id: 228, media_type: "Video", created_at: "2015-05-15 00:20:26", updated_at: "2015-05-20 01:06:34">
[33] pry(main)> m.user_tags
ActsAsTaggableOn::Tag Load (3.8ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND "taggings"."context" = 'user_tags' [["taggable_id", 85], ["taggable_type", "Node"]]
=> [#<ActsAsTaggableOn::Tag id: 4, name: "gerry#test.com", taggings_count: 1>, #<ActsAsTaggableOn::Tag id: 6, name: "danny#test.com", taggings_count: 1>]
[34] pry(main)> m.user_tag_list
ActsAsTaggableOn::Tag Load (0.8ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'user_tags' AND taggings.tagger_id IS NULL) [["taggable_id", 85], ["taggable_type", "Node"]]
=> ["gerry#test.com", "danny#test.com"]
[35] pry(main)> m.tagged_users
User Load (5.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'gerry#test.com' LIMIT 1
User Load (2.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'danny#test.com' LIMIT 1
=> [#<User id: 52, email: "gerry#test.com", encrypted_password: "$2a$10$KaX1kvtIw1.jGITnt9Czqeq3xTzhY3OM052NSHsL5Lf...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 5, current_sign_in_at: "2015-04-03 17:10:28", last_sign_in_at: "2015-04-03 00:38:24", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2015-03-05 01:36:31", updated_at: "2015-04-03 17:10:28", first_name: "Gerry ", confirmation_token: nil, confirmed_at: "2015-03-05 01:36:52", confirmation_sent_at: nil, unconfirmed_email: nil, invitation_relation: "uncle", avatar: nil, invitation_token: nil, invitation_created_at: "2015-03-05 01:36:31", invitation_sent_at: "2015-03-05 01:36:31", invitation_accepted_at: "2015-03-05 01:36:52", invitation_limit: nil, invited_by_id: 1, invited_by_type: "User", invitations_count: 0, bio: nil, last_name: "Atrick", gender: 0>,
#<User id: 58, email: "danny#test.com", encrypted_password: "$2a$10$ZpzLH17iFrOXzH4U/pOX.e4nwN.9IJ1s1Ap/zQglk9K...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 6, current_sign_in_at: "2015-05-26 04:36:32", last_sign_in_at: "2015-04-03 00:14:55", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2015-03-12 03:39:28", updated_at: "2015-05-26 04:36:32", first_name: "Daniel", confirmation_token: nil, confirmed_at: "2015-03-12 05:46:18", confirmation_sent_at: nil, unconfirmed_email: nil, invitation_relation: "son", avatar: nil, invitation_token: nil, invitation_created_at: "2015-03-12 03:39:28", invitation_sent_at: "2015-03-12 03:39:28", invitation_accepted_at: "2015-03-12 05:46:18", invitation_limit: nil, invited_by_id: 57, invited_by_type: "User", invitations_count: 0, bio: nil, last_name: "Marty", gender: 0>]
So, ideally, what I would like to do is allow users to search for say Gerry and it would return Node.id = 85 from the above example, because Node.id = 85 has a user tag with the first name Gerry.
This is my existing pg_search on Node:
pg_search_scope :node_search, against: [:name, :user_id, :circa],
using: { tsearch: { any_word: true} },
:associated_against => {
comments: [:message],
user: [:first_name, :last_name, :email],
memberships: [:relation]
}
I feel like I should be able to use pg_search's dynamic scope, but I can't quite grok it.
How would I achieve this?
It turns out that the answer is pretty straight-forward.
Given that this is the declaration of acts-as-taggable-on in my Node model:
acts_as_taggable_on :user_tags
The solution is simply to add this column in the pg_search declaration like so:
include PgSearch
pg_search_scope :node_search, against: [:name, :user_id, :circa],
using: { tsearch: { any_word: true, dictionary: :english} },
:associated_against => {
comments: [:message],
user: [:first_name, :last_name, :email],
memberships: [:relation],
user_tags: [:name]
}
Note that I had to specify the field within user_tags (i.e. the :name attribute).
So now, it searches against that virtual attribute of :user_tags like I wanted.
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 using acts_as_commentable_with_threading gem with my Rails 4.0 application.
I have Notification controller:
class Notification < ActiveRecord::Base
acts_as_commentable
# ......
end
Now using build_from to create a comment is not working:
>> n = Notification.last
Notification Load (2.3ms) SELECT "notifications".* FROM "notifications" ORDER BY "notifications"."id" DESC LIMIT 1
=> #<Notification id: 134, owner_id: 223, sender_id: 247, n_type: "SAVED_SEARCH", url: nil, content: "2219", read: false, created_at: "2015-01-02 19:52:54", updated_at: "2015-01-02 19:52:54", archived: false, short_url: "http://www.some_url.com">
>> u = User.last
User Load (1.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
=> #<User id: 252, email: "my#email.com", encrypted_password: "$2a$1...", 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, first_name: "eqbalosss", last_name: nil, created_at: "2015-01-01 20:16:52", updated_at: "2015-01-01 20:16:52", provider: nil, uid: nil>
>> Comment.build_from(n, u.id, "test")
=> #<Comment id: nil, commentable_id: 134, commentable_type: "Notification", title: nil, body: "test", subject: nil, user_id: 252, parent_id: nil, lft: nil, rgt: nil, created_at: nil, updated_at: nil>
>> Comment.count
(1.0ms) SELECT COUNT(*) FROM "comments"
=> 0
I checked out the documentation and I have no clue what am I doing wrong? do I still have association between Comment and Notification? I assume that the gem would do that already.
Any help would be appreciated.
When you use build_from when are not actually saving the comment into the database; instead you're just building it based on your User model.
So, when you perform Comment.count you're querying your database and since the comment wasn't saved, it returns zero results.
You have to call either comment.save or comment.save! after building it in order to persist it to the database.
I hope it helps
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