role_fu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +144 -0
- data/Rakefile +25 -0
- data/gemfiles/rails_7.0.gemfile +16 -0
- data/gemfiles/rails_7.0.gemfile.lock +419 -0
- data/gemfiles/rails_7.1.gemfile +16 -0
- data/gemfiles/rails_7.1.gemfile.lock +437 -0
- data/gemfiles/rails_7.2.gemfile +16 -0
- data/gemfiles/rails_7.2.gemfile.lock +431 -0
- data/gemfiles/rails_8.0.gemfile +16 -0
- data/gemfiles/rails_8.0.gemfile.lock +428 -0
- data/gemfiles/rails_8.1.gemfile +16 -0
- data/gemfiles/rails_8.1.gemfile.lock +430 -0
- data/lefthook.yml +11 -0
- data/lib/generators/role_fu/install_generator.rb +17 -0
- data/lib/generators/role_fu/role_fu_generator.rb +43 -0
- data/lib/generators/role_fu/templates/migration.rb.erb +22 -0
- data/lib/generators/role_fu/templates/role.rb.erb +5 -0
- data/lib/generators/role_fu/templates/role_assignment.rb.erb +5 -0
- data/lib/generators/role_fu/templates/role_fu.rb +12 -0
- data/lib/role_fu/configuration.rb +35 -0
- data/lib/role_fu/resourceable.rb +156 -0
- data/lib/role_fu/role.rb +21 -0
- data/lib/role_fu/role_assignment.rb +31 -0
- data/lib/role_fu/roleable.rb +290 -0
- data/lib/role_fu/version.rb +5 -0
- data/lib/role_fu.rb +16 -0
- data/sig/role_fu.rbs +4 -0
- metadata +173 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RoleFu
|
|
4
|
+
# Resourceable concern - provides resource management for models with roles
|
|
5
|
+
module Resourceable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
has_many :roles,
|
|
10
|
+
as: :resource,
|
|
11
|
+
dependent: :destroy,
|
|
12
|
+
class_name: RoleFu.configuration.role_class_name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ClassMethods
|
|
16
|
+
# Find roles defined on any instance of this resource class
|
|
17
|
+
# @param role_name [String, Symbol, nil] Filter by role name
|
|
18
|
+
# @param user [User, nil] Filter by user
|
|
19
|
+
# @return [ActiveRecord::Relation] Roles
|
|
20
|
+
def find_roles(role_name = nil, user = nil)
|
|
21
|
+
RoleFu.role_class.table_name
|
|
22
|
+
query = RoleFu.role_class.where(resource_type: name)
|
|
23
|
+
query = query.where(name: role_name.to_s) if role_name
|
|
24
|
+
query = query.joins(:users).where(RoleFu.user_class.table_name => {id: user.id}) if user
|
|
25
|
+
query
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Find resources that have a specific role applied (to a user)
|
|
29
|
+
# @param role_name [String, Symbol] The role name
|
|
30
|
+
# @param user [User, nil] Filter by specific user having the role
|
|
31
|
+
# @return [ActiveRecord::Relation] Resources
|
|
32
|
+
def with_role(role_name, user = nil)
|
|
33
|
+
role_table = RoleFu.role_class.table_name
|
|
34
|
+
|
|
35
|
+
query = joins(:roles).where(role_table => {name: role_name.to_s})
|
|
36
|
+
|
|
37
|
+
if user
|
|
38
|
+
query = query.joins(roles: :users).where(RoleFu.user_class.table_name => {id: user.id})
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
query.distinct
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Find resources that do NOT have a specific role applied
|
|
45
|
+
# @param role_name [String, Symbol] The role name
|
|
46
|
+
# @param user [User, nil] Filter by user
|
|
47
|
+
# @return [ActiveRecord::Relation] Resources
|
|
48
|
+
def without_role(role_name, user = nil)
|
|
49
|
+
where.not(id: with_role(role_name, user).select(:id))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Get roles applied to this resource instance (plus global class-level roles if any - though RoleFu focuses on instance roles)
|
|
54
|
+
# @return [ActiveRecord::Relation] Roles
|
|
55
|
+
def applied_roles
|
|
56
|
+
roles
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get users with a specific role on this resource
|
|
60
|
+
# @param role_name [String, Symbol] The role name
|
|
61
|
+
# @return [ActiveRecord::Relation] Relation of users
|
|
62
|
+
def users_with_role(role_name)
|
|
63
|
+
role_table = RoleFu.role_class.table_name
|
|
64
|
+
user_class.joins(:roles)
|
|
65
|
+
.where(role_table => {name: role_name.to_s, resource_type: self.class.name, resource_id: id})
|
|
66
|
+
.distinct
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get users with any role on this resource
|
|
70
|
+
# @param role_names [Array<String, Symbol>] Array of role names
|
|
71
|
+
# @return [ActiveRecord::Relation] Relation of users
|
|
72
|
+
def users_with_any_role(*role_names)
|
|
73
|
+
role_table = RoleFu.role_class.table_name
|
|
74
|
+
user_class.joins(:roles)
|
|
75
|
+
.where(role_table => {name: role_names.flatten.map(&:to_s), resource_type: self.class.name, resource_id: id})
|
|
76
|
+
.distinct
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get users with all specified roles on this resource
|
|
80
|
+
# @param role_names [Array<String, Symbol>] Array of role names
|
|
81
|
+
# @return [Array<User>] Array of users
|
|
82
|
+
def users_with_all_roles(*role_names)
|
|
83
|
+
role_names = role_names.flatten.map(&:to_s)
|
|
84
|
+
role_table = RoleFu.role_class.table_name
|
|
85
|
+
|
|
86
|
+
user_class.joins(:roles)
|
|
87
|
+
.where(role_table => {name: role_names, resource_type: self.class.name, resource_id: id})
|
|
88
|
+
.group("#{user_class.table_name}.#{user_class.primary_key}")
|
|
89
|
+
.having("COUNT(DISTINCT #{role_table}.name) = ?", role_names.size)
|
|
90
|
+
.distinct
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get all users with any role on this resource
|
|
94
|
+
# @return [ActiveRecord::Relation] Relation of users
|
|
95
|
+
def users_with_roles
|
|
96
|
+
role_table = RoleFu.role_class.table_name
|
|
97
|
+
user_class.joins(:roles)
|
|
98
|
+
.where(role_table => {resource_type: self.class.name, resource_id: id})
|
|
99
|
+
.distinct
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get all role names defined for this resource
|
|
103
|
+
# @return [Array<String>] Array of role names
|
|
104
|
+
def available_roles
|
|
105
|
+
roles.pluck(:name).uniq
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if resource has any users with a specific role
|
|
109
|
+
# @param role_name [String, Symbol] The role name
|
|
110
|
+
# @return [Boolean] true if any user has this role
|
|
111
|
+
def has_role?(role_name)
|
|
112
|
+
roles.exists?(name: role_name.to_s)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Count users with a specific role
|
|
116
|
+
# @param role_name [String, Symbol] The role name
|
|
117
|
+
# @return [Integer] Number of users
|
|
118
|
+
def count_users_with_role(role_name)
|
|
119
|
+
users_with_role(role_name).count
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check if a specific user has a role on this resource
|
|
123
|
+
# @param user [User] The user
|
|
124
|
+
# @param role_name [String, Symbol] The role name
|
|
125
|
+
# @return [Boolean] true if user has the role
|
|
126
|
+
def user_has_role?(user, role_name)
|
|
127
|
+
return false if user.nil?
|
|
128
|
+
|
|
129
|
+
user.has_role?(role_name, self)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Add a role to a user on this resource
|
|
133
|
+
# @param user [User] The user
|
|
134
|
+
# @param role_name [String, Symbol] The role name
|
|
135
|
+
# @return [Role] The role
|
|
136
|
+
def add_role_to_user(user, role_name)
|
|
137
|
+
user.add_role(role_name, self)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Remove a role from a user on this resource
|
|
141
|
+
# @param user [User] The user
|
|
142
|
+
# @param role_name [String, Symbol] The role name
|
|
143
|
+
# @return [Array<Role>] The removed roles
|
|
144
|
+
def remove_role_from_user(user, role_name)
|
|
145
|
+
user.remove_role(role_name, self)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
# Get the User class
|
|
151
|
+
# @return [Class] User class
|
|
152
|
+
def user_class
|
|
153
|
+
RoleFu.user_class
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
data/lib/role_fu/role.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RoleFu
|
|
4
|
+
# Module for the Role model
|
|
5
|
+
module Role
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
has_many :role_assignments,
|
|
10
|
+
class_name: RoleFu.configuration.role_assignment_class_name,
|
|
11
|
+
dependent: :destroy
|
|
12
|
+
has_many :users,
|
|
13
|
+
through: :role_assignments,
|
|
14
|
+
class_name: RoleFu.configuration.user_class_name
|
|
15
|
+
|
|
16
|
+
belongs_to :resource, polymorphic: true, optional: true
|
|
17
|
+
|
|
18
|
+
validates :name, presence: true, uniqueness: {scope: [:resource_type, :resource_id]}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RoleFu
|
|
4
|
+
# Module for the RoleAssignment model
|
|
5
|
+
module RoleAssignment
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
belongs_to :user, class_name: RoleFu.configuration.user_class_name
|
|
10
|
+
belongs_to :role, class_name: RoleFu.configuration.role_class_name
|
|
11
|
+
|
|
12
|
+
scope :global_roles, -> { joins(:role).where(RoleFu.role_class.table_name => {resource_type: nil, resource_id: nil}) }
|
|
13
|
+
scope :resource_specific, -> { joins(:role).where.not(RoleFu.role_class.table_name => {resource_type: nil}) }
|
|
14
|
+
|
|
15
|
+
after_destroy :cleanup_orphaned_role
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def cleanup_orphaned_role
|
|
21
|
+
# If this record is being destroyed as part of role destruction,
|
|
22
|
+
# do not try to destroy the same role again.
|
|
23
|
+
return if destroyed_by_association&.active_record == RoleFu.role_class
|
|
24
|
+
|
|
25
|
+
# Delete role if it has no more users assigned
|
|
26
|
+
return if role.destroyed?
|
|
27
|
+
|
|
28
|
+
role.destroy if role.role_assignments.none?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RoleFu
|
|
4
|
+
# Roleable concern - provides role management for User model
|
|
5
|
+
module Roleable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
has_many :role_assignments,
|
|
10
|
+
class_name: RoleFu.configuration.role_assignment_class_name,
|
|
11
|
+
dependent: :destroy
|
|
12
|
+
has_many :roles,
|
|
13
|
+
through: :role_assignments,
|
|
14
|
+
class_name: RoleFu.configuration.role_class_name
|
|
15
|
+
|
|
16
|
+
class_attribute :role_fu_callbacks, default: {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ClassMethods
|
|
20
|
+
def role_fu_options(options = {})
|
|
21
|
+
self.role_fu_callbacks = options.slice(:before_add, :after_add, :before_remove, :after_remove)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Find users with a specific role
|
|
25
|
+
# @param role_name [String, Symbol] The name of the role
|
|
26
|
+
# @param resource [ActiveRecord::Base, Class, nil, :any] The resource
|
|
27
|
+
# @return [ActiveRecord::Relation] Users with the role
|
|
28
|
+
def with_role(role_name, resource = nil)
|
|
29
|
+
role_table = RoleFu.role_class.table_name
|
|
30
|
+
RoleFu.role_assignment_class.table_name
|
|
31
|
+
|
|
32
|
+
query = joins(:roles).where(role_table => {name: role_name.to_s})
|
|
33
|
+
|
|
34
|
+
if resource.nil?
|
|
35
|
+
query.where(role_table => {resource_type: nil, resource_id: nil})
|
|
36
|
+
elsif resource == :any
|
|
37
|
+
query
|
|
38
|
+
elsif resource.is_a?(Class)
|
|
39
|
+
query.where(role_table => {resource_type: resource.to_s, resource_id: nil})
|
|
40
|
+
else
|
|
41
|
+
query.where(role_table => {resource_type: resource.class.name, resource_id: resource.id})
|
|
42
|
+
end.distinct
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Find users without a specific role
|
|
46
|
+
# @param role_name [String, Symbol] The name of the role
|
|
47
|
+
# @param resource [ActiveRecord::Base, Class, nil, :any] The resource
|
|
48
|
+
# @return [ActiveRecord::Relation] Users without the role
|
|
49
|
+
def without_role(role_name, resource = nil)
|
|
50
|
+
where.not(id: with_role(role_name, resource).select(:id))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Find users with any of the specified roles
|
|
54
|
+
# @param args [Array<String, Symbol, Hash>] Roles to check
|
|
55
|
+
# @return [ActiveRecord::Relation] Users with any of the roles
|
|
56
|
+
def with_any_role(*args)
|
|
57
|
+
ids = []
|
|
58
|
+
args.each do |arg|
|
|
59
|
+
ids += if arg.is_a?(Hash)
|
|
60
|
+
with_role(arg[:name], arg[:resource]).pluck(:id)
|
|
61
|
+
else
|
|
62
|
+
with_role(arg).pluck(:id)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
where(id: ids.uniq)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Find users with all of the specified roles
|
|
69
|
+
# @param args [Array<String, Symbol, Hash>] Roles to check
|
|
70
|
+
# @return [ActiveRecord::Relation] Users with all of the roles
|
|
71
|
+
def with_all_roles(*args)
|
|
72
|
+
ids = nil
|
|
73
|
+
args.each do |arg|
|
|
74
|
+
current_ids = if arg.is_a?(Hash)
|
|
75
|
+
with_role(arg[:name], arg[:resource]).pluck(:id)
|
|
76
|
+
else
|
|
77
|
+
with_role(arg).pluck(:id)
|
|
78
|
+
end
|
|
79
|
+
ids = ids.nil? ? current_ids : ids & current_ids
|
|
80
|
+
return none if ids.empty?
|
|
81
|
+
end
|
|
82
|
+
where(id: ids)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Add a role to the user
|
|
87
|
+
# @param role_name [String, Symbol] The name of the role
|
|
88
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource (organization, etc.) or nil for global role
|
|
89
|
+
# @return [Role] The role that was added
|
|
90
|
+
def add_role(role_name, resource = nil)
|
|
91
|
+
role = find_or_create_role(role_name, resource)
|
|
92
|
+
|
|
93
|
+
return role if roles.include?(role)
|
|
94
|
+
|
|
95
|
+
run_role_fu_callback(:before_add, role)
|
|
96
|
+
roles << role
|
|
97
|
+
run_role_fu_callback(:after_add, role)
|
|
98
|
+
|
|
99
|
+
role
|
|
100
|
+
end
|
|
101
|
+
alias_method :grant, :add_role
|
|
102
|
+
|
|
103
|
+
# Remove a role from the user
|
|
104
|
+
# @param role_name [String, Symbol] The name of the role
|
|
105
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource or nil for global role
|
|
106
|
+
# @return [Array<Role>] The roles that were removed
|
|
107
|
+
def remove_role(role_name, resource = nil)
|
|
108
|
+
roles_to_remove_relation = find_roles(role_name, resource)
|
|
109
|
+
return [] if roles_to_remove_relation.empty?
|
|
110
|
+
|
|
111
|
+
# Materialize before removing associations, because removing may trigger cleanup that deletes the role.
|
|
112
|
+
removed_roles = roles_to_remove_relation.to_a
|
|
113
|
+
|
|
114
|
+
removed_roles.each do |role|
|
|
115
|
+
run_role_fu_callback(:before_remove, role)
|
|
116
|
+
role_assignments.where(role_id: role.id).destroy_all
|
|
117
|
+
run_role_fu_callback(:after_remove, role)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
removed_roles
|
|
121
|
+
end
|
|
122
|
+
alias_method :revoke, :remove_role
|
|
123
|
+
|
|
124
|
+
# Check if user has a specific role
|
|
125
|
+
# @param role_name [String, Symbol] The name of the role
|
|
126
|
+
# @param resource [ActiveRecord::Base, Class, nil, :any] The resource, nil for global, or :any for any resource
|
|
127
|
+
# @return [Boolean] true if user has the role
|
|
128
|
+
def has_role?(role_name, resource = nil)
|
|
129
|
+
return false if role_name.nil?
|
|
130
|
+
|
|
131
|
+
if resource == :any
|
|
132
|
+
roles.exists?(name: role_name.to_s)
|
|
133
|
+
else
|
|
134
|
+
find_roles(role_name, resource).any?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Check if user has a specific role strictly (resource match must be exact, no globals overriding)
|
|
139
|
+
# Note: In RoleFu, has_role? is already strict about resource matching unless :any is passed,
|
|
140
|
+
# but this method explicitly bypasses any future global-fallback logic if we were to add it.
|
|
141
|
+
# Included for API compatibility.
|
|
142
|
+
# @param role_name [String, Symbol] The name of the role
|
|
143
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
144
|
+
# @return [Boolean] true if user has the role strictly
|
|
145
|
+
def has_strict_role?(role_name, resource = nil)
|
|
146
|
+
has_role?(role_name, resource)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Check if user only has this one role
|
|
150
|
+
# @param role_name [String, Symbol] The name of the role
|
|
151
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
152
|
+
# @return [Boolean] true if user has this role and no others
|
|
153
|
+
def only_has_role?(role_name, resource = nil)
|
|
154
|
+
has_role?(role_name, resource) && roles.count == 1
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Check for role using preloaded association to avoid N+1
|
|
158
|
+
def has_cached_role?(role_name, resource = nil)
|
|
159
|
+
role_name = role_name.to_s
|
|
160
|
+
roles.to_a.any? do |role|
|
|
161
|
+
next false unless role.name == role_name
|
|
162
|
+
|
|
163
|
+
if resource == :any
|
|
164
|
+
true
|
|
165
|
+
elsif resource.is_a?(Class)
|
|
166
|
+
role.resource_type == resource.to_s && role.resource_id.nil?
|
|
167
|
+
elsif resource
|
|
168
|
+
role.resource_type == resource.class.name && role.resource_id == resource.id
|
|
169
|
+
else
|
|
170
|
+
role.resource_type.nil? && role.resource_id.nil?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Get all role names for this user
|
|
176
|
+
# @param resource [ActiveRecord::Base, Class, nil] Filter by resource
|
|
177
|
+
# @return [Array<String>] Array of role names
|
|
178
|
+
def roles_name(resource = nil)
|
|
179
|
+
if resource
|
|
180
|
+
roles.where(resource: resource).pluck(:name)
|
|
181
|
+
else
|
|
182
|
+
roles.pluck(:name)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Check if user has only global roles (no resource-specific roles)
|
|
187
|
+
# @return [Boolean] true if user has only global roles
|
|
188
|
+
def has_only_global_roles?
|
|
189
|
+
roles.where.not(resource_type: nil).empty?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Check if user has any role (global or resource-specific)
|
|
193
|
+
# @param resource [ActiveRecord::Base, Class, nil] Filter by resource
|
|
194
|
+
# @return [Boolean] true if user has any role
|
|
195
|
+
def has_any_role?(resource = nil)
|
|
196
|
+
if resource
|
|
197
|
+
roles.exists?(resource: resource)
|
|
198
|
+
else
|
|
199
|
+
roles.exists?
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Check if user has all specified roles
|
|
204
|
+
# @param role_names [Array<String, Symbol>] Array of role names
|
|
205
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
206
|
+
# @return [Boolean] true if user has all roles
|
|
207
|
+
def has_all_roles?(*role_names, resource: nil)
|
|
208
|
+
role_names.flatten.all? { |role_name| has_role?(role_name, resource) }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Check if user has any of the specified roles
|
|
212
|
+
# @param role_names [Array<String, Symbol>] Array of role names
|
|
213
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
214
|
+
# @return [Boolean] true if user has any of the roles
|
|
215
|
+
def has_any_role_of?(*role_names, resource: nil)
|
|
216
|
+
role_names.flatten.any? { |role_name| has_role?(role_name, resource) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Get all resources of a specific type where user has a role
|
|
220
|
+
# @param resource_class [Class] The resource class (e.g., Organization)
|
|
221
|
+
# @return [ActiveRecord::Relation] Relation of resources
|
|
222
|
+
def resources(resource_class)
|
|
223
|
+
resource_class.joins(:roles)
|
|
224
|
+
.merge(roles.where(resource_type: resource_class.name))
|
|
225
|
+
.distinct
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
# Find or create a role
|
|
231
|
+
# @param role_name [String, Symbol] The role name
|
|
232
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
233
|
+
# @return [Role] The found or created role
|
|
234
|
+
def find_or_create_role(role_name, resource)
|
|
235
|
+
resource_type = resource_type_for(resource)
|
|
236
|
+
resource_id = resource_id_for(resource)
|
|
237
|
+
|
|
238
|
+
RoleFu.role_class.find_or_create_by(
|
|
239
|
+
name: role_name.to_s,
|
|
240
|
+
resource_type: resource_type,
|
|
241
|
+
resource_id: resource_id
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Find roles matching criteria
|
|
246
|
+
# @param role_name [String, Symbol] The role name
|
|
247
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
248
|
+
# @return [ActiveRecord::Relation] Relation of matching roles
|
|
249
|
+
def find_roles(role_name, resource)
|
|
250
|
+
query = roles.where(name: role_name.to_s)
|
|
251
|
+
|
|
252
|
+
if resource.is_a?(Class)
|
|
253
|
+
query.where(resource_type: resource.to_s, resource_id: nil)
|
|
254
|
+
elsif resource
|
|
255
|
+
query.where(resource_type: resource.class.name, resource_id: resource.id)
|
|
256
|
+
else
|
|
257
|
+
query.where(resource_type: nil, resource_id: nil)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Get resource type for a resource
|
|
262
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
263
|
+
# @return [String, nil] The resource type
|
|
264
|
+
def resource_type_for(resource)
|
|
265
|
+
return nil if resource.nil?
|
|
266
|
+
|
|
267
|
+
resource.is_a?(Class) ? resource.to_s : resource.class.name
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Get resource id for a resource
|
|
271
|
+
# @param resource [ActiveRecord::Base, Class, nil] The resource
|
|
272
|
+
# @return [Integer, nil] The resource id
|
|
273
|
+
def resource_id_for(resource)
|
|
274
|
+
return nil if resource.nil? || resource.is_a?(Class)
|
|
275
|
+
|
|
276
|
+
resource.id
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def run_role_fu_callback(callback_name, role)
|
|
280
|
+
method_name = role_fu_callbacks[callback_name]
|
|
281
|
+
return unless method_name
|
|
282
|
+
|
|
283
|
+
if method_name.is_a?(Proc)
|
|
284
|
+
instance_exec(role, &method_name)
|
|
285
|
+
elsif respond_to?(method_name, true)
|
|
286
|
+
send(method_name, role)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
data/lib/role_fu.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
require "active_support/core_ext/string/inflections"
|
|
5
|
+
|
|
6
|
+
require_relative "role_fu/version"
|
|
7
|
+
require_relative "role_fu/configuration"
|
|
8
|
+
require_relative "role_fu/role"
|
|
9
|
+
require_relative "role_fu/role_assignment"
|
|
10
|
+
require_relative "role_fu/roleable"
|
|
11
|
+
require_relative "role_fu/resourceable"
|
|
12
|
+
|
|
13
|
+
module RoleFu
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
# Your code goes here...
|
|
16
|
+
end
|
data/sig/role_fu.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: role_fu
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alexey Poimtsev
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: appraisal
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: lefthook
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '13.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rspec
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: simplecov
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.21'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.21'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: sqlite3
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '2.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '2.0'
|
|
110
|
+
description: RoleFu provides explicit role management with Role and RoleAssignment
|
|
111
|
+
models, avoiding hidden HABTM tables. Supports scopes, resource-specific roles,
|
|
112
|
+
and cleaner architecture.
|
|
113
|
+
email:
|
|
114
|
+
- alexey.poimtsev@gmail.com
|
|
115
|
+
executables: []
|
|
116
|
+
extensions: []
|
|
117
|
+
extra_rdoc_files: []
|
|
118
|
+
files:
|
|
119
|
+
- Appraisals
|
|
120
|
+
- CHANGELOG.md
|
|
121
|
+
- LICENSE.txt
|
|
122
|
+
- README.md
|
|
123
|
+
- Rakefile
|
|
124
|
+
- gemfiles/rails_7.0.gemfile
|
|
125
|
+
- gemfiles/rails_7.0.gemfile.lock
|
|
126
|
+
- gemfiles/rails_7.1.gemfile
|
|
127
|
+
- gemfiles/rails_7.1.gemfile.lock
|
|
128
|
+
- gemfiles/rails_7.2.gemfile
|
|
129
|
+
- gemfiles/rails_7.2.gemfile.lock
|
|
130
|
+
- gemfiles/rails_8.0.gemfile
|
|
131
|
+
- gemfiles/rails_8.0.gemfile.lock
|
|
132
|
+
- gemfiles/rails_8.1.gemfile
|
|
133
|
+
- gemfiles/rails_8.1.gemfile.lock
|
|
134
|
+
- lefthook.yml
|
|
135
|
+
- lib/generators/role_fu/install_generator.rb
|
|
136
|
+
- lib/generators/role_fu/role_fu_generator.rb
|
|
137
|
+
- lib/generators/role_fu/templates/migration.rb.erb
|
|
138
|
+
- lib/generators/role_fu/templates/role.rb.erb
|
|
139
|
+
- lib/generators/role_fu/templates/role_assignment.rb.erb
|
|
140
|
+
- lib/generators/role_fu/templates/role_fu.rb
|
|
141
|
+
- lib/role_fu.rb
|
|
142
|
+
- lib/role_fu/configuration.rb
|
|
143
|
+
- lib/role_fu/resourceable.rb
|
|
144
|
+
- lib/role_fu/role.rb
|
|
145
|
+
- lib/role_fu/role_assignment.rb
|
|
146
|
+
- lib/role_fu/roleable.rb
|
|
147
|
+
- lib/role_fu/version.rb
|
|
148
|
+
- sig/role_fu.rbs
|
|
149
|
+
homepage: https://github.com/alec-c4/role_fu
|
|
150
|
+
licenses:
|
|
151
|
+
- MIT
|
|
152
|
+
metadata:
|
|
153
|
+
homepage_uri: https://github.com/alec-c4/role_fu
|
|
154
|
+
source_code_uri: https://github.com/alec-c4/role_fu
|
|
155
|
+
changelog_uri: https://github.com/alec-c4/role_fu/blob/main/CHANGELOG.md
|
|
156
|
+
rdoc_options: []
|
|
157
|
+
require_paths:
|
|
158
|
+
- lib
|
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - ">="
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: 3.2.0
|
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
|
+
requirements:
|
|
166
|
+
- - ">="
|
|
167
|
+
- !ruby/object:Gem::Version
|
|
168
|
+
version: '0'
|
|
169
|
+
requirements: []
|
|
170
|
+
rubygems_version: 4.0.5
|
|
171
|
+
specification_version: 4
|
|
172
|
+
summary: A modern role management gem for Rails, replacing rolify.
|
|
173
|
+
test_files: []
|