I have a ClientServer that has many ClientApplications. I want users to only be able to destroy Servers that are associated with them. Every ClientApplication object has an application_owner_email that is matched against the current_user. If the emails match, they should have destroy permission for the associated Server.
In the abilities.rb, I have the following
if user.has_role?(:application_owner)
can :destroy, ClientServer.all.each do |server|
server.client_applications.each do |app|
app.application_owner_email == user.email
end
end
But this isn't working. I have set up similar conditions. For instance, the below condition works fine:
can :destroy, ClientApplication.all.each do |app|
app.application_owner_email == user.email
end
A user can only destroy ClientApplications where they are the application_owner.
Any help on this would be appreciated.
Enumerable#each returns object self, you probably want .all? or .any?:
can :destroy, ClientServer do |server|
server.client_applications.all?{|app| app.application_owner_email == user.email }
end
Note that all? returns true for empty array.
Also note that abilities with blocks cannot generate scopes and are usually slower.
Related
I have form, that creates some objects in batch. For my flow i have to either save them all at once, or not save at all. Problem is - when I do my validations, they are not failing, since each object, validates according to current records in db(i have uniqueness validation), but i also need to validate current object to each of my unsaved objects. Small example
class User
#field: email
end
In my form object i have an array of users. And in loop i do
#users.each do |user|
valid_users << user if user.valid? #and this is where i need to validate `user` within not just DB, but within #users aswell
end
How do i achieve this?
You first need to check if all the unsaved object pass the validation test or not, if yes, to do that you can do this instead for the unique field email
if #users.map { |user| user.email.downcase }.uniq.length == #users.size
#users.each do |user|
valid_users << user if user.valid?
end
else
# Error: Emails of the users must be unique
end
Hope this helps!
You can wrap them in a transaction which will roll back the entire batch if one fails.
begin
ActiveRecord::Base.transaction do
#users.map(&:save!)
end
rescue ActiveRecord::RecordInvalid
end
Why not just check the user.valid? that validates the DB records and then manually check the #users and only save if it's not a duplicate.
#users.each do |user|
#the following would check for identical objects in #users, but that may not be what you want
valid_users << user if user.valid? and #users.count(user)<2
#this would check only the required field on the array
valid_users << user if user.valid? and #users.map(&:email).count(user.email)<2
end
One way to do this, can be using all?:
if #users.all?{|u| u.valid? } # true, only when all users are validated
# save all users
else
# redirect/render with error message.
end
I have the snippet below:
can :read, Task do |task|
cus = task.case.case_users.ids
cus.each do |id|
cu = CaseUser.find(id)
cu.user_id === user.id
end
end
Each case has many tasks. This is authorization for the task, so I want to get the tasks's case. Each case has a case_user. I want to be sure that before giving permission to read the task, that there is a case_user record with a user_id equal to the user passed through by cancan.
How can I find the task's related case, the case's related case_users, and allow :read if the user's id is in the case_users array of records?
When testing it in the console, it returns false, however, cancan still gives authorization to read the record when it should not be.
Edit:
I have tried adding this readable method to the Task model below:
def readable?(user_id, task_id)
Task.find(task_id).case.case_users.ids.each do |id|
cu = CaseUser.find(id)
if cu.user_id === user_id
true
end
end
end
And I have this in the initialize method in the Ability class:
can :read, Task do |task|
task.readable?(user.id, task.id)
end
Try this:
can :read, Task, :case=> { :user_id => user.id }
If you're using CanCan > 1.4, then in your view:
<% if can? :read, #case => Task %>
Taken from the CanCan Wiki.
I would move the logic into your model and do something like this in your cancan ability.rb:
can :read, Task do |task|
task.readable_by_user? user
end
Then add a readable_by_user? function to your model that returns true or false like this:
def readable_by_user?(user)
case.case_users.include? user
end
I cannot figure this out for the life of me. I am using CanCan and InheritedResources. I want to delete a group, but revoke the users in the group (not delete them from the database). The revocation is done by setting revoked to true on a user. In my tests there are 2 users at the beginning, and one group.
class GroupsController < InheritedResources::Base
load_and_authorize_resource
def destroy
p User.all # shows the correct value, 2!
#group.users.each do |user|
user.revoked = true
p User.all # still shows 2 on the first loop iteration
user.save!
p User.all # shows 1 on the first iteration! The user was deleted?!
end
super # InheritedResources call to destroy the group
end
Why are my users being deleted? At the end of all this, I have no group, and no users! .save! is not raising an exception, I have tried if user.save as well, and it returns true. I have tried with and without super, so I don't think it is anything InheritedResources related. In my group model, I have:
has_many :users
There is no :dependent => ":destroy". What is going on here? I am surprised and confused that save! is silently deleting my records.
It's not clear to me, from the context above, why this is happening...
Try using user.update_column :revoked, true instead of user.revoked = true and user.save!. This will save it without callbacks, which could potentially (likely?) be interfering with something.
If I have this:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
Rails.logger.info 'XXXX'
end
CanCan works properly but if I remove the logger, it fails:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
end
Any ideas what's going on here with CanCan? or my code? :) thanks
From the fine manual:
If the conditions hash does not give you enough control over defining abilities, you can use a block along with any Ruby code you want.
can :update, Project do |project|
project.groups.include?(user.group)
end
If the block returns true then the user has that :update ability for that project, otherwise he will be denied access. The downside to using a block is that it cannot be used to generate conditions for database queries.
Your first block:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
Rails.logger.info 'XXXX'
end
Will always return a true value because Rails.logger.info 'XXXX' returns "XXXX\n" (info is just a wrapper for add and you have to read the source to see what add returns as it isn't very well documented). Without the Rails.logger.info call, the block returns just:
wall_member.try(:user_id) == current_user.id
and that must be false for you.
Have SubscriberList
When an order is placed I want to check if New User's email is all ready in our subscribers list.
If not, then add them. Problem is it adds them no matter what. Guess it's not performing check correctly.
Currently in my orders_controller I have
unless logged_in?
#order.subscribe_after_purchase(#order.user.email)
end
And in my Order.rb I have
def subscribe_after_purchase(email)
unless SubscriberList.exists?(email)
SubscriberList.create(:email => email)
end
end
Try using:
unless SubscriberList.exists?(:email => email)
SubscriberList.create(:email => email)
end
When you just pass the email address to the exists? method then ActiveRecord will interpret it as a primary key. Alternatively, you can use:
SubscriberList.find_or_create_by_email(email)
—which will have the same effect as your unless block; creating the record unless it already exists.
exists? API documentation