Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ end
```
I.e. you get true only on a role that you manually add.

### Cached Roles (to avoid N+1 issue)

```ruby
@user.add_role :admin, Forum
@user.add_role :member, Forum

users = User.with_role(:admin, Forum).preload(:roles)
users.each do |user|
user.has_cached_role?(:member, Forum) # no extra queries
end
```

This method should be used with caution. If you don't preload the roles, the `has_cached_role?` might return `false`. In the above example, it would return `false` for `@user.has_cached_role?(:member, Forum)`, because `User.with_role(:admin, Forum)` will load only the `:admin` roles.

## Resources

* [Wiki](https://github.com/RolifyCommunity/rolify/wiki)
Expand Down
22 changes: 22 additions & 0 deletions lib/rolify/adapters/active_record/role_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ def where_strict(relation, args)
relation.where(:name => args[:name], :resource_type => resource[:class], :resource_id => resource[:id])
end

def find_cached(relation, args)
resource_id = (args[:resource].nil? || args[:resource].is_a?(Class) || args[:resource] == :any) ? nil : args[:resource].id
resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name

return relation.find_all { |role| role.name == args[:name].to_s } if args[:resource] == :any

relation.find_all do |role|
(role.name == args[:name].to_s && role.resource_type == nil && role.resource_id == nil) ||
(role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == nil) ||
(role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == resource_id)
end
end

def find_cached_strict(relation, args)
resource_id = (args[:resource].nil? || args[:resource].is_a?(Class)) ? nil : args[:resource].id
resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name

relation.find_all do |role|
role.resource_id == resource_id && role.resource_type == resource_type && role.name == args[:name].to_s
end
end

def find_or_create_by(role_name, resource_type = nil, resource_id = nil)
role_class.where(:name => role_name, :resource_type => resource_type, :resource_id => resource_id).first_or_create
end
Expand Down
22 changes: 22 additions & 0 deletions lib/rolify/adapters/mongoid/role_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ def where_strict(relation, args)
relation.where(:name => args[:name], :resource_type => resource[:class], :resource_id => resource[:id])
end

def find_cached(relation, args)
resource_id = (args[:resource].nil? || args[:resource].is_a?(Class) || args[:resource] == :any) ? nil : args[:resource].id
resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name

return relation.find_all { |role| role.name == args[:name].to_s } if args[:resource] == :any

relation.find_all do |role|
(role.name == args[:name].to_s && role.resource_type == nil && role.resource_id == nil) ||
(role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == nil) ||
(role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == resource_id)
end
end

def find_cached_strict(relation, args)
resource_id = (args[:resource].nil? || args[:resource].is_a?(Class)) ? nil : args[:resource].id
resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name

relation.find_all do |role|
role.resource_id == resource_id && role.resource_type == resource_type && role.name == args[:name].to_s
end
end

def find_or_create_by(role_name, resource_type = nil, resource_id = nil)
self.role_class.find_or_create_by(:name => role_name,
:resource_type => resource_type,
Expand Down
9 changes: 9 additions & 0 deletions lib/rolify/role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ def has_strict_role?(role_name, resource)
self.class.adapter.where_strict(self.roles, name: role_name, resource: resource).any?
end

def has_cached_role?(role_name, resource = nil)
return has_strict_cached_role?(role_name, resource) if self.class.strict_rolify and resource and resource != :any
self.class.adapter.find_cached(self.roles, name: role_name, resource: resource).any?
end

def has_strict_cached_role?(role_name, resource = nil)
self.class.adapter.find_cached_strict(self.roles, name: role_name, resource: resource).any?
end

def has_all_roles?(*args)
args.each do |arg|
if arg.is_a? Hash
Expand Down
5 changes: 5 additions & 0 deletions spec/rolify/resource_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -548,23 +548,28 @@

it "should return only strict forum" do
@strict_user.has_role?(:forum, Forum.first).should be true
@strict_user.has_cached_role?(:forum, Forum.first).should be true
end

it "should return false on strict another forum" do
@strict_user.has_role?(:forum, Forum.last).should be false
@strict_user.has_cached_role?(:forum, Forum.last).should be false
end

it "should return true if user has role on Forum model" do
@strict_user.has_role?(:forum, Forum).should be true
@strict_user.has_cached_role?(:forum, Forum).should be true
end

it "should return true if user has role any forum name" do
@strict_user.has_role?(:forum, :any).should be true
@strict_user.has_cached_role?(:forum, :any).should be true
end

it "should return false when deleted role on Forum model" do
@strict_user.remove_role(:forum, Forum)
@strict_user.has_role?(:forum, Forum).should be false
@strict_user.has_cached_role?(:forum, Forum).should be false
end
end
end
Expand Down
65 changes: 65 additions & 0 deletions spec/rolify/shared_examples/shared_examples_for_has_role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,46 @@
context "with a global role", :scope => :global do
it { subject.has_role?("admin".send(param_method)).should be_truthy }

it { subject.has_cached_role?("admin".send(param_method)).should be_truthy }

context "on resource request" do
it { subject.has_role?("admin".send(param_method), Forum.first).should be_truthy }
it { subject.has_role?("admin".send(param_method), Forum).should be_truthy }
it { subject.has_role?("admin".send(param_method), :any).should be_truthy }

it { subject.has_cached_role?("admin".send(param_method), Forum.first).should be_truthy }
it { subject.has_cached_role?("admin".send(param_method), Forum).should be_truthy }
it { subject.has_cached_role?("admin".send(param_method), :any).should be_truthy }
end

context "with another global role" do
before(:all) { role_class.create(:name => "global") }

it { subject.has_role?("global".send(param_method)).should be_falsey }
it { subject.has_role?("global".send(param_method), :any).should be_falsey }

it { subject.has_cached_role?("global".send(param_method)).should be_falsey }
it { subject.has_cached_role?("global".send(param_method), :any).should be_falsey }
end

it "should not get an instance scoped role" do
subject.has_role?("moderator".send(param_method), Group.first).should be_falsey

subject.has_cached_role?("moderator".send(param_method), Group.first).should be_falsey
end

it "should not get a class scoped role" do
subject.has_role?("manager".send(param_method), Forum).should be_falsey

subject.has_cached_role?("manager".send(param_method), Forum).should be_falsey
end

context "using inexisting role" do
it { subject.has_role?("dummy".send(param_method)).should be_falsey }
it { subject.has_role?("dumber".send(param_method), Forum.first).should be_falsey }

it { subject.has_cached_role?("dummy".send(param_method)).should be_falsey }
it { subject.has_cached_role?("dumber".send(param_method), Forum.first).should be_falsey }
end
end

Expand All @@ -35,15 +51,23 @@
it { subject.has_role?("manager".send(param_method), Forum).should be_truthy }
it { subject.has_role?("manager".send(param_method), Forum.first).should be_truthy }
it { subject.has_role?("manager".send(param_method), :any).should be_truthy }

it { subject.has_cached_role?("manager".send(param_method), Forum).should be_truthy }
it { subject.has_cached_role?("manager".send(param_method), Forum.first).should be_truthy }
it { subject.has_cached_role?("manager".send(param_method), :any).should be_truthy }
end

it "should not get a scoped role when asking for a global" do
subject.has_role?("manager".send(param_method)).should be_falsey

subject.has_cached_role?("manager".send(param_method)).should be_falsey
end

it "should not get a global role" do
role_class.create(:name => "admin")
subject.has_role?("admin".send(param_method)).should be_falsey

subject.has_cached_role?("admin".send(param_method)).should be_falsey
end

context "with another class scoped role" do
Expand All @@ -52,26 +76,38 @@

it { subject.has_role?("member".send(param_method), Forum).should be_falsey }
it { subject.has_role?("member".send(param_method), :any).should be_falsey }

it { subject.has_cached_role?("member".send(param_method), Forum).should be_falsey }
it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey }
end

context "on another resource with the same name" do
before(:all) { role_class.create(:name => "manager", :resource_type => "Group") }

it { subject.has_role?("manager".send(param_method), Group).should be_falsey }
it { subject.has_role?("manager".send(param_method), :any).should be_truthy }

it { subject.has_cached_role?("manager".send(param_method), Group).should be_falsey }
it { subject.has_cached_role?("manager".send(param_method), :any).should be_truthy }
end

context "on another resource with another name" do
before(:all) { role_class.create(:name => "defenders", :resource_type => "Group") }

it { subject.has_role?("defenders".send(param_method), Group).should be_falsey }
it { subject.has_role?("defenders".send(param_method), :any).should be_falsey }

it { subject.has_cached_role?("defenders".send(param_method), Group).should be_falsey }
it { subject.has_cached_role?("defenders".send(param_method), :any).should be_falsey }
end
end

context "using inexisting role" do
it { subject.has_role?("dummy".send(param_method), Forum).should be_falsey }
it { subject.has_role?("dumber".send(param_method)).should be_falsey }

it { subject.has_cached_role?("dummy".send(param_method), Forum).should be_falsey }
it { subject.has_cached_role?("dumber".send(param_method)).should be_falsey }
end
end

Expand All @@ -84,19 +120,33 @@
m.add_role("moderator", Forum.first)
m.has_role?("moderator".send(param_method), :any).should be_truthy
}

it { subject.has_cached_role?("moderator".send(param_method), Forum.first).should be_truthy }
it { subject.has_cached_role?("moderator".send(param_method), :any).should be_truthy }
it {
m = subject.class.new
m.add_role("moderator", Forum.first)
m.has_cached_role?("moderator".send(param_method), :any).should be_truthy
}
end

it "should not get an instance scoped role when asking for a global" do
subject.has_role?("moderator".send(param_method)).should be_falsey

subject.has_cached_role?("moderator".send(param_method)).should be_falsey
end

it "should not get an instance scoped role when asking for a class scoped" do
subject.has_role?("moderator".send(param_method), Forum).should be_falsey

subject.has_cached_role?("moderator".send(param_method), Forum).should be_falsey
end

it "should not get a global role" do
role_class.create(:name => "admin")
subject.has_role?("admin".send(param_method)).should be_falsey

subject.has_cached_role?("admin".send(param_method)).should be_falsey
end

context "with another instance scoped role" do
Expand All @@ -105,34 +155,49 @@

it { subject.has_role?("member".send(param_method), Forum.first).should be_falsey }
it { subject.has_role?("member".send(param_method), :any).should be_falsey }

it { subject.has_cached_role?("member".send(param_method), Forum.first).should be_falsey }
it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey }
end

context "on another resource of the same type but with the same role name" do
before(:all) { role_class.create(:name => "moderator", :resource => Forum.last) }

it { subject.has_role?("moderator".send(param_method), Forum.last).should be_falsey }
it { subject.has_role?("moderator".send(param_method), :any).should be_truthy }

it { subject.has_cached_role?("moderator".send(param_method), Forum.last).should be_falsey }
it { subject.has_cached_role?("moderator".send(param_method), :any).should be_truthy }
end

context "on another resource of different type but with the same role name" do
before(:all) { role_class.create(:name => "moderator", :resource => Group.last) }

it { subject.has_role?("moderator".send(param_method), Group.last).should be_falsey }
it { subject.has_role?("moderator".send(param_method), :any).should be_truthy }

it { subject.has_cached_role?("moderator".send(param_method), Group.last).should be_falsey }
it { subject.has_cached_role?("moderator".send(param_method), :any).should be_truthy }
end

context "on another resource of the same type and with another role name" do
before(:all) { role_class.create(:name => "member", :resource => Forum.last) }

it { subject.has_role?("member".send(param_method), Forum.last).should be_falsey }
it { subject.has_role?("member".send(param_method), :any).should be_falsey }

it { subject.has_cached_role?("member".send(param_method), Forum.last).should be_falsey }
it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey }
end

context "on another resource of different type and with another role name" do
before(:all) { role_class.create(:name => "member", :resource => Group.first) }

it { subject.has_role?("member".send(param_method), Group.first).should be_falsey }
it { subject.has_role?("member".send(param_method), :any).should be_falsey }

it { subject.has_cached_role?("member".send(param_method), Group.first).should be_falsey }
it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey }
end
end
end
Expand Down