do_role 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e0656ea0457c67f9aff8a0e70bde325a857891a4e6e12df2ac9b29df757a330
4
+ data.tar.gz: b178335b7e1938fcb1aae1d49c0592995db7cc62236ec0b26bc369d08e4e4aff
5
+ SHA512:
6
+ metadata.gz: 1494eb63a948ff541d2473bb5c119b0783e14b2e10b0f36758b72e11058e647e2358c63750ce7866c2335a7bf7cf97f81f0b9e4784e7a54d1dd4ebcc079af4bf
7
+ data.tar.gz: f3b52ea6aea1c23688e5ac48ed0e4fae822e0c0ac584bd3a857ef8ac523604c26322ffe7fea0617c2bd6a8188b50b73c41a8d7541ff96bd0a7f330b09a13ba05
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # DoRole
2
+
3
+ 一个简单而灵活的Ruby on Rails角色权限管理解决方案。DoRole提供了直观的方式来管理Rails应用中的角色和权限。
4
+
5
+ ## 特性
6
+
7
+ - 简单直观的API设计
8
+ - 灵活的权限配置系统
9
+ - 支持权限继承
10
+ - 支持条件权限
11
+ - 内置权限审计功能
12
+ - 完全兼容Rails 7.0+
13
+
14
+ ## 安装
15
+
16
+ 在你的Gemfile中添加:
17
+
18
+ ```ruby
19
+ gem 'do_role'
20
+ ```
21
+
22
+ 然后执行:
23
+
24
+ ```bash
25
+ $ bundle install
26
+ ```
27
+
28
+ ## 基本配置
29
+
30
+ ### 生成迁移文件
31
+
32
+ ```bash
33
+ $ rails generate do_role:install
34
+ $ rails db:migrate
35
+ ```
36
+
37
+ ### 配置权限
38
+
39
+ 在`config/initializers/do_role.rb`中定义权限:
40
+
41
+ ```ruby
42
+ DoRole.permission_set do
43
+ # 使用namespace组织权限
44
+ namespace :posts do
45
+ permission :index
46
+ permission :create
47
+ permission :update
48
+ permission :destroy
49
+ end
50
+
51
+ namespace :users do
52
+ permission :manage
53
+ permission :view
54
+ end
55
+
56
+ # 可以添加自定义选项
57
+ namespace :admin do
58
+ permission :dashboard, description: "访问管理后台"
59
+ end
60
+
61
+ # 支持权限继承
62
+ namespace :articles do
63
+ permission :create
64
+ permission :update
65
+ permission :destroy
66
+ permission :manage, requires: [:create, :update, :destroy]
67
+ end
68
+
69
+ # 条件权限
70
+ namespace :comments do
71
+ permission :moderate, user_id: ->(user_id) { user_id == @user.id }
72
+ end
73
+ end
74
+ ```
75
+
76
+ ## 使用方法
77
+
78
+ ### 在模型中包含DoRole
79
+
80
+ ```ruby
81
+ class User < ApplicationRecord
82
+ include DoRole::Model
83
+ # 自动提供以下方法:
84
+ # - roles
85
+ # - permissions
86
+ # - has_permission?
87
+ end
88
+ ```
89
+
90
+ ### 创建和管理角色
91
+
92
+ ```ruby
93
+ # 创建角色
94
+ role = user.roles.create(name: "编辑", permissions: ["posts.create", "posts.update"])
95
+
96
+ # 添加权限
97
+ role.add_permission("posts.destroy")
98
+
99
+ # 移除权限
100
+ role.remove_permission("posts.destroy")
101
+
102
+ # 检查权限
103
+ role.has_permission?("posts.create") # => true
104
+ ```
105
+
106
+ ### 用户权限验证
107
+
108
+ ```ruby
109
+ # 检查用户是否拥有特定权限
110
+ user.has_permission?("posts.create")
111
+
112
+ # 带条件的权限检查
113
+ user.has_permission?("comments.moderate", comment)
114
+
115
+ # 获取用户所有权限
116
+ user.permissions # => ["posts.create", "posts.update", ...]
117
+ ```
118
+
119
+ ### 权限审计
120
+
121
+ ```ruby
122
+ # 启用权限审计
123
+ DoRole.configure do |config|
124
+ config.enable_permission_audit = true
125
+ end
126
+
127
+ # 查看权限变更记录
128
+ user.permission_audits
129
+ ```
130
+
131
+ ## 贡献
132
+
133
+ 1. Fork 项目
134
+ 2. 创建特性分支 (`git checkout -b my-new-feature`)
135
+ 3. 提交你的改动 (`git commit -am 'Add some feature'`)
136
+ 4. 推送到分支 (`git push origin my-new-feature`)
137
+ 5. 创建一个 Pull Request
138
+
139
+ ## 许可证
140
+
141
+ 本项目基于 [MIT License](https://opensource.org/licenses/MIT) 开源。
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ t.warning = false
11
+ end
12
+
13
+ Rake::TestTask.new(:integration) do |t|
14
+ t.libs << "test"
15
+ t.libs << "lib"
16
+ t.test_files = FileList["test/integration/**/*_test.rb"]
17
+ t.warning = false
18
+ end
19
+
20
+ task default: :test
21
+
22
+ desc "运行所有测试(单元测试和集成测试)"
23
+ task :all_tests do
24
+ Rake::Task["test"].invoke
25
+ Rake::Task["integration"].invoke
26
+ end
27
+
28
+ desc "构建和安装gem包"
29
+ task :build_and_install do
30
+ system "gem build do_role.gemspec"
31
+ system "gem install do_role-*.gem"
32
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ class Ability
5
+ attr_reader :user
6
+
7
+ def initialize(user)
8
+ @user = user
9
+ end
10
+
11
+ def can?(action, subject, *extra_args)
12
+ conditions = extra_args.extract_options!
13
+ permission_name = build_permission_name(action, subject)
14
+
15
+ return false unless has_base_permission?(permission_name)
16
+
17
+ permission = DoRole.permission_set.get(permission_name)
18
+ if permission && permission[:requires].present?
19
+ return false unless permission[:requires].all? { |req| can?(req, subject, conditions) }
20
+ end
21
+
22
+ # # 如果是对类进行检查且没有条件权限,则只检查基本权限
23
+ # return false if subject.is_a?(Class) && permission[:conditions].present?
24
+ # # 如果是对类进行检查且有条件权限,则返回false
25
+ return true if subject.is_a?(Class) #&& permission[:conditions].empty?
26
+
27
+ conditions_to_check = conditions
28
+ if permission && !permission[:conditions].empty?
29
+ conditions_to_check = permission[:conditions].merge(conditions)
30
+ end
31
+
32
+ check_conditions(subject, conditions_to_check)
33
+ end
34
+
35
+ private
36
+
37
+ def has_base_permission?(permission_name)
38
+ namespace, action = permission_name.to_s.split('.')
39
+ return false unless namespace && action
40
+ user.roles.any? { |role| role.has_permission?(permission_name) } ||
41
+ user.permissions[namespace]&.include?(action)
42
+ end
43
+
44
+ def check_conditions(subject, conditions)
45
+ return true if conditions.empty?
46
+
47
+ conditions.all? do |key, value|
48
+ subject_value = if subject.is_a?(Hash)
49
+ subject[key]
50
+ else
51
+ subject.respond_to?(key) ? subject.public_send(key) : nil
52
+ end
53
+
54
+ if value.is_a?(Proc)
55
+ instance_exec(subject_value, &value)
56
+ else
57
+ subject_value == value
58
+ end
59
+ end
60
+ end
61
+
62
+ def build_permission_name(action, subject)
63
+ subject_name = if subject.is_a?(Class)
64
+ if defined?(ActiveRecord) && subject.respond_to?(:base_class)
65
+ subject.base_class.name.underscore
66
+ else
67
+ subject.superclass.name.underscore
68
+ end
69
+ elsif subject.is_a?(Hash)
70
+ subject[:resource_type]&.to_s&.underscore || raise(ArgumentError, "resource_type is required when subject is a Hash")
71
+ else
72
+ get_base_class_name(subject.class)
73
+ end
74
+ [subject_name.pluralize, action].join('.')
75
+ end
76
+
77
+ def get_base_class_name(klass)
78
+ return klass.name.underscore if !defined?(ActiveRecord) || !klass.respond_to?(:base_class)
79
+ klass.base_class.name.underscore
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,28 @@
1
+ module DoRole
2
+ module Model
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def do_role(options = {})
7
+ role_association = options[:role_association] || :user_roles
8
+ role_class = options[:role_class] || 'DoRole::Role'
9
+
10
+ has_many role_association, dependent: :destroy
11
+ has_many :roles, through: role_association, class_name: role_class
12
+ end
13
+ end
14
+
15
+ included do
16
+ do_role
17
+ end
18
+
19
+ def has_permission?(permission)
20
+ return false unless permission
21
+ roles.any? { |role| role.has_permission?(permission) }
22
+ end
23
+
24
+ def computed_permissions
25
+ roles.map(&:computed_permissions).flatten.uniq
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ module PermissionManageable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ serialize :permissions, coder: JSON
9
+ validates :permissions, presence: true
10
+
11
+ after_initialize :initialize_permissions
12
+ end
13
+
14
+ # 检查是否拥有某个权限
15
+ def has_permission?(permission)
16
+ return false if permission.blank?
17
+ namespace, action = parse_permission(permission)
18
+ cached_permissions.dig(namespace)&.include?(action)
19
+ end
20
+
21
+ # 添加权限
22
+ def add_permission(permission)
23
+ validate_permission!(permission)
24
+ modify_permission(permission, :add)
25
+ end
26
+
27
+ # 移除权限
28
+ def remove_permission(permission)
29
+ return if permission.blank?
30
+ modify_permission(permission, :remove)
31
+ end
32
+
33
+ # 获取所有权限
34
+ def all_permissions
35
+ cached_permissions
36
+ end
37
+
38
+ # 计算完整权限路径
39
+ def computed_permissions
40
+ all_permissions.flat_map do |namespace, actions|
41
+ if actions.is_a?(Hash)
42
+ actions.flat_map do |sub_namespace, sub_actions|
43
+ Array.wrap(sub_actions).map { |action| "#{namespace}.#{sub_namespace}.#{action}" }
44
+ end
45
+ else
46
+ Array.wrap(actions).map { |action| "#{namespace}.#{action}" }
47
+ end
48
+ end
49
+ end
50
+
51
+ # 带缓存的权限
52
+ def cached_permissions
53
+ @cached_permissions ||= begin
54
+ all_permissions = permissions.deep_dup
55
+ if parent_role
56
+ parent_permissions = parent_role.cached_permissions
57
+ all_permissions.deep_merge!(parent_permissions) { |_, old_val, new_val| Array.wrap(old_val) | Array.wrap(new_val) }
58
+ end
59
+ all_permissions
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # 初始化权限
66
+ def initialize_permissions
67
+ self.permissions = permissions.presence || {}.with_indifferent_access
68
+ self.permissions = convert_array_to_hash if permissions.is_a?(Array)
69
+ end
70
+
71
+ # 修改权限(添加或删除)
72
+ def modify_permission(permission, action)
73
+ namespace, action_name = parse_permission(permission)
74
+ current_permissions = permissions.deep_dup
75
+
76
+ *path, last = namespace.split('.')
77
+ target = path.inject(current_permissions) { |hash, ns| hash[ns] ||= {} }
78
+
79
+ case action
80
+ when :add
81
+ target[last] = Array.wrap(target[last]) | [action_name]
82
+ when :remove
83
+ if actions = target[last]
84
+ Array.wrap(actions).delete(action_name)
85
+ target.delete(last) if actions.blank?
86
+ end
87
+ end
88
+
89
+ self.permissions = current_permissions
90
+ clear_permissions_cache
91
+ save if persisted?
92
+ end
93
+
94
+ # 解析权限字符串
95
+ def parse_permission(permission)
96
+ raise ArgumentError, "Permission cannot be nil or empty" if permission.blank?
97
+ permission.to_s.strip.split('.').then { |parts| [parts[0..-2].join('.'), parts.last] }
98
+ end
99
+
100
+ # 验证权限格式
101
+ def validate_permission!(permission)
102
+ normalized = normalize_permission(permission)
103
+ raise ArgumentError, "Invalid permission format" unless normalized.match?(/\A[\w]+(?:\.[\w]+)+\z/)
104
+ raise ArgumentError, "Permission must contain only letters, numbers, and dots" unless normalized.match?(/\A[a-zA-Z0-9_.]+\z/)
105
+ raise ArgumentError if normalized != permission
106
+ end
107
+
108
+ # 规范化权限字符串
109
+ def normalize_permission(permission)
110
+ permission.to_s.strip.downcase
111
+ end
112
+
113
+ # 将数组格式的权限转换为哈希
114
+ def convert_array_to_hash
115
+ return unless permissions.is_a?(Array)
116
+ permissions.select(&:present?).map(&:strip).each_with_object({}.with_indifferent_access) do |permission, hash|
117
+ namespace, action = parse_permission(permission)
118
+ *path, last = namespace.split('.')
119
+ current = path.inject(hash) { |h, ns| h[ns] ||= {} }
120
+ current[last] = Array.wrap(current[last]) | [action]
121
+ end
122
+ end
123
+
124
+ # 清除权限缓存
125
+ def clear_permissions_cache
126
+ @cached_permissions = nil
127
+ child_roles.each(&:clear_permissions_cache)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ module RoleModel
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :user_roles, dependent: :destroy
9
+ has_many :roles, through: :user_roles
10
+
11
+ serialize :permissions, coder: JSON
12
+
13
+ after_initialize do
14
+ self.permissions ||= {}
15
+ end
16
+ end
17
+
18
+ def has_permission?(permission)
19
+ return true if check_direct_permission(permission)
20
+ return true if roles.any? { |role| role.has_permission?(permission) }
21
+ false
22
+ end
23
+
24
+ def add_permission(permission)
25
+ self.permissions = (Array(permissions) + [permission.to_s]).uniq
26
+ end
27
+
28
+ def remove_permission(permission)
29
+ return unless permission
30
+ self.permissions = Array(permissions) - [permission.to_s]
31
+ end
32
+
33
+ def computed_permissions
34
+ role_permissions = roles.map(&:computed_permissions).flatten
35
+ direct_permissions = DoRole.permission_set.computed_permissions(permissions)
36
+ (role_permissions + direct_permissions).uniq
37
+ end
38
+
39
+ private
40
+
41
+ def check_direct_permission(permission)
42
+ return false unless permission
43
+ permissions.include?(permission.to_s)
44
+ end
45
+
46
+ def parse_permission(permission)
47
+ permission = permission.to_s.strip
48
+ namespace, action = permission.split('.')
49
+ [namespace, action]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ class ConditionsProxy
5
+ attr_reader :conditions
6
+ def initialize(conditions)
7
+ @conditions = conditions
8
+ end
9
+
10
+ def condition(name, value)
11
+ @conditions[name.to_sym] = value
12
+ end
13
+
14
+ def requires(*permissions)
15
+ @conditions[:requires] = permissions.flatten.map(&:to_s)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ class PermissionAudit
5
+ class << self
6
+ def log_changes(action, namespace, permission_name, options = {})
7
+ @audit_logs ||= []
8
+ @audit_logs << {
9
+ action: action,
10
+ namespace: namespace,
11
+ permission_name: permission_name,
12
+ options: options,
13
+ timestamp: Time.now
14
+ }
15
+ end
16
+
17
+ def clear_logs
18
+ @audit_logs = []
19
+ end
20
+
21
+ def get_logs(filters = {})
22
+ return [] if @audit_logs.nil?
23
+
24
+ logs = @audit_logs.dup
25
+ logs = logs.select { |log| log[:namespace] == filters[:namespace] } if filters[:namespace]
26
+ logs = logs.select { |log| log[:action] == filters[:action] } if filters[:action]
27
+ logs
28
+ end
29
+
30
+ def get_recent_logs(limit = 10)
31
+ return [] if @audit_logs.nil?
32
+ @audit_logs.last(limit)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ class PermissionSet
5
+ attr_reader :registered_permissions
6
+ alias :permissions :registered_permissions
7
+
8
+ RESERVED_OPTIONS = [:priority, :description, :requires, :default, :conditions].freeze
9
+
10
+ def initialize
11
+ @registered_permissions = {}.with_indifferent_access
12
+ @namespace_stack = []
13
+ end
14
+
15
+ def draw(&block)
16
+ instance_eval(&block) if block_given?
17
+ end
18
+
19
+ def namespace(name, options = {}, &block)
20
+ raise ArgumentError, "name can't be blank" if name.blank?
21
+
22
+ name = name.to_s
23
+ @namespace_stack.push(name)
24
+
25
+ instance_eval(&block) if block_given?
26
+
27
+ @namespace_stack.pop
28
+ end
29
+
30
+ def permission(name, options = {}, &block)
31
+ name = name.to_s
32
+ raise ArgumentError, "name can't be blank" if name.blank?
33
+
34
+ conditions = if block_given?
35
+ ConditionsProxy.new({}).tap { |proxy| proxy.instance_eval(&block) }.conditions
36
+ else
37
+ non_reserved_options = options.except(*RESERVED_OPTIONS)
38
+ options.except!(*non_reserved_options.keys)
39
+ non_reserved_options
40
+ end
41
+
42
+ options.reverse_merge!(
43
+ conditions: conditions,
44
+ priority: 0,
45
+ description: "",
46
+ requires: Array.wrap(options[:requires]).map(&:to_s)
47
+ )
48
+
49
+ register_permission(name, options)
50
+ end
51
+
52
+ def get(permission_name)
53
+ permission = registered_permissions[permission_name.to_s]
54
+ return permission if permission&.key?(:priority)
55
+ nil
56
+ end
57
+
58
+ def valid_permission?(permission_name)
59
+ permission = get(permission_name)
60
+ !permission.nil?
61
+ end
62
+
63
+ def computed_permissions(base_permissions)
64
+ base_permissions
65
+ .map(&:to_s)
66
+ .select { |p| valid_permission?(p) }
67
+ .tap { |result| result.concat(collect_required_permissions(result)) }
68
+ .uniq
69
+ end
70
+
71
+ def namespaces
72
+ collect_namespaces(registered_permissions, [], []).uniq
73
+ end
74
+
75
+ def permissions_in_namespace(namespace)
76
+ current = registered_permissions.dig(*namespace.to_s.split('.'))
77
+ return {} unless current.is_a?(Hash)
78
+
79
+ flatten_permissions(current, namespace.to_s)
80
+ end
81
+
82
+ private
83
+
84
+ def collect_required_permissions(permissions)
85
+ permissions.flat_map do |permission|
86
+ permission_config = get(permission)
87
+ next [] unless permission_config&.dig(:requires)&.present?
88
+
89
+ permission_config[:requires].map { |req| build_full_permission_path(permission, req) }
90
+ end
91
+ end
92
+
93
+ def build_full_permission_path(permission, req)
94
+ return req if req.include?('.')
95
+
96
+ namespace = permission.split('.')[0..-2].join('.')
97
+ "#{namespace}.#{req}"
98
+ end
99
+
100
+ def collect_namespaces(hash, current_path, result)
101
+ hash.each do |key, value|
102
+ if value.is_a?(Hash) && RESERVED_OPTIONS.none? { |opt| value.key?(opt) }
103
+ result << (current_path + [key]).join('.')
104
+ collect_namespaces(value, current_path + [key], result)
105
+ end
106
+ end
107
+ result
108
+ end
109
+
110
+ def flatten_permissions(hash, prefix)
111
+ hash.each_with_object({}) do |(key, value), result|
112
+ next unless value.is_a?(Hash)
113
+
114
+ full_key = prefix.presence ? "#{prefix}.#{key}" : key
115
+ if (RESERVED_OPTIONS & value.keys).present?
116
+ result[full_key] = value
117
+ else
118
+ result.merge!(flatten_permissions(value, full_key))
119
+ end
120
+ end
121
+ end
122
+
123
+ def register_permission(name, options = {})
124
+ options.reverse_merge!(conditions: {}, priority: 0, description: "")
125
+ options[:requires] = Array.wrap(options[:requires]).map(&:to_s) if options[:requires].present?
126
+
127
+ full_path = (@namespace_stack + [name]).join('.')
128
+
129
+ current_hash = @namespace_stack.inject(registered_permissions) do |hash, part|
130
+ hash[part] ||= {}
131
+ end
132
+
133
+ current_hash[name] = options.deep_dup
134
+ registered_permissions[full_path] = options.deep_dup
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ class PermissionSetManager
5
+ delegate :clear, to: :@permission_sets
6
+
7
+ def initialize
8
+ @permission_sets = {}.with_indifferent_access
9
+ end
10
+
11
+ def for_namespace(namespace)
12
+ @permission_sets[namespace.to_s] ||= PermissionSet.new
13
+ end
14
+
15
+ def namespaces
16
+ @permission_sets.keys
17
+ end
18
+
19
+ def namespace_exists?(namespace)
20
+ @permission_sets.key?(namespace.to_s)
21
+ end
22
+
23
+ def remove_namespace(namespace)
24
+ @permission_sets.delete(namespace.to_s)
25
+ end
26
+
27
+ def permissions_for(namespace)
28
+ @permission_sets[namespace.to_s].try(:permissions) || {}
29
+ end
30
+
31
+ def valid_permission?(namespace, permission_name)
32
+ @permission_sets[namespace.to_s].try(:valid_permission?, permission_name)
33
+ end
34
+
35
+ def set_inheritance(namespace, inherit_from)
36
+ target_set = for_namespace(namespace)
37
+ Array(inherit_from).each do |parent_namespace|
38
+ if (parent_set = @permission_sets[parent_namespace])
39
+ target_set.registered_permissions.deep_merge!(parent_set.registered_permissions)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ class Role < ActiveRecord::Base
5
+ include PermissionManageable
6
+
7
+ has_many :user_roles, dependent: :destroy
8
+ has_many :users, through: :user_roles
9
+ belongs_to :parent_role, class_name: 'DoRole::Role', optional: true
10
+ has_many :child_roles, class_name: 'DoRole::Role', foreign_key: 'parent_role_id'
11
+
12
+ validates :name, presence: true
13
+
14
+ def self.define_role_association(association_name, options = {})
15
+ has_many association_name, options.merge(dependent: :destroy)
16
+ has_many options[:through_model].to_s.pluralize.to_sym, through: association_name if options[:through_model]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ VERSION = "0.2.0"
5
+ end
data/lib/do_role.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "active_support"
5
+ require "do_role/version"
6
+ require "do_role/conditions_proxy"
7
+ require "do_role/concerns/model"
8
+ require "do_role/concerns/role_model"
9
+ require "do_role/concerns/permission_manageable"
10
+ require "do_role/permission_audit"
11
+ require "do_role/permission_set"
12
+ require "do_role/permission_set_manager"
13
+ require "do_role/ability"
14
+ require "do_role/role"
15
+
16
+ module DoRole
17
+ class Error < StandardError; end
18
+
19
+ class << self
20
+ def permission_set_manager
21
+ @permission_set_manager ||= PermissionSetManager.new
22
+ end
23
+
24
+ def permission_set(namespace = :default, inherit_from: [], &block)
25
+ set = permission_set_manager.for_namespace(namespace)
26
+ if inherit_from.any?
27
+ permission_set_manager.set_inheritance(namespace, inherit_from)
28
+ end
29
+ set.draw(&block) if block_given?
30
+ set
31
+ end
32
+
33
+ def reset_permissions(namespace = :default)
34
+ if namespace == :all
35
+ permission_set_manager.clear
36
+ else
37
+ permission_set_manager.remove_namespace(namespace)
38
+ end
39
+ end
40
+
41
+ def permissions(namespace = :default)
42
+ permission_set_manager.permissions_for(namespace)
43
+ end
44
+
45
+ def valid_permission?(permission_name, namespace = :default)
46
+ permission_set_manager.valid_permission?(namespace, permission_name)
47
+ end
48
+
49
+ def ability_for(user)
50
+ Ability.new(user)
51
+ end
52
+ end
53
+ end
54
+
55
+ # ActiveSupport.on_load(:active_record) do
56
+ # include DoRole::Model
57
+ # end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoRole
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def self.next_migration_number(dirname)
11
+ if ActiveRecord::Base.timestamped_migrations
12
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
13
+ else
14
+ format("%03d", current_migration_number(dirname) + 1)
15
+ end
16
+ end
17
+
18
+ def create_migration_file
19
+ migration_template(
20
+ 'create_do_role_tables.rb',
21
+ 'db/migrate/create_do_role_tables.rb'
22
+ )
23
+ end
24
+
25
+ def create_initializer
26
+ template 'do_role.rb', 'config/initializers/do_role.rb'
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDoRoleTables < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :roles do |t|
6
+ t.string :name, null: false
7
+ t.text :permissions
8
+ t.text :description
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :roles, :name, unique: true
14
+
15
+ create_table :user_roles do |t|
16
+ t.references :user, null: false, foreign_key: true
17
+ t.references :role, null: false, foreign_key: true
18
+
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :user_roles, [:user_id, :role_id], unique: true
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ DoRole.permission_set do
4
+ # 使用namespace组织权限
5
+ namespace :posts do
6
+ permission :index
7
+ permission :create
8
+ permission :update
9
+ permission :destroy
10
+ end
11
+
12
+ namespace :users do
13
+ permission :manage
14
+ permission :view
15
+ end
16
+
17
+ # 可以添加自定义选项
18
+ namespace :admin do
19
+ permission :dashboard, description: "访问管理后台"
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ <%= form_with model: @role do |f| %>
2
+ <div class="permission-form">
3
+ <% DoRole.permission_set.registered_permissions.group_by { |name, _| name.split('.').first }.each do |namespace, permissions| %>
4
+ <div class="permission-group">
5
+ <h3><%= namespace.titleize %></h3>
6
+ <div class="permission-items">
7
+ <% permissions.each do |permission_name, permission| %>
8
+ <div class="permission-item">
9
+ <%= check_box_tag "role[permissions][]", permission_name, @role.has_permission?(permission_name), id: "permission_#{permission_name.gsub('.', '_')}" %>
10
+ <%= label_tag "permission_#{permission_name.gsub('.', '_')}", permission_name %>
11
+ <% if permission[:description].present? %>
12
+ <small class="permission-description"><%= permission[:description] %></small>
13
+ <% end %>
14
+ </div>
15
+ <% end %>
16
+ </div>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+
21
+ <div class="form-actions">
22
+ <%= f.submit "保存", class: "btn btn-primary" %>
23
+ </div>
24
+ <% end %>
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: do_role
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - doabit
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-02-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 7.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 7.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: activerecord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 7.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 7.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: DoRole provides a clean and intuitive way to manage roles and permissions
98
+ in Rails applications
99
+ email:
100
+ - doinsist@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - README.md
106
+ - Rakefile
107
+ - lib/do_role.rb
108
+ - lib/do_role/ability.rb
109
+ - lib/do_role/concerns/model.rb
110
+ - lib/do_role/concerns/permission_manageable.rb
111
+ - lib/do_role/concerns/role_model.rb
112
+ - lib/do_role/conditions_proxy.rb
113
+ - lib/do_role/permission_audit.rb
114
+ - lib/do_role/permission_set.rb
115
+ - lib/do_role/permission_set_manager.rb
116
+ - lib/do_role/role.rb
117
+ - lib/do_role/version.rb
118
+ - lib/generators/do_role/install/install_generator.rb
119
+ - lib/generators/do_role/install/templates/create_do_role_tables.rb
120
+ - lib/generators/do_role/install/templates/do_role.rb
121
+ - lib/generators/do_role/templates/permission_form.html.erb
122
+ homepage: https://github.com/doabit/do_role
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubygems_version: 3.5.22
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: A simple and flexible role management solution for Rails applications
145
+ test_files: []