datacentred 0.1.1pre → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +11 -0
  3. data/.coveralls.yml +1 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +2 -0
  6. data/.yardopts +1 -0
  7. data/CODE_OF_CONDUCT.md +46 -0
  8. data/Gemfile.lock +39 -2
  9. data/LICENSE +21 -0
  10. data/README.md +160 -2
  11. data/circle.yml +0 -2
  12. data/datacentred.gemspec +17 -14
  13. data/docs/Datacentred.html +537 -0
  14. data/docs/Datacentred/Errors.html +260 -0
  15. data/docs/Datacentred/Errors/Error.html +139 -0
  16. data/docs/Datacentred/Errors/NotFound.html +145 -0
  17. data/docs/Datacentred/Errors/Unauthorized.html +149 -0
  18. data/docs/Datacentred/Errors/UnprocessableEntity.html +145 -0
  19. data/docs/Datacentred/Model.html +128 -0
  20. data/docs/Datacentred/Model/Base.html +255 -0
  21. data/docs/Datacentred/Model/Project.html +1729 -0
  22. data/docs/Datacentred/Model/Role.html +1830 -0
  23. data/docs/Datacentred/Model/Usage.html +510 -0
  24. data/docs/Datacentred/Model/User.html +832 -0
  25. data/docs/Datacentred/Model/Version.html +451 -0
  26. data/docs/Datacentred/Project.html +142 -0
  27. data/docs/Datacentred/Request.html +128 -0
  28. data/docs/Datacentred/Request/Base.html +675 -0
  29. data/docs/Datacentred/Request/Projects.html +1286 -0
  30. data/docs/Datacentred/Request/Roles.html +1286 -0
  31. data/docs/Datacentred/Request/Usage.html +315 -0
  32. data/docs/Datacentred/Request/Users.html +841 -0
  33. data/docs/Datacentred/Request/Versions.html +258 -0
  34. data/docs/Datacentred/Response.html +410 -0
  35. data/docs/Datacentred/Role.html +142 -0
  36. data/docs/Datacentred/Usage.html +142 -0
  37. data/docs/Datacentred/User.html +142 -0
  38. data/docs/Datacentred/Version.html +142 -0
  39. data/docs/_index.html +349 -0
  40. data/docs/class_list.html +51 -0
  41. data/docs/css/common.css +1 -0
  42. data/docs/css/full_list.css +58 -0
  43. data/docs/css/style.css +492 -0
  44. data/docs/file.README.html +231 -0
  45. data/docs/file_list.html +56 -0
  46. data/docs/frames.html +17 -0
  47. data/docs/index.html +231 -0
  48. data/docs/js/app.js +248 -0
  49. data/docs/js/full_list.js +216 -0
  50. data/docs/js/jquery.js +4 -0
  51. data/docs/method_list.html +643 -0
  52. data/docs/top-level-namespace.html +110 -0
  53. data/lib/datacentred.rb +65 -12
  54. data/lib/datacentred/error.rb +37 -15
  55. data/lib/datacentred/model/base.rb +21 -0
  56. data/lib/datacentred/model/project.rb +90 -31
  57. data/lib/datacentred/model/role.rb +89 -31
  58. data/lib/datacentred/model/usage.rb +16 -9
  59. data/lib/datacentred/model/user.rb +54 -22
  60. data/lib/datacentred/model/version.rb +17 -8
  61. data/lib/datacentred/request/base.rb +68 -31
  62. data/lib/datacentred/request/projects.rb +92 -24
  63. data/lib/datacentred/request/roles.rb +92 -24
  64. data/lib/datacentred/request/usage.rb +10 -1
  65. data/lib/datacentred/request/users.rb +58 -15
  66. data/lib/datacentred/request/versions.rb +13 -1
  67. data/lib/datacentred/response.rb +6 -2
  68. data/test/integration/authorization_test.rb +30 -0
  69. data/test/integration/projects_test.rb +11 -11
  70. data/test/integration/roles_test.rb +17 -17
  71. data/test/integration/usage_test.rb +8 -8
  72. data/test/integration/users_test.rb +23 -19
  73. data/test/integration/versions_test.rb +1 -2
  74. data/test/test_helper.rb +8 -5
  75. data/test/vcr_cassettes/not_authorized.yml +57 -0
  76. data/test/vcr_cassettes/unexpected_error.yml +56 -0
  77. metadata +115 -9
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.9
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Datacentred.html" title="Datacentred (module)">Datacentred</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Fri Sep 15 16:27:41 2017 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.9 (ruby-2.3.1).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -1,41 +1,94 @@
1
1
  require 'faraday'
2
2
  require 'json'
3
+ require 'recursive-open-struct'
3
4
  require 'time'
4
5
 
6
+ # Main Datacentred client module
7
+ #
8
+ # This library acts as a Ruby wrapper for the DataCentred API.
5
9
  module Datacentred
6
- require_relative 'datacentred/response'
7
10
  require_relative 'datacentred/error'
8
- require_relative 'datacentred/model/user'
11
+ require_relative 'datacentred/model/base'
9
12
  require_relative 'datacentred/model/project'
10
13
  require_relative 'datacentred/model/role'
11
14
  require_relative 'datacentred/model/usage'
15
+ require_relative 'datacentred/model/user'
12
16
  require_relative 'datacentred/model/version'
13
17
  require_relative 'datacentred/request/base'
14
18
  require_relative 'datacentred/request/projects'
15
- require_relative 'datacentred/request/users'
16
19
  require_relative 'datacentred/request/roles'
17
20
  require_relative 'datacentred/request/usage'
21
+ require_relative 'datacentred/request/users'
18
22
  require_relative 'datacentred/request/versions'
23
+ require_relative 'datacentred/response'
19
24
 
25
+ # Access key credential for the DataCentred API.
26
+ #
27
+ # This value is automatically loaded from the *DATACENTRED_ACCESS* environment variable.
28
+ #
29
+ # You can find your API credentials by logging into the dashboard at https://my.datacentred.io
30
+ #
31
+ # @return [String] API access key credential
20
32
  def self.access_key
21
33
  @@access_key ||= ENV['DATACENTRED_ACCESS']
22
34
  end
23
35
 
24
- def self.access_key=(val)
25
- @@access_key = val
36
+ # Set a new access key value.
37
+ #
38
+ # This will override any values loaded from environment variables.
39
+ #
40
+ # @param [String] new_access_key New access key value
41
+ # @return [String] API access key credential
42
+ def self.access_key=(new_access_key)
43
+ @@access_key = new_access_key
26
44
  end
27
45
 
46
+ # Secret key credential for the DataCentred API.
47
+ #
48
+ # This value is automatically loaded from the *DATACENTRED_SECRET* environment variable.
49
+ #
50
+ # You can find your API credentials by logging into the dashboard at https://my.datacentred.io
51
+ #
52
+ # @return [String] API secret key credential
28
53
  def self.secret_key
29
54
  @@secret_key ||= ENV['DATACENTRED_SECRET']
30
55
  end
31
56
 
32
- def self.secret_key=(val)
33
- @@secret_key = val
57
+ # Set a new secret key value.
58
+ #
59
+ # This will override any values loaded from environment variables.
60
+ #
61
+ # @param [String] new_secret_key New secret key value
62
+ #
63
+ # @return [String] API secret key credential
64
+ def self.secret_key=(new_secret_key)
65
+ @@secret_key = new_secret_key
34
66
  end
35
67
 
36
- User = Model::User
37
- Project = Model::Project
38
- Role = Model::Role
39
- Usage = Model::Usage
40
- Version = Model::Version
68
+ # Shorthand alias for {Model::Project}
69
+ # @see Model::Project
70
+ class Project < Model::Project; end
71
+
72
+ # Shorthand alias for {Model::Role}
73
+ # @see Model::Role
74
+ class Role < Model::Role; end
75
+
76
+ # Shorthand alias for {Model::Usage}
77
+ # @see Model::Usage
78
+ class Usage < Model::Usage; end
79
+
80
+ # Shorthand alias for {Model::User}
81
+ # @see Model::User
82
+ class User < Model::User; end
83
+
84
+ # Shorthand alias for {Model::Version}
85
+ # @see Model::Version
86
+ class Version < Model::Version; end
87
+
88
+ # Model classes representing RESTful API entities.
89
+ module Model ; end
90
+
91
+ # Request classes representing RESTful API interactions.
92
+ module Request
93
+ end
41
94
  end
@@ -1,28 +1,50 @@
1
1
  module Datacentred
2
- class Error < StandardError
2
+ # Behaviours and exceptions for recoverable errors.
3
+ module Errors
4
+ # Test server response and raise appropriate error if an error has been returned.
5
+ #
6
+ # @raise [Error] Appropriate error for server response code.
7
+ # @return [nil] Returns nil on success
3
8
  def self.raise_unless_successful(status, body)
4
9
  return if status.to_s.start_with? "2" # 2xx
5
- err = Datacentred::errors[status]
10
+ err = errors[status]
6
11
  message = body&.fetch("errors")&.first&.fetch("detail")
7
12
  if err
8
- raise err, message
13
+ raise err, message || status.to_s
9
14
  else
10
- raise Datacentred::Error, "Error #{status}: #{message}"
15
+ raise Error, "Error #{status}: #{message}"
11
16
  end
12
17
  end
13
- end
14
18
 
15
- class NotFoundError < StandardError; end
16
- class UnprocessableEntity < StandardError; end
17
- class Unauthorized < StandardError; end
19
+ # Datacentred base error
20
+ class Error < StandardError ; end
21
+
22
+ # Raised when an entity cannot be located using the unique id specified.
23
+ #
24
+ # Corresponds to a HTTP 404 error.
25
+ class NotFound < Error; end
26
+
27
+ # Raised usually when data validations fail on operations that mutate state.
28
+ #
29
+ # Corresponds to a HTTP 422 error.
30
+ class UnprocessableEntity < Error; end
18
31
 
19
- private
32
+ # Raised when credentials are invalid.
33
+ #
34
+ # Credentials may be invalid because they're incorrect, or because they correspond to an account
35
+ # that does not have the correct permissions to access the API.
36
+ #
37
+ # Corresponds to a HTTP 403 error.
38
+ class Unauthorized < Error; end
20
39
 
21
- def self.errors
22
- {
23
- 401 => Unauthorized,
24
- 404 => NotFoundError,
25
- 422 => UnprocessableEntity
26
- }
40
+ private
41
+
42
+ def self.errors
43
+ {
44
+ 401 => Datacentred::Errors::Unauthorized,
45
+ 404 => Datacentred::Errors::NotFound,
46
+ 422 => Datacentred::Errors::UnprocessableEntity
47
+ }
48
+ end
27
49
  end
28
50
  end
@@ -0,0 +1,21 @@
1
+ module Datacentred
2
+ module Model
3
+ # Base class for all API models.
4
+ #
5
+ # Uses Recursive Structs to allow nested property access.
6
+ class Base < RecursiveOpenStruct
7
+ # Instantiate a new model object.
8
+ #
9
+ # @param [Hash] params Object properties as returned by the API.
10
+ def initialize(params, _opts=nil)
11
+ params.delete "links" if params['links']
12
+
13
+ ["created_at", "updated_at", "last_updated_at"].each do |key|
14
+ params[key] = Time.parse(params[key]) if params[key]
15
+ end
16
+
17
+ super params, recurse_over_arrays: true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,43 +1,102 @@
1
1
  module Datacentred
2
2
  module Model
3
- class Project < OpenStruct
4
- def initialize(params)
5
- params.delete("links") if params
6
- params["created_at"] = Time.parse params["created_at"]
7
- params["updated_at"] = Time.parse params["updated_at"]
8
- super(params)
9
- end
3
+ # A project on your DataCentred account.
4
+ #
5
+ # Projects (also called "Cloud Projects" or "Tenants") are a way of grouping together users and resources.
6
+ #
7
+ # All projects created in your DataCented account are backed by a corresponding project in OpenStack's identity service (Keystone).
8
+ #
9
+ # @attr [String] id
10
+ # @attr [String] name
11
+ # @attr [Hash] quota_set
12
+ # @attr_reader [Time] created_at
13
+ # @attr_reader [Time] updated_at
14
+ class Project < Base
15
+ class << self
16
+ # Create a new project.
17
+ #
18
+ # @param [Hash] params Project attributes
19
+ # @raise [Errors::UnprocessableEntity] Raised if validations fail for the supplied attributes.
20
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
21
+ # @return [Project] New project.
22
+ def create(params)
23
+ new Request::Projects.create params
24
+ end
10
25
 
11
- def self.all
12
- Request::Projects.list.map{ |project| new(project) }
13
- end
26
+ # List all available projects.
27
+ #
28
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
29
+ # @return [[Project]] A collection of all projects on this account.
30
+ def all
31
+ Request::Projects.list.map{|project| new project }
32
+ end
14
33
 
15
- def self.find(id)
16
- new Request::Projects.show(id)
17
- end
34
+ # Find a project by unique ID.
35
+ #
36
+ # @param [String] id The unique identifier for this project.
37
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
38
+ # @raise [Errors::NotFound] Raised if the project couldn't be found.
39
+ # @return [Project] The project, if it exists.
40
+ def find(id)
41
+ new Request::Projects.show id
42
+ end
18
43
 
19
- def self.create(params)
20
- new Request::Projects.create(params)
21
- end
44
+ # Update a project by unique ID.
45
+ #
46
+ # @param [String] id The unique identifier for this project.
47
+ # @param [Hash] params Project attributes.
48
+ # @raise [Errors::UnprocessableEntity] Raised if validations fail for the supplied attributes.
49
+ # @raise [Errors::NotFound] Raised if the project could not be found.
50
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
51
+ # @return [Project] The updated project.
52
+ def update(id, params)
53
+ new Request::Projects.update id, params
54
+ end
22
55
 
23
- def self.update(id, params)
24
- new Request::Projects.update(id, params)
25
- end
56
+ # Permanently remove the specified project.
57
+ #
58
+ # @param [String] id The unique identifier for this project.
59
+ # @raise [Errors::NotFound] Raised if the project couldn't be found.
60
+ # @raise [Errors::UnprocessableEntity] Raised if validations fail for the specified project.
61
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
62
+ # @return [Boolean] Confirms the user was destroyed.
63
+ def destroy(id)
64
+ Request::Projects.destroy id
65
+ true
66
+ end
26
67
 
27
- def self.remove(id)
28
- Request::Projects.destroy(id)
29
- end
30
-
31
- def self.users(id)
32
- Request::Projects.list_users(id).map{ |user| new(user) }
33
- end
68
+ # List all users assigned to this project.
69
+ #
70
+ # @param [String] id The unique identifier for this project.
71
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
72
+ # @return [[User]] A collection of the project's users.
73
+ def users(id)
74
+ Request::Projects.list_users(id).map{|user| new user }
75
+ end
34
76
 
35
- def self.add_user(project_id, user_id)
36
- Request::Projects.add_user(project_id, user_id)
37
- end
77
+ # Add a new user to this project, giving them access to it via OpenStack.
78
+ #
79
+ # @param [String] project_id The unique identifier for this project.
80
+ # @param [String] user_id The unique identifier for this user.
81
+ # @raise [Errors::NotFound] Raised if the project or user couldn't be found.
82
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
83
+ # @return [Boolean] Confirms the user was added (or is already present).
84
+ def add_user(project_id:, user_id:)
85
+ Request::Projects.add_user project_id, user_id
86
+ true
87
+ end
38
88
 
39
- def self.remove_user(project_id, user_id)
40
- Request::Projects.remove_user(project_id, user_id)
89
+ # Remove user from this project, revoking their access to it on OpenStack.
90
+ #
91
+ # @param [String] project_id The unique identifier for this project.
92
+ # @param [String] user_id The unique identifier for this user.
93
+ # @raise [Errors::NotFound] Raised if project or user couldn't be found.
94
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
95
+ # @return [Boolean] Confirms that user was removed (or is already absent).
96
+ def remove_user(project_id:, user_id:)
97
+ Request::Projects.remove_user project_id, user_id
98
+ true
99
+ end
41
100
  end
42
101
  end
43
102
  end
@@ -1,43 +1,101 @@
1
1
  module Datacentred
2
2
  module Model
3
- class Role < OpenStruct
4
- def initialize(params)
5
- params.delete("links")
6
- params["created_at"] = Time.parse params["created_at"]
7
- params["updated_at"] = Time.parse params["updated_at"]
8
- super(params)
9
- end
3
+ # A role on your DataCentred account.
4
+ #
5
+ # Roles allow simple setup of user permissions via the creation of roles, then assigning those roles to users.
6
+ #
7
+ # @attr [String] id
8
+ # @attr [String] name
9
+ # @attr [Boolean] admin
10
+ # @attr [[String]] permissions
11
+ # @attr_reader [Time] created_at
12
+ # @attr_reader [Time] updated_at
13
+ class Role < Base
14
+ class << self
15
+ # Create a new role.
16
+ #
17
+ # @param [Hash] params Role attributes.
18
+ # @raise [Errors::UnprocessableEntity] Raised if validations fail for the supplied attributes.
19
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
20
+ # @return [Role] New role.
21
+ def create(params)
22
+ new Request::Roles.create params
23
+ end
10
24
 
11
- def self.all
12
- Request::Roles.list.map{|role| new(role) }
13
- end
25
+ # List all available roles.
26
+ #
27
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
28
+ # @return [[Role]] A collection of all roles on this account.
29
+ def all
30
+ Request::Roles.list.map{|role| new role }
31
+ end
14
32
 
15
- def self.find(id)
16
- new Request::Roles.show(id)
17
- end
33
+ # Find a role by unique ID.
34
+ #
35
+ # @param [String] id The unique identifier for this role.
36
+ # @raise [Errors::NotFound] Raised if the role couldn't be found.
37
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
38
+ # @return [Role] The role, if it exists.
39
+ def find(id)
40
+ new Request::Roles.show id
41
+ end
18
42
 
19
- def self.create(params)
20
- new Request::Roles.create(params)
21
- end
43
+ # Update a role by unique ID.
44
+ #
45
+ # @param [String] id The unique identifier for this role.
46
+ # @param [Hash] params Role attributes.
47
+ # @raise [Errors::UnprocessableEntity] Raised if validations fail for the supplied attributes.
48
+ # @raise [Errors::NotFound] Raised if the role doesn't exist.
49
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
50
+ # @return [Role] The updated role.
51
+ def update(id, params)
52
+ new Request::Roles.update id, params
53
+ end
22
54
 
23
- def self.update(id, params)
24
- new Request::Roles.update(id, params)
25
- end
55
+ # Permanently remove the specified role.
56
+ #
57
+ # @param [String] id The unique identifier for this role.
58
+ # @raise [Errors::NotFound] Raised if the role couldn't be found.
59
+ # @raise [Errors::UnprocessableEntity] Raised if validations fail for the specifed role.
60
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
61
+ # @return [Boolean] Confirms the role was destroyed.
62
+ def destroy(id)
63
+ Request::Roles.destroy id
64
+ true
65
+ end
26
66
 
27
- def self.delete(id)
28
- Request::Roles.destroy(id)
29
- end
30
-
31
- def self.users(id)
32
- Request::Roles.list_users(id).map{|user| new(user) }
33
- end
67
+ # List all users assigned to this role.
68
+ #
69
+ # @param [String] id The unique identifier for this role.
70
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
71
+ # @return [[User]] A collection of the role's users.
72
+ def users(id)
73
+ Request::Roles.list_users(id).map{|user| new user }
74
+ end
34
75
 
35
- def self.add_user(role_id, user_id)
36
- Request::Roles.add_user(role_id, user_id)
37
- end
76
+ # Add new user to this role, giving them the associated permissions.
77
+ #
78
+ # @param [String] role_id The unique identifier for this role.
79
+ # @param [String] user_id The unique identifier for this user.
80
+ # @raise [Errors::NotFound] Raised if the role or user couldn't be found.
81
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
82
+ # @return [Boolean] Confirms that user was added (or is already present).
83
+ def add_user(role_id:, user_id:)
84
+ Request::Roles.add_user role_id, user_id
85
+ true
86
+ end
38
87
 
39
- def self.remove_user(role_id, user_id)
40
- Request::Roles.remove_user(role_id, user_id)
88
+ # Remove user from this role, revoking the associated permissions.
89
+ #
90
+ # @param [String] role_id The unique identifier for this role.
91
+ # @param [String] user_id The unique identifier for this user.
92
+ # @raise [Errors::NotFound] Raised if the role or user coundn't be found.
93
+ # @raise [Errors::Unauthorized] Raised if credentials aren't valid.
94
+ # @return [Boolean] Confirms that user was removed (or is already absent).
95
+ def remove_user(role_id:, user_id:)
96
+ Request::Roles.remove_user role_id, user_id
97
+ true
98
+ end
41
99
  end
42
100
  end
43
101
  end